#![forbid(unsafe_code)]
pub mod argon2_kdf;
pub mod balloon;
pub mod hkdf_label;
pub mod kbkdf;
pub mod pbkdf2_kdf;
pub mod scrypt_kdf;
pub mod stretcher;
pub const PBKDF2_SHA256_MIN_ITERATIONS: u32 = 600_000;
pub const PBKDF2_SHA512_MIN_ITERATIONS: u32 = 210_000;
pub use argon2_kdf::{
argon2d_derive, argon2i_derive, argon2id_derive, argon2id_to_phc_string, argon2id_verify_phc,
Argon2Params, Argon2idHasher,
};
pub use balloon::{
balloon_sha256, balloon_sha256_secret, balloon_sha512, balloon_sha512_secret, BalloonHasher,
BalloonParams, BalloonVariant, BALLOON_DELTA,
};
pub use hkdf_label::{hkdf_expand_label_sha256, hkdf_expand_label_sha384};
pub use kbkdf::{
kbkdf_counter_hmac_sha256, kbkdf_counter_hmac_sha256_secret, kbkdf_counter_hmac_sha384,
kbkdf_counter_hmac_sha512,
};
pub use pbkdf2_kdf::{
pbkdf2_sha256, pbkdf2_sha512, Pbkdf2Params, Pbkdf2Sha256Hasher, Pbkdf2Sha512Hasher,
};
pub use scrypt_kdf::{scrypt_derive, ScryptHasher, ScryptParams};
pub use stretcher::{
Argon2idStretchParams, BalloonStretchParams, KeyStretcher, Pbkdf2StretchParams,
ScryptStretchParams, StretchParams, Stretcher,
};
use hkdf::Hkdf;
use oxicrypto_core::{CryptoError, Kdf, PasswordHash};
use subtle::ConstantTimeEq;
#[derive(Debug, Default, Clone, Copy)]
pub struct HkdfSha256;
impl Kdf for HkdfSha256 {
fn name(&self) -> &'static str {
"HKDF-SHA-256"
}
fn derive(
&self,
ikm: &[u8],
salt: &[u8],
info: &[u8],
okm_out: &mut [u8],
) -> Result<(), CryptoError> {
if okm_out.is_empty() {
return Err(CryptoError::BadInput);
}
let salt_opt = if salt.is_empty() { None } else { Some(salt) };
let hk = Hkdf::<sha2::Sha256>::new(salt_opt, ikm);
hk.expand(info, okm_out)
.map_err(|_| CryptoError::Internal("HKDF expand failed (output too long)"))?;
Ok(())
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct HkdfSha512;
impl Kdf for HkdfSha512 {
fn name(&self) -> &'static str {
"HKDF-SHA-512"
}
fn derive(
&self,
ikm: &[u8],
salt: &[u8],
info: &[u8],
okm_out: &mut [u8],
) -> Result<(), CryptoError> {
if okm_out.is_empty() {
return Err(CryptoError::BadInput);
}
let salt_opt = if salt.is_empty() { None } else { Some(salt) };
let hk = Hkdf::<sha2::Sha512>::new(salt_opt, ikm);
hk.expand(info, okm_out)
.map_err(|_| CryptoError::Internal("HKDF expand failed (output too long)"))?;
Ok(())
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct HkdfSha384;
impl Kdf for HkdfSha384 {
fn name(&self) -> &'static str {
"HKDF-SHA-384"
}
fn derive(
&self,
ikm: &[u8],
salt: &[u8],
info: &[u8],
okm_out: &mut [u8],
) -> Result<(), CryptoError> {
if okm_out.is_empty() {
return Err(CryptoError::BadInput);
}
let salt_opt = if salt.is_empty() { None } else { Some(salt) };
let hk = Hkdf::<sha2::Sha384>::new(salt_opt, ikm);
hk.expand(info, okm_out)
.map_err(|_| CryptoError::Internal("HKDF-SHA-384 expand failed (output too long)"))?;
Ok(())
}
}
#[must_use]
pub fn hkdf_sha256_extract(salt: &[u8], ikm: &[u8]) -> [u8; 32] {
let salt_opt = if salt.is_empty() { None } else { Some(salt) };
let (prk, _) = Hkdf::<sha2::Sha256>::extract(salt_opt, ikm);
let mut out = [0u8; 32];
out.copy_from_slice(&prk);
out
}
#[must_use = "HKDF expand result must be checked"]
pub fn hkdf_sha256_expand(prk: &[u8], info: &[u8], okm_out: &mut [u8]) -> Result<(), CryptoError> {
if okm_out.is_empty() {
return Err(CryptoError::BadInput);
}
let hk = Hkdf::<sha2::Sha256>::from_prk(prk).map_err(|_| CryptoError::InvalidKey)?;
hk.expand(info, okm_out)
.map_err(|_| CryptoError::Internal("HKDF-SHA-256 expand failed (output too long)"))?;
Ok(())
}
#[must_use]
pub fn hkdf_sha384_extract(salt: &[u8], ikm: &[u8]) -> [u8; 48] {
let salt_opt = if salt.is_empty() { None } else { Some(salt) };
let (prk, _) = Hkdf::<sha2::Sha384>::extract(salt_opt, ikm);
let mut out = [0u8; 48];
out.copy_from_slice(&prk);
out
}
#[must_use = "HKDF expand result must be checked"]
pub fn hkdf_sha384_expand(prk: &[u8], info: &[u8], okm_out: &mut [u8]) -> Result<(), CryptoError> {
if okm_out.is_empty() {
return Err(CryptoError::BadInput);
}
let hk = Hkdf::<sha2::Sha384>::from_prk(prk).map_err(|_| CryptoError::InvalidKey)?;
hk.expand(info, okm_out)
.map_err(|_| CryptoError::Internal("HKDF-SHA-384 expand failed (output too long)"))?;
Ok(())
}
#[must_use]
pub fn hkdf_sha512_extract(salt: &[u8], ikm: &[u8]) -> [u8; 64] {
let salt_opt = if salt.is_empty() { None } else { Some(salt) };
let (prk, _) = Hkdf::<sha2::Sha512>::extract(salt_opt, ikm);
let mut out = [0u8; 64];
out.copy_from_slice(&prk);
out
}
#[must_use = "HKDF expand result must be checked"]
pub fn hkdf_sha512_expand(prk: &[u8], info: &[u8], okm_out: &mut [u8]) -> Result<(), CryptoError> {
if okm_out.is_empty() {
return Err(CryptoError::BadInput);
}
let hk = Hkdf::<sha2::Sha512>::from_prk(prk).map_err(|_| CryptoError::InvalidKey)?;
hk.expand(info, okm_out)
.map_err(|_| CryptoError::Internal("HKDF-SHA-512 expand failed (output too long)"))?;
Ok(())
}
#[must_use = "HKDF derive result must be checked"]
pub fn hkdf_sha256_derive_to_vec(
ikm: &[u8],
salt: &[u8],
info: &[u8],
len: usize,
) -> Result<Vec<u8>, CryptoError> {
if len == 0 {
return Err(CryptoError::BadInput);
}
let mut out = vec![0u8; len];
HkdfSha256.derive(ikm, salt, info, &mut out)?;
Ok(out)
}
#[must_use = "HKDF derive result must be checked"]
pub fn hkdf_sha384_derive_to_vec(
ikm: &[u8],
salt: &[u8],
info: &[u8],
len: usize,
) -> Result<Vec<u8>, CryptoError> {
if len == 0 {
return Err(CryptoError::BadInput);
}
let mut out = vec![0u8; len];
HkdfSha384.derive(ikm, salt, info, &mut out)?;
Ok(out)
}
#[must_use = "HKDF derive result must be checked"]
pub fn hkdf_sha512_derive_to_vec(
ikm: &[u8],
salt: &[u8],
info: &[u8],
len: usize,
) -> Result<Vec<u8>, CryptoError> {
if len == 0 {
return Err(CryptoError::BadInput);
}
let mut out = vec![0u8; len];
HkdfSha512.derive(ikm, salt, info, &mut out)?;
Ok(out)
}
#[must_use = "generated salt result must be checked"]
pub fn generate_salt_16() -> Result<[u8; 16], CryptoError> {
let bytes = oxicrypto_rand::random_bytes(16)?;
let mut out = [0u8; 16];
out.copy_from_slice(&bytes);
Ok(out)
}
#[must_use = "generated salt result must be checked"]
pub fn generate_salt_32() -> Result<[u8; 32], CryptoError> {
let bytes = oxicrypto_rand::random_bytes(32)?;
let mut out = [0u8; 32];
out.copy_from_slice(&bytes);
Ok(out)
}
#[must_use = "password verification result must be checked"]
pub fn verify_password<H>(
hasher: &H,
password: &[u8],
salt: &[u8],
expected: &[u8],
) -> Result<(), CryptoError>
where
H: PasswordHash,
{
if expected.is_empty() {
return Err(CryptoError::BadInput);
}
let mut computed = vec![0u8; expected.len()];
struct NullParams;
impl oxicrypto_core::PasswordHashParams for NullParams {
fn memory_cost(&self) -> Option<u32> {
None
}
fn time_cost(&self) -> Option<u32> {
None
}
fn parallelism(&self) -> Option<u32> {
None
}
}
hasher.hash_password(password, salt, &NullParams, &mut computed)?;
let ok: bool = computed.ct_eq(expected).into();
if ok {
Ok(())
} else {
Err(CryptoError::InvalidTag)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn hex_decode(s: &str) -> Vec<u8> {
(0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
.collect()
}
#[test]
fn hkdf_sha256_rfc5869_tc1() {
let ikm = hex_decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
let salt = hex_decode("000102030405060708090a0b0c");
let info = hex_decode("f0f1f2f3f4f5f6f7f8f9");
let expected = hex_decode(
"3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865",
);
let kdf = HkdfSha256;
let mut okm = vec![0u8; 42];
kdf.derive(&ikm, &salt, &info, &mut okm)
.expect("HKDF-SHA-256 RFC5869 TC1 failed");
assert_eq!(okm, expected, "HKDF-SHA-256 RFC5869 TC1 mismatch");
}
#[test]
fn hkdf_sha256_empty_salt() {
let kdf = HkdfSha256;
let mut okm = [0u8; 32];
kdf.derive(b"input key material", b"", b"info", &mut okm)
.expect("HKDF with empty salt failed");
assert_ne!(okm, [0u8; 32]);
}
#[test]
fn hkdf_sha512_round_trip() {
let kdf = HkdfSha512;
let mut okm1 = [0u8; 64];
let mut okm2 = [0u8; 64];
kdf.derive(b"secret", b"salt", b"info", &mut okm1).unwrap();
kdf.derive(b"secret", b"salt", b"info", &mut okm2).unwrap();
assert_eq!(okm1, okm2, "HKDF-SHA-512 must be deterministic");
assert_ne!(okm1, [0u8; 64]);
}
#[test]
fn hkdf_empty_output_errors() {
let kdf = HkdfSha256;
let result = kdf.derive(b"ikm", b"salt", b"info", &mut []);
assert_eq!(result, Err(CryptoError::BadInput));
}
#[test]
fn hkdf_sha384_round_trip() {
let kdf = HkdfSha384;
let mut okm1 = [0u8; 48];
let mut okm2 = [0u8; 48];
kdf.derive(b"secret", b"salt", b"info", &mut okm1)
.expect("derive 1 failed");
kdf.derive(b"secret", b"salt", b"info", &mut okm2)
.expect("derive 2 failed");
assert_eq!(okm1, okm2, "HKDF-SHA-384 must be deterministic");
assert_ne!(okm1, [0u8; 48]);
}
#[test]
fn hkdf_sha384_empty_output_errors() {
let kdf = HkdfSha384;
let result = kdf.derive(b"ikm", b"salt", b"info", &mut []);
assert_eq!(result, Err(CryptoError::BadInput));
}
#[test]
fn hkdf_sha256_extract_expand_equivalent() {
let ikm = b"input key material";
let salt = b"salt value";
let info = b"info";
let kdf = HkdfSha256;
let mut okm_full = [0u8; 42];
kdf.derive(ikm, salt, info, &mut okm_full)
.expect("full derive failed");
let prk = hkdf_sha256_extract(salt, ikm);
let mut okm_sep = [0u8; 42];
hkdf_sha256_expand(&prk, info, &mut okm_sep).expect("expand failed");
assert_eq!(okm_full, okm_sep, "Extract+Expand must equal full derive");
}
#[test]
fn hkdf_sha384_extract_expand_round_trip() {
let prk = hkdf_sha384_extract(b"salt", b"ikm");
assert_eq!(prk.len(), 48);
let mut okm = [0u8; 32];
hkdf_sha384_expand(&prk, b"info", &mut okm).expect("expand failed");
assert_ne!(okm, [0u8; 32]);
}
#[test]
fn hkdf_sha512_extract_expand_round_trip() {
let prk = hkdf_sha512_extract(b"salt", b"ikm");
assert_eq!(prk.len(), 64);
let mut okm = [0u8; 64];
hkdf_sha512_expand(&prk, b"info", &mut okm).expect("expand failed");
assert_ne!(okm, [0u8; 64]);
}
#[test]
fn hkdf_expand_empty_output_errors() {
let prk = hkdf_sha256_extract(b"salt", b"ikm");
let result = hkdf_sha256_expand(&prk, b"info", &mut []);
assert_eq!(result, Err(CryptoError::BadInput));
}
const VERIFY_SALT: &[u8] = b"0123456789abcdef";
#[test]
fn verify_password_argon2id_correct() {
let hasher = Argon2idHasher::new(Argon2Params::TEST_PARAMS);
let mut expected = [0u8; 32];
hasher
.hash_password(b"password", VERIFY_SALT, &hasher.params, &mut expected)
.expect("hash");
verify_password(&hasher, b"password", VERIFY_SALT, &expected)
.expect("correct password must pass");
}
#[test]
fn verify_password_argon2id_wrong_password() {
let hasher = Argon2idHasher::new(Argon2Params::TEST_PARAMS);
let mut expected = [0u8; 32];
hasher
.hash_password(b"password", VERIFY_SALT, &hasher.params, &mut expected)
.expect("hash");
let result = verify_password(&hasher, b"wrongpassword", VERIFY_SALT, &expected);
assert_eq!(result, Err(CryptoError::InvalidTag));
}
#[test]
fn verify_password_pbkdf2_correct() {
let hasher = Pbkdf2Sha256Hasher::new(1_000);
let mut expected = [0u8; 32];
hasher
.hash_password(b"mypassword", VERIFY_SALT, &hasher.params(), &mut expected)
.expect("hash");
verify_password(&hasher, b"mypassword", VERIFY_SALT, &expected)
.expect("correct password must pass");
}
#[test]
fn verify_password_pbkdf2_wrong_password() {
let hasher = Pbkdf2Sha256Hasher::new(1_000);
let mut expected = [0u8; 32];
hasher
.hash_password(b"mypassword", VERIFY_SALT, &hasher.params(), &mut expected)
.expect("hash");
let result = verify_password(&hasher, b"notmypassword", VERIFY_SALT, &expected);
assert_eq!(result, Err(CryptoError::InvalidTag));
}
#[test]
fn verify_password_empty_expected_errors() {
let hasher = Pbkdf2Sha256Hasher::new(1_000);
let result = verify_password(&hasher, b"password", VERIFY_SALT, &[]);
assert_eq!(result, Err(CryptoError::BadInput));
}
}