use sha2::{Digest, Sha256};
const HMAC_SHA256_BLOCK: usize = 64;
pub fn hmac_sha256(key: &[u8], data: &[u8]) -> [u8; 32] {
let mut padded = [0u8; HMAC_SHA256_BLOCK];
if key.len() > HMAC_SHA256_BLOCK {
let h = sha256(key);
padded[..32].copy_from_slice(&h);
} else {
padded[..key.len()].copy_from_slice(key);
}
let mut ipad = [0u8; HMAC_SHA256_BLOCK];
let mut opad = [0u8; HMAC_SHA256_BLOCK];
for i in 0..HMAC_SHA256_BLOCK {
ipad[i] = padded[i] ^ 0x36;
opad[i] = padded[i] ^ 0x5c;
}
let mut inner = Sha256::new();
inner.update(ipad);
inner.update(data);
let inner_hash = inner.finalize();
let mut outer = Sha256::new();
outer.update(opad);
outer.update(inner_hash);
let mut out = [0u8; 32];
out.copy_from_slice(&outer.finalize());
out
}
pub fn sha256(data: &[u8]) -> [u8; 32] {
let mut h = Sha256::new();
h.update(data);
let mut out = [0u8; 32];
out.copy_from_slice(&h.finalize());
out
}
pub fn pbkdf2_sha256(password: &[u8], salt: &[u8], iter: u32) -> [u8; 32] {
let mut block = [0u8; 32];
let mut salted = Vec::with_capacity(salt.len() + 4);
salted.extend_from_slice(salt);
salted.extend_from_slice(&1u32.to_be_bytes());
let mut u = hmac_sha256(password, &salted);
block.copy_from_slice(&u);
for _ in 1..iter {
u = hmac_sha256(password, &u);
for i in 0..32 {
block[i] ^= u[i];
}
}
block
}
pub fn xor(a: &[u8], b: &[u8]) -> Vec<u8> {
a.iter().zip(b.iter()).map(|(x, y)| x ^ y).collect()
}
pub fn client_proof(password: &[u8], salt: &[u8], iter: u32, auth_message: &[u8]) -> Vec<u8> {
let salted = pbkdf2_sha256(password, salt, iter);
let client_key = hmac_sha256(&salted, b"Client Key");
let stored_key = sha256(&client_key);
let signature = hmac_sha256(&stored_key, auth_message);
xor(&client_key, &signature)
}
pub fn verify_server_signature(
password: &[u8],
salt: &[u8],
iter: u32,
auth_message: &[u8],
presented_signature: &[u8],
) -> bool {
if presented_signature.len() != 32 {
return false;
}
let salted = pbkdf2_sha256(password, salt, iter);
let server_key = hmac_sha256(&salted, b"Server Key");
let expected = hmac_sha256(&server_key, auth_message);
constant_time_eq(&expected, presented_signature)
}
fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
if a.len() != b.len() {
return false;
}
let mut diff = 0u8;
for i in 0..a.len() {
diff |= a[i] ^ b[i];
}
diff == 0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hmac_sha256_known_vector() {
let key = [0x0bu8; 20];
let data = b"Hi There";
let mac = hmac_sha256(&key, data);
let expected = [
0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0x0b,
0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x00, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c,
0x2e, 0x32, 0xcf, 0xf7,
];
assert_eq!(mac, expected);
}
#[test]
fn pbkdf2_smoke() {
let a = pbkdf2_sha256(b"password", b"salt", 1024);
let b = pbkdf2_sha256(b"password", b"salt", 1024);
assert_eq!(a, b);
let c = pbkdf2_sha256(b"different", b"salt", 1024);
assert_ne!(a, c);
}
#[test]
fn proof_round_trip_via_client_function() {
let salt = b"reddb-test";
let iter = 4096;
let password = b"hunter2";
let auth_message = b"client-first-bare,server-first,client-final-no-proof";
let proof_a = client_proof(password, salt, iter, auth_message);
let proof_b = client_proof(password, salt, iter, auth_message);
assert_eq!(proof_a, proof_b);
let proof_wrong = client_proof(b"wrong", salt, iter, auth_message);
assert_ne!(proof_a, proof_wrong);
}
}