-
Notifications
You must be signed in to change notification settings - Fork 152
pkcs12: KDF support #1154
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
pkcs12: KDF support #1154
Changes from all commits
6dc0d09
e611e76
e14c270
6125b80
5286894
217783b
02a8193
236b957
c6b4928
1183d10
7e11f44
87cc22a
3dd8ece
282baa5
7537728
ebe05c2
8622bfd
30960c8
b04da8c
f669db8
23c364a
bf455d3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The RFC talks about There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, I guess we should really add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm a bit uncomfortable with the use of I don't know what the best course of action is here though, the best I can think of would be to add a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (sorry last comment was sent before I refreshed the page, well, go for a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The format of an ASN1 Openssl provides similar functions for However, I am not sure how to interpret the remark "There are no Unicode byte order marks." in the RFC. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. last commit does not really fix the issue with unicode characters that do not encode on two bytes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A BOM would precede the string, and if it had one in this case would be
We can worry about that all later though. It isn't needed for an initial PR. |
||
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. RFC calls for a
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using a |
||
// 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 | ||
} |
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") | ||
); | ||
} |
Uh oh!
There was an error while loading. Please reload this page.