Permalink
Fetching contributors…
Cannot retrieve contributors at this time
358 lines (313 sloc) 11.5 KB
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
/*!
* This module implements the PBKDF2 Key Derivation Function as specified by
* http://tools.ietf.org/html/rfc2898.
*/
use std::iter::repeat;
use std::io;
use cryptoutil::copy_memory;
use rand::{OsRng, Rng};
use serialize::base64;
use serialize::base64::{FromBase64, ToBase64};
use cryptoutil::{read_u32_be, write_u32_be};
use hmac::Hmac;
use mac::Mac;
use sha2::Sha256;
use util::fixed_time_eq;
// Calculate a block of the output of size equal to the output_bytes of the underlying Mac function
// mac - The Mac function to use
// salt - the salt value to use
// c - the iteration count
// idx - the 1 based index of the block
// scratch - a temporary variable the same length as the block
// block - the block of the output to calculate
fn calculate_block<M: Mac>(
mac: &mut M,
salt: &[u8],
c: u32,
idx: u32,
scratch: &mut [u8],
block: &mut [u8]) {
// Perform the 1st iteration. The output goes directly into block
mac.input(salt);
let mut idx_buf = [0u8; 4];
write_u32_be(&mut idx_buf, idx);
mac.input(&idx_buf);
mac.raw_result(block);
mac.reset();
// Perform the 2nd iteration. The input comes from block and is output into scratch. scratch is
// then exclusive-or added into block. After all this, the input to the next step is now in
// scratch and block is left to just accumulate the exclusive-of sum of remaining iterations.
if c > 1 {
mac.input(block);
mac.raw_result(scratch);
mac.reset();
for (output, &input) in block.iter_mut().zip(scratch.iter()) {
*output ^= input;
}
}
// Perform all remaining iterations
for _ in 2..c {
mac.input(scratch);
mac.raw_result(scratch);
mac.reset();
for (output, &input) in block.iter_mut().zip(scratch.iter()) {
*output ^= input;
}
}
}
/**
* Execute the PBKDF2 Key Derivation Function. The Scrypt Key Derivation Function generally provides
* better security, so, applications that do not have a requirement to use PBKDF2 specifically
* should consider using that function instead.
*
* # Arguments
* * mac - The Pseudo Random Function to use.
* * salt - The salt value to use.
* * c - The iteration count. Users should carefully determine this value as it is the primary
* factor in determining the security of the derived key.
* * output - The output buffer to fill with the derived key value.
*
*/
pub fn pbkdf2<M: Mac>(mac: &mut M, salt: &[u8], c: u32, output: &mut [u8]) {
assert!(c > 0);
let os = mac.output_bytes();
// A temporary storage array needed by calculate_block. This is really only necessary if c > 1.
// Most users of pbkdf2 should use a value much larger than 1, so, this allocation should almost
// always be necessary. A big exception is Scrypt. However, this allocation is unlikely to be
// the bottleneck in Scrypt performance.
let mut scratch: Vec<u8> = repeat(0).take(os).collect();
let mut idx: u32 = 0;
for chunk in output.chunks_mut(os) {
// The block index starts at 1. So, this is supposed to run on the first execution.
idx = idx.checked_add(1).expect("PBKDF2 size limit exceeded.");
if chunk.len() == os {
calculate_block(mac, salt, c, idx, &mut scratch, chunk);
} else {
let mut tmp: Vec<u8> = repeat(0).take(os).collect();
calculate_block(mac, salt, c, idx, &mut scratch[..], &mut tmp[..]);
let chunk_len = chunk.len();
copy_memory(&tmp[..chunk_len], chunk);
}
}
}
/**
* pbkdf2_simple is a helper function that should be sufficient for the majority of cases where
* an application needs to use PBKDF2 to hash a password for storage. The result is a String that
* contains the parameters used as part of its encoding. The pbkdf2_check function may be used on
* a password to check if it is equal to a hashed value.
*
* # Format
*
* The format of the output is a modified version of the Modular Crypt Format that encodes algorithm
* used and iteration count. The format is indicated as "rpbkdf2" which is short for "Rust PBKF2
* format."
*
* $rpbkdf2$0$<base64(c)>$<base64(salt)>$<based64(hash)>$
*
* # Arguments
*
* * password - The password to process as a str
* * c - The iteration count
*
*/
pub fn pbkdf2_simple(password: &str, c: u32) -> io::Result<String> {
let mut rng = try!(OsRng::new());
// 128-bit salt
let salt: Vec<u8> = rng.gen_iter::<u8>().take(16).collect();
// 256-bit derived key
let mut dk = [0u8; 32];
let mut mac = Hmac::new(Sha256::new(), password.as_bytes());
pbkdf2(&mut mac, &salt[..], c, &mut dk);
let mut result = "$rpbkdf2$0$".to_string();
let mut tmp = [0u8; 4];
write_u32_be(&mut tmp, c);
result.push_str(&tmp.to_base64(base64::STANDARD)[..]);
result.push('$');
result.push_str(&salt.to_base64(base64::STANDARD)[..]);
result.push('$');
result.push_str(&dk.to_base64(base64::STANDARD)[..]);
result.push('$');
Ok(result)
}
/**
* pbkdf2_check compares a password against the result of a previous call to pbkdf2_simple and
* returns true if the passed in password hashes to the same value.
*
* # Arguments
*
* * password - The password to process as a str
* * hashed_value - A string representing a hashed password returned by pbkdf2_simple()
*
*/
pub fn pbkdf2_check(password: &str, hashed_value: &str) -> Result<bool, &'static str> {
static ERR_STR: &'static str = "Hash is not in Rust PBKDF2 format.";
let mut iter = hashed_value.split('$');
// Check that there are no characters before the first "$"
match iter.next() {
Some(x) => if x != "" { return Err(ERR_STR); },
None => return Err(ERR_STR)
}
// Check the name
match iter.next() {
Some(t) => if t != "rpbkdf2" { return Err(ERR_STR); },
None => return Err(ERR_STR)
}
// Parse format - currenlty only version 0 is supported
match iter.next() {
Some(fstr) => {
match fstr {
"0" => { }
_ => return Err(ERR_STR)
}
}
None => return Err(ERR_STR)
}
// Parse the iteration count
let c = match iter.next() {
Some(pstr) => match pstr.from_base64() {
Ok(pvec) => {
if pvec.len() != 4 { return Err(ERR_STR); }
read_u32_be(&pvec[..])
}
Err(_) => return Err(ERR_STR)
},
None => return Err(ERR_STR)
};
// Salt
let salt = match iter.next() {
Some(sstr) => match sstr.from_base64() {
Ok(salt) => salt,
Err(_) => return Err(ERR_STR)
},
None => return Err(ERR_STR)
};
// Hashed value
let hash = match iter.next() {
Some(hstr) => match hstr.from_base64() {
Ok(hash) => hash,
Err(_) => return Err(ERR_STR)
},
None => return Err(ERR_STR)
};
// Make sure that the input ends with a "$"
match iter.next() {
Some(x) => if x != "" { return Err(ERR_STR); },
None => return Err(ERR_STR)
}
// Make sure there is no trailing data after the final "$"
match iter.next() {
Some(_) => return Err(ERR_STR),
None => { }
}
let mut mac = Hmac::new(Sha256::new(), password.as_bytes());
let mut output: Vec<u8> = repeat(0).take(hash.len()).collect();
pbkdf2(&mut mac, &salt[..], c, &mut output[..]);
// Be careful here - its important that the comparison be done using a fixed time equality
// check. Otherwise an adversary that can measure how long this step takes can learn about the
// hashed value which would allow them to mount an offline brute force attack against the
// hashed password.
Ok(fixed_time_eq(&output[..], &hash[..]))
}
#[cfg(test)]
mod test {
use std::iter::repeat;
use pbkdf2::{pbkdf2, pbkdf2_simple, pbkdf2_check};
use hmac::Hmac;
use sha1::Sha1;
struct Test {
password: Vec<u8>,
salt: Vec<u8>,
c: u32,
expected: Vec<u8>
}
// Test vectors from http://tools.ietf.org/html/rfc6070. The 4th test vector is omitted because
// it takes too long to run.
fn tests() -> Vec<Test> {
vec![
Test {
password: b"password".to_vec(),
salt: b"salt".to_vec(),
c: 1,
expected: vec![
0x0c, 0x60, 0xc8, 0x0f, 0x96, 0x1f, 0x0e, 0x71,
0xf3, 0xa9, 0xb5, 0x24, 0xaf, 0x60, 0x12, 0x06,
0x2f, 0xe0, 0x37, 0xa6 ]
},
Test {
password: b"password".to_vec(),
salt: b"salt".to_vec(),
c: 2,
expected: vec![
0xea, 0x6c, 0x01, 0x4d, 0xc7, 0x2d, 0x6f, 0x8c,
0xcd, 0x1e, 0xd9, 0x2a, 0xce, 0x1d, 0x41, 0xf0,
0xd8, 0xde, 0x89, 0x57 ]
},
Test {
password: b"password".to_vec(),
salt: b"salt".to_vec(),
c: 4096,
expected: vec![
0x4b, 0x00, 0x79, 0x01, 0xb7, 0x65, 0x48, 0x9a,
0xbe, 0xad, 0x49, 0xd9, 0x26, 0xf7, 0x21, 0xd0,
0x65, 0xa4, 0x29, 0xc1 ]
},
Test {
password: b"passwordPASSWORDpassword".to_vec(),
salt: b"saltSALTsaltSALTsaltSALTsaltSALTsalt".to_vec(),
c: 4096,
expected: vec![
0x3d, 0x2e, 0xec, 0x4f, 0xe4, 0x1c, 0x84, 0x9b,
0x80, 0xc8, 0xd8, 0x36, 0x62, 0xc0, 0xe4, 0x4a,
0x8b, 0x29, 0x1a, 0x96, 0x4c, 0xf2, 0xf0, 0x70, 0x38 ]
},
Test {
password: vec![112, 97, 115, 115, 0, 119, 111, 114, 100],
salt: vec![115, 97, 0, 108, 116],
c: 4096,
expected: vec![
0x56, 0xfa, 0x6a, 0xa7, 0x55, 0x48, 0x09, 0x9d,
0xcc, 0x37, 0xd7, 0xf0, 0x34, 0x25, 0xe0, 0xc3 ]
}
]
}
#[test]
fn test_pbkdf2() {
let tests = tests();
for t in tests.iter() {
let mut mac = Hmac::new(Sha1::new(), &t.password[..]);
let mut result: Vec<u8> = repeat(0).take(t.expected.len()).collect();
pbkdf2(&mut mac, &t.salt[..], t.c, &mut result);
assert!(result == t.expected);
}
}
#[test]
fn test_pbkdf2_simple() {
let password = "password";
let out1 = pbkdf2_simple(password, 1024).unwrap();
let out2 = pbkdf2_simple(password, 1024).unwrap();
// This just makes sure that a salt is being applied. It doesn't verify that that salt is
// cryptographically strong, however.
assert!(out1 != out2);
match pbkdf2_check(password, &out1[..]) {
Ok(r) => assert!(r),
Err(_) => panic!()
}
match pbkdf2_check(password, &out2[..]) {
Ok(r) => assert!(r),
Err(_) => panic!()
}
match pbkdf2_check("wrong", &out1[..]) {
Ok(r) => assert!(!r),
Err(_) => panic!()
}
match pbkdf2_check("wrong", &out2[..]) {
Ok(r) => assert!(!r),
Err(_) => panic!()
}
}
}