Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion Cargo.lock

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

16 changes: 15 additions & 1 deletion pkcs12/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pkcs12"
version = "0.0.0"
version = "0.1.0"
description = """
Pure Rust implementation of Public-Key Cryptography Standards (PKCS) #12:
Personal Information Exchange Syntax v1.1 (RFC7292)
Expand All @@ -14,6 +14,20 @@ readme = "README.md"
edition = "2021"
rust-version = "1.65"

[features]
alloc = []

[dependencies]
cfg-if = "1.0.0"
digest = { version = "0.10.7", features=["alloc"] }
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"]

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

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

/// 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.
pub fn str_to_unicode(utf8_str: &str) -> Vec<u8> {
let mut utf16_bytes = Vec::new();
// reserve max number of required bytes to avoid re-allocation
utf16_bytes.reserve(utf8_str.len() * 2 + 2);
for code_point in utf8_str.encode_utf16() {
utf16_bytes.extend(code_point.to_be_bytes());
}
utf16_bytes.push(0);
utf16_bytes.push(0);
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
/// ```rust
/// let key = pkcs12::kdf::derive_key::<sha2::Sha256>("top-secret", &[0x1, 0x2, 0x3, 0x4],
/// pkcs12::kdf::Pkcs12KeyType::EncryptionKey, 1000, 32);
/// ```
pub fn derive_key<D>(
pass: &str,
salt: &[u8],
id: Pkcs12KeyType,
rounds: i32,
key_len: usize,
) -> Vec<u8>
where
D: Digest + FixedOutputReset + BlockSizeUser,
{
let mut digest = D::new();
let mut pass_utf16 = str_to_unicode(pass);
let output_size = <D as OutputSizeUser>::output_size();
let block_size = D::block_size();
let slen = block_size * ((salt.len() + block_size - 1) / block_size);
let plen = block_size * ((pass_utf16.len() + block_size - 1) / block_size);
let ilen = slen + plen;
let mut init_key = vec![0u8; ilen];
for i in 0..slen {
init_key[i] = salt[i % salt.len()];
}
for i in 0..plen {
init_key[slen + i] = pass_utf16[i % pass_utf16.len()];
}
pass_utf16.zeroize();
Copy link
Member

@baloo baloo Jul 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like step2,3 & 4

   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.

   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.
   
   4.  Set I=S||P to be the concatenation of S and P.

Could you move code around (to preserve ordering) and copy the algorithm from the RFC as comments?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added comments to the code describing the single steps of the implementation, as far as possible since I derive at some point slightly from the algorithm.


let id_block = match id {
Pkcs12KeyType::EncryptionKey => vec![1u8; block_size],
Pkcs12KeyType::Iv => vec![2u8; block_size],
Pkcs12KeyType::Mac => vec![3u8; block_size],
};

let mut m = key_len;
let mut n = 0;
let mut out = vec![0u8; key_len];
loop {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RFC calls for a c = ceiling(n/u) and for the loop to be:

for _i in 1..=c {
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a loop instead has the advantage that the last step 6. could be skipped.

<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();
}
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;
}

// prepare `init_key` for next block if `ouput_size` is smaller than `key_len`
m -= new_bytes_num;
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();
out
}
14 changes: 13 additions & 1 deletion pkcs12/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,16 @@
unused_qualifications
)]

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

use cfg_if::cfg_if;

cfg_if! {
if #[cfg(feature = "alloc")] {

#[macro_use]
extern crate alloc;

pub mod kdf;
}
}
130 changes: 130 additions & 0 deletions pkcs12/tests/kdf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
use cfg_if::cfg_if;

cfg_if! {
if #[cfg(feature="alloc")] {

#[test]
fn pkcs12_key_derive_sha256() {
use hex_literal::hex;
use pkcs12::kdf::{derive_key, Pkcs12KeyType};

const PASS_SHORT: &str = "ge@äheim";
const SALT_INC: [u8; 8] = [0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8];

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

#[test]
fn pkcs12_key_derive_sha512() {
use hex_literal::hex;
use pkcs12::kdf::{derive_key, Pkcs12KeyType};

const PASS_SHORT: &str = "ge@äheim";
const SALT_INC: [u8; 8] = [0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8];

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

#[test]
fn pkcs12_key_derive_whirlpool() {
use hex_literal::hex;
use pkcs12::kdf::{derive_key, Pkcs12KeyType};

const PASS_SHORT: &str = "ge@äheim";
const SALT_INC: [u8; 8] = [0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8];

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