Skip to content

Commit

Permalink
pkcs12: KDF support (RustCrypto#1154)
Browse files Browse the repository at this point in the history
Implementation of the KDF in RFC7292 Appendix B
  • Loading branch information
xemwebe authored Jul 23, 2023
1 parent 53db3d3 commit ad234de
Show file tree
Hide file tree
Showing 5 changed files with 365 additions and 1 deletion.
17 changes: 17 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions pkcs12/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,20 @@ readme = "README.md"
edition = "2021"
rust-version = "1.65"

[dependencies]
cfg-if = "1.0.0"
digest = { version = "0.10.7", features=["alloc"], optional = true }
zeroize = "1.6.0"

[dev-dependencies]
hex-literal = "0.4"
sha2 = "0.10.7"
whirlpool = "0.10.4"

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[features]
kdf = ["dep:digest"]

164 changes: 164 additions & 0 deletions pkcs12/src/kdf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
//! Implementation of the key derivation function
//! [RFC 7292 Appendix B](https://datatracker.ietf.org/doc/html/rfc7292#appendix-B)

use alloc::{vec, vec::Vec};
use digest::{core_api::BlockSizeUser, Digest, FixedOutputReset, OutputSizeUser, Update};
use zeroize::{Zeroize, Zeroizing};

/// Transform a utf-8 string in a unicode (utf16) string as binary array.
/// The Utf16 code points are stored in big endian format with two trailing zero bytes.
fn str_to_unicode(utf8_str: &str) -> Vec<u8> {
let mut utf16_bytes = Vec::with_capacity(utf8_str.len() * 2 + 2);
for code_point in utf8_str.encode_utf16().chain(Some(0)) {
utf16_bytes.extend(code_point.to_be_bytes());
}
utf16_bytes
}

/// Specify the usage type of the generated key
/// This allows to derive distinct encryption keys, IVs and MAC from the same password or text
/// string.
pub enum Pkcs12KeyType {
/// Use key for encryption
EncryptionKey = 1,
/// Use key as initial vector
Iv = 2,
/// Use key as MAC
Mac = 3,
}

/// Derives `key` of type `id` from `pass` and `salt` with length `key_len` using `rounds`
/// iterations of the algorithm
/// `pass` must be a utf8 string.
/// ```rust
/// let key = pkcs12::kdf::derive_key_utf8::<sha2::Sha256>("top-secret", &[0x1, 0x2, 0x3, 0x4],
/// pkcs12::kdf::Pkcs12KeyType::EncryptionKey, 1000, 32);
/// ```
pub fn derive_key_utf8<D>(
pass: &str,
salt: &[u8],
id: Pkcs12KeyType,
rounds: i32,
key_len: usize,
) -> Vec<u8>
where
D: Digest + FixedOutputReset + BlockSizeUser,
{
let pass_utf16 = Zeroizing::new(str_to_unicode(pass));
derive_key::<D>(&pass_utf16, salt, id, rounds, key_len)
}

/// Derives `key` of type `id` from `pass` and `salt` with length `key_len` using `rounds`
/// iterations of the algorithm
/// `pass` must be a unicode (utf16) byte array in big endian order without order mark and with two
/// terminating zero bytes.
/// ```rust
/// let key = pkcs12::kdf::derive_key_utf8::<sha2::Sha256>("top-secret", &[0x1, 0x2, 0x3, 0x4],
/// pkcs12::kdf::Pkcs12KeyType::EncryptionKey, 1000, 32);
/// ```
pub fn derive_key<D>(
pass: &[u8],
salt: &[u8],
id: Pkcs12KeyType,
rounds: i32,
key_len: usize,
) -> Vec<u8>
where
D: Digest + FixedOutputReset + BlockSizeUser,
{
let mut digest = D::new();
let output_size = <D as OutputSizeUser>::output_size();
let block_size = D::block_size();

// In the following, the numbered comments relate directly to the algorithm
// described in RFC 7292, Appendix B.2. Actual variable names may differ.
// Comments of the RFC are in enclosed in []
//
// 1. Construct a string, D (the "diversifier"), by concatenating v/8
// copies of ID, where v is the block size in bits.
let id_block = match id {
Pkcs12KeyType::EncryptionKey => vec![1u8; block_size],
Pkcs12KeyType::Iv => vec![2u8; block_size],
Pkcs12KeyType::Mac => vec![3u8; block_size],
};

let slen = block_size * ((salt.len() + block_size - 1) / block_size);
let plen = block_size * ((pass.len() + block_size - 1) / block_size);
let ilen = slen + plen;
let mut init_key = vec![0u8; ilen];
// 2. Concatenate copies of the salt together to create a string S of
// length v(ceiling(s/v)) bits (the final copy of the salt may be
// truncated to create S). Note that if the salt is the empty
// string, then so is S.
for i in 0..slen {
init_key[i] = salt[i % salt.len()];
}

// 3. Concatenate copies of the password together to create a string P
// of length v(ceiling(p/v)) bits (the final copy of the password
// may be truncated to create P). Note that if the password is the
// empty string, then so is P.
for i in 0..plen {
init_key[slen + i] = pass[i % pass.len()];
}

// 4. Set I=S||P to be the concatenation of S and P.
// [already done in `init_key`]

let mut m = key_len;
let mut n = 0;
let mut out = vec![0u8; key_len];
// 5. Set c=ceiling(n/u)
// 6. For i=1, 2, ..., c, do the following:
// [ Instead of following this approach, we use an infinite loop and
// use the break condition below, if we have produced n bytes for the key]
loop {
// 6. A. Set A2=H^r(D||I). (i.e., the r-th hash of D||1,
// H(H(H(... H(D||I))))
<D as Update>::update(&mut digest, &id_block);
<D as Update>::update(&mut digest, &init_key);
let mut result = digest.finalize_fixed_reset();
for _ in 1..rounds {
<D as Update>::update(&mut digest, &result[0..output_size]);
result = digest.finalize_fixed_reset();
}

// 7. Concateate A_1, A_2, ..., A_c together to form a pseudorandom
// bit string, A.
// [ Instead of storing all Ais and concatenating later, we concatenate
// them immediately ]
let new_bytes_num = m.min(output_size);
out[n..n + new_bytes_num].copy_from_slice(&result[0..new_bytes_num]);
n += new_bytes_num;
if m <= new_bytes_num {
break;
}
m -= new_bytes_num;

// 6. B. Concatenate copies of Ai to create a string B of length v
// bits (the final copy of Ai may be truncated to create B).
// [ we achieve this on thy fly with the expression `result[k % output_size]` below]

// 6. C. Treating I as a concatenation I_0, I_1, ..., I_(k-1) of v-bit
// blocks, where k=ceiling(s/v)+ceiling(p/v), modify I by
// setting I_j=(I_j+B+1) mod 2^v for each j.
let mut j = 0;
while j < ilen {
let mut c = 1_u16;
let mut k = block_size - 1;
loop {
c += init_key[k + j] as u16 + result[k % output_size] as u16;
init_key[j + k] = (c & 0x00ff) as u8;
c >>= 8;
if k == 0 {
break;
}
k -= 1;
}
j += block_size;
}
}
init_key.zeroize();
// 8. Use the first n bits of A as the output of this entire process.
out
}
8 changes: 7 additions & 1 deletion pkcs12/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,10 @@
unused_qualifications
)]

//! TODO: PKCS#12 crate
//! TODO: complete PKCS#12 crate

#[cfg(feature = "kdf")]
extern crate alloc;

#[cfg(feature = "kdf")]
pub mod kdf;
163 changes: 163 additions & 0 deletions pkcs12/tests/kdf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
#![cfg(feature = "kdf")]
/// Test cases for the key derivation functions.
/// All test cases have been verified against openssl's method `PKCS12_key_gen_utf8`.
/// See https://github.com/xemwebe/test_pkcs12_kdf for a sample program.
///
use hex_literal::hex;
use pkcs12::kdf::{derive_key_utf8, Pkcs12KeyType};

#[test]
fn pkcs12_key_derive_sha256() {
const PASS_SHORT: &str = "ge@äheim";
const SALT_INC: [u8; 8] = [0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8];

assert_eq!(
derive_key_utf8::<sha2::Sha256>(
PASS_SHORT,
&SALT_INC,
Pkcs12KeyType::EncryptionKey,
100,
32
),
hex!("fae4d4957a3cc781e1180b9d4fb79c1e0c8579b746a3177e5b0768a3118bf863")
);

assert_eq!(
derive_key_utf8::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::Iv, 100, 32),
hex!("e5ff813bc6547de5155b14d2fada85b3201a977349db6e26ccc998d9e8f83d6c")
);

assert_eq!(
derive_key_utf8::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::Mac, 100, 32),
hex!("136355ed9434516682534f46d63956db5ff06b844702c2c1f3b46321e2524a4d")
);

assert_eq!(
derive_key_utf8::<sha2::Sha256>(
PASS_SHORT,
&SALT_INC,
Pkcs12KeyType::EncryptionKey,
100,
20
),
hex!("fae4d4957a3cc781e1180b9d4fb79c1e0c8579b7")
);

assert_eq!(
derive_key_utf8::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::Iv, 100, 20),
hex!("e5ff813bc6547de5155b14d2fada85b3201a9773")
);

assert_eq!(
derive_key_utf8::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::Mac, 100, 20),
hex!("136355ed9434516682534f46d63956db5ff06b84")
);

assert_eq!(
derive_key_utf8::<sha2::Sha256>(
PASS_SHORT,
&SALT_INC,
Pkcs12KeyType::EncryptionKey,
100,
12
),
hex!("fae4d4957a3cc781e1180b9d")
);

assert_eq!(
derive_key_utf8::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::Iv, 100, 12),
hex!("e5ff813bc6547de5155b14d2")
);

assert_eq!(
derive_key_utf8::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::Mac, 100, 12),
hex!("136355ed9434516682534f46")
);

assert_eq!(
derive_key_utf8::<sha2::Sha256>(
PASS_SHORT,
&SALT_INC,
Pkcs12KeyType::EncryptionKey,
1000,
32
),
hex!("2b95a0569b63f641fae1efca32e84db3699ab74540628ba66283b58cf5400527")
);

assert_eq!(
derive_key_utf8::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::Iv, 1000, 32),
hex!("6472c0ebad3fab4123e8b5ed7834de21eeb20187b3eff78a7d1cdffa4034851d")
);

assert_eq!(
derive_key_utf8::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::Mac, 1000, 32),
hex!("3f9113f05c30a996c4a516409bdac9d065f44296ccd52bb75de3fcfdbe2bf130")
);

assert_eq!(
derive_key_utf8::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::Mac, 1000, 32),
hex!("3f9113f05c30a996c4a516409bdac9d065f44296ccd52bb75de3fcfdbe2bf130")
);

assert_eq!(
derive_key_utf8::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::EncryptionKey, 1000, 100),
hex!("2b95a0569b63f641fae1efca32e84db3699ab74540628ba66283b58cf5400527d8d0ebe2ccbf768c51c4d8fbd1bb156be06c1c59cbb69e44052ffc37376fdb47b2de7f9e543de9d096d8e5474b220410ff1c5d8bb7e5bc0f61baeaa12fd0da1d7a970172")
);

assert_eq!(
derive_key_utf8::<sha2::Sha256>(PASS_SHORT, &SALT_INC, Pkcs12KeyType::EncryptionKey, 1000, 200),
hex!("2b95a0569b63f641fae1efca32e84db3699ab74540628ba66283b58cf5400527d8d0ebe2ccbf768c51c4d8fbd1bb156be06c1c59cbb69e44052ffc37376fdb47b2de7f9e543de9d096d8e5474b220410ff1c5d8bb7e5bc0f61baeaa12fd0da1d7a9701729cea6014d7fe62a2ed926dc36b61307f119d64edbceb5a9c58133bbf75ba0bef000a1a5180e4b1de7d89c89528bcb7899a1e46fd4da0d9de8f8e65e8d0d775e33d1247e76d596a34303161b219f39afda448bf518a2835fc5e28f0b55a1b6137a2c70cf7")
);
}

#[test]
fn pkcs12_key_derive_sha512() {
const PASS_SHORT: &str = "ge@äheim";
const SALT_INC: [u8; 8] = [0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8];

assert_eq!(
derive_key_utf8::<sha2::Sha512>(
PASS_SHORT,
&SALT_INC,
Pkcs12KeyType::EncryptionKey,
100,
32
),
hex!("b14a9f01bfd9dce4c9d66d2fe9937e5fd9f1afa59e370a6fa4fc81c1cc8ec8ee")
);
}

#[test]
fn pkcs12_key_derive_whirlpool() {
const PASS_SHORT: &str = "ge@äheim";
const SALT_INC: [u8; 8] = [0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8];

assert_eq!(
derive_key_utf8::<whirlpool::Whirlpool>(
PASS_SHORT,
&SALT_INC,
Pkcs12KeyType::EncryptionKey,
100,
32
),
hex!("3324282adb468bff0734d3b7e399094ec8500cb5b0a3604055da107577aaf766")
);
}

#[test]
fn pkcs12_key_derive_special_chars() {
const PASS_SHORT: &str = "🔥";
const SALT_INC: [u8; 8] = [0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8];

assert_eq!(
derive_key_utf8::<sha2::Sha256>(
PASS_SHORT,
&SALT_INC,
Pkcs12KeyType::EncryptionKey,
100,
32
),
hex!("d01e72a940b4b1a7a5707fc8264a60cb7606ff9051dedff90930687d2513c006")
);
}

0 comments on commit ad234de

Please sign in to comment.