extern crate alloc;
use alloc::vec;
use alloc::vec::Vec;
use crate::cipher::blowfish::Blowfish;
use crate::hash::{Digest, Sha512};
const MAX_KEYLEN: usize = 1024;
const BCRYPT_HASHSIZE: usize = 32;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Error {
InvalidParameters,
}
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("bcrypt_pbkdf: invalid parameters")
}
}
impl core::error::Error for Error {}
pub fn bcrypt_pbkdf(
password: &[u8],
salt: &[u8],
rounds: u32,
keylen: usize,
) -> Result<Vec<u8>, Error> {
if rounds == 0 || keylen == 0 || keylen > MAX_KEYLEN {
return Err(Error::InvalidParameters);
}
let stride: usize = keylen.div_ceil(BCRYPT_HASHSIZE);
let initial_amt: usize = keylen.div_ceil(stride);
let mut sha2pass: [u8; 64] = Sha512::digest(password);
let mut out = vec![0u8; keylen];
let mut remaining = keylen;
let mut count: u32 = 1;
while remaining > 0 {
let mut salt_hasher = Sha512::new();
salt_hasher.update(salt);
salt_hasher.update(&count.to_be_bytes());
let mut sha2salt: [u8; 64] = salt_hasher.finalize();
let mut tmpout = bcrypt_hash(&sha2pass, &sha2salt);
let mut block = tmpout;
for _ in 1..rounds {
sha2salt = Sha512::digest(&tmpout);
tmpout = bcrypt_hash(&sha2pass, &sha2salt);
for j in 0..BCRYPT_HASHSIZE {
block[j] ^= tmpout[j];
}
}
let amt = initial_amt.min(remaining);
let mut written = 0usize;
for (i, &b) in block.iter().take(amt).enumerate() {
let dest = i * stride + (count as usize - 1);
if dest >= keylen {
break;
}
out[dest] = b;
written += 1;
}
remaining -= written;
count += 1;
wipe(&mut sha2salt);
wipe(&mut tmpout);
wipe(&mut block);
}
wipe(&mut sha2pass);
Ok(out)
}
fn wipe(buf: &mut [u8]) {
for b in buf.iter_mut() {
*b = 0;
}
let _ = core::hint::black_box(buf);
}
fn bcrypt_hash(sha2pass: &[u8; 64], sha2salt: &[u8; 64]) -> [u8; BCRYPT_HASHSIZE] {
let mut state = Blowfish::new();
state.eks_setup(sha2salt, sha2pass);
for _ in 0..64 {
state.expand_key(sha2salt);
state.expand_key(sha2pass);
}
let mut cdata: [u32; 8] = [
0x4f78_7963, 0x6872_6f6d, 0x6174_6963, 0x426c_6f77, 0x6669_7368, 0x5377_6174, 0x4479_6e61, 0x6d69_7465, ];
for _ in 0..64 {
let mut i = 0;
while i < 8 {
let (mut l, mut r) = (cdata[i], cdata[i + 1]);
state.encipher(&mut l, &mut r);
cdata[i] = l;
cdata[i + 1] = r;
i += 2;
}
}
let mut out = [0u8; BCRYPT_HASHSIZE];
for i in 0..8 {
out[4 * i..4 * i + 4].copy_from_slice(&cdata[i].to_le_bytes());
}
state.p.iter_mut().for_each(|w| *w = 0);
for sbox in state.s.iter_mut() {
sbox.iter_mut().for_each(|w| *w = 0);
}
let _ = core::hint::black_box(&state.p);
let _ = core::hint::black_box(&state.s);
cdata.iter_mut().for_each(|w| *w = 0);
let _ = core::hint::black_box(&cdata);
out
}
#[cfg(test)]
mod tests {
use super::*;
fn from_hex(s: &str) -> Vec<u8> {
let bytes: Vec<u8> = s.bytes().filter(|b| !b.is_ascii_whitespace()).collect();
assert!(bytes.len().is_multiple_of(2));
bytes
.chunks(2)
.map(|p| {
let hi = (p[0] as char).to_digit(16).unwrap() as u8;
let lo = (p[1] as char).to_digit(16).unwrap() as u8;
(hi << 4) | lo
})
.collect()
}
#[test]
fn go_bcrypt_hash_kat() {
let mut pass = [0u8; 64];
let mut salt = [0u8; 64];
for i in 0..64 {
pass[i] = i as u8;
salt[i] = (i + 64) as u8;
}
let out = bcrypt_hash(&pass, &salt);
let expected = from_hex("87904870eef9deddf8e7611a140106e6aaf1a363d9a2c504db356443721eb555");
assert_eq!(&out[..], &expected[..]);
}
#[test]
fn pbkdf_vector_1() {
let out = bcrypt_pbkdf(b"password", b"salt", 4, 32).unwrap();
let expected = from_hex("5bbf0cc293587f1c3635555c27796598d47e579071bf427e9d8fbe842aba34d9");
assert_eq!(out, expected);
}
#[test]
fn pbkdf_go_vector_rounds12() {
let out = bcrypt_pbkdf(b"password", b"salt", 12, 32).unwrap();
let expected = from_hex("1ae42c05d487bc02f64921a4ebe4ea93bcacfe135fda99974c06b7b01fae149a");
assert_eq!(out, expected);
}
#[test]
fn pbkdf_go_vector_stride() {
let pwd: &[u8] = b"passwordy\x00PASSWORD\x00";
let salt: &[u8] = b"salty\x00SALT\x00";
let out = bcrypt_pbkdf(pwd, salt, 3, 32).unwrap();
let expected = from_hex("7f310bd3e78c3280c59ce4595211a2928e8d4ec744c1ed2efc9f764e3388e0ad");
assert_eq!(out, expected);
}
#[test]
fn pbkdf_go_vector_long_output() {
let pwd = "секретное слово".as_bytes();
let salt = "посолить немножко".as_bytes();
let out = bcrypt_pbkdf(pwd, salt, 8, 88).unwrap();
let expected = from_hex(
"8df43fc6fe131fc47f0c9e39224bd94c70b6fcc8ee8135faddf61156e6cb2733\
ea765f315a3e1e4afc35bf8687d189254c1e05a6fe80c0617f9183d67260d6a1\
15c6c94e3603e2303fbb43a76a64523ffda686b1d4518543",
);
assert_eq!(out.len(), 88);
assert_eq!(out, expected);
}
#[test]
fn rejects_zero_rounds() {
assert_eq!(
bcrypt_pbkdf(b"x", b"y", 0, 32),
Err(Error::InvalidParameters)
);
}
#[test]
fn rejects_zero_keylen() {
assert_eq!(
bcrypt_pbkdf(b"x", b"y", 1, 0),
Err(Error::InvalidParameters)
);
}
#[test]
fn rejects_too_large_keylen() {
assert_eq!(
bcrypt_pbkdf(b"x", b"y", 1, 1025),
Err(Error::InvalidParameters)
);
}
#[test]
fn accepts_max_keylen() {
let out = bcrypt_pbkdf(b"password", b"salt", 1, 1024).unwrap();
assert_eq!(out.len(), 1024);
}
}