Rust wrapper: add cipher trait support

This commit is contained in:
Josh Holtrop
2026-03-13 15:12:48 -04:00
parent 873bc05cde
commit 23cb7ae30c
5 changed files with 1371 additions and 2 deletions
+39 -1
View File
@@ -9,7 +9,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
dependencies = [
"blobby",
"crypto-common",
"crypto-common 0.1.7",
"generic-array",
]
@@ -69,6 +69,16 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
[[package]]
name = "cipher"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e34d8227fe1ba289043aeb13792056ff80fd6de1a9f49137a5f499de8e8c78ea"
dependencies = [
"crypto-common 0.2.1",
"inout",
]
[[package]]
name = "clang-sys"
version = "1.8.1"
@@ -91,6 +101,15 @@ dependencies = [
"typenum",
]
[[package]]
name = "crypto-common"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710"
dependencies = [
"hybrid-array",
]
[[package]]
name = "either"
version = "1.15.0"
@@ -113,6 +132,24 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
[[package]]
name = "hybrid-array"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8655f91cd07f2b9d0c24137bd650fe69617773435ee5ec83022377777ce65ef1"
dependencies = [
"typenum",
]
[[package]]
name = "inout"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7"
dependencies = [
"hybrid-array",
]
[[package]]
name = "itertools"
version = "0.13.0"
@@ -353,6 +390,7 @@ version = "1.2.0"
dependencies = [
"aead",
"bindgen",
"cipher",
"rand_core 0.10.0",
"regex",
]
@@ -14,13 +14,16 @@ readme = "README.md"
std = []
rand_core = ["dep:rand_core"]
aead = ["dep:aead"]
cipher = ["dep:cipher"]
[dependencies]
rand_core = { version = "0.10", optional = true, default-features = false }
aead = { version = "0.5", optional = true, default-features = false }
cipher = { version = "0.5", optional = true, default-features = false }
[dev-dependencies]
aead = { version = "0.5", features = ["alloc", "dev"] }
cipher = "0.5"
[build-dependencies]
bindgen = "0.72.1"
+1 -1
View File
@@ -1,4 +1,4 @@
FEATURES := rand_core,aead
FEATURES := rand_core,aead,cipher
CARGO_FEATURE_FLAGS := --features $(FEATURES)
.PHONY: all
File diff suppressed because it is too large Load Diff
@@ -1114,3 +1114,312 @@ fn test_aes256ccm_aead_roundtrip() {
assert_eq!(recovered, plaintext);
}
// ---------------------------------------------------------------------------
// AES cipher crate trait tests
// ---------------------------------------------------------------------------
/// Test AES-128-ECB encryption against the known test vector used in the
/// existing `test_ecb_encrypt_decrypt` test.
#[test]
#[cfg(all(feature = "cipher", aes_ecb))]
fn test_aes128_ecb_enc_block_encrypt() {
use cipher::{BlockModeEncrypt, KeyInit};
use wolfssl_wolfcrypt::aes::Aes128EcbEnc;
let key: [u8; 16] = *b"0123456789abcdef";
let plaintext: [u8; 16] = [
0x6e, 0x6f, 0x77, 0x20, 0x69, 0x73, 0x20, 0x74,
0x68, 0x65, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x20,
];
let expected: [u8; 16] = [
0xd0, 0xc9, 0xd9, 0xc9, 0x40, 0xe8, 0x97, 0xb6,
0xc8, 0x8c, 0x33, 0x3b, 0xb5, 0x8f, 0x85, 0xd1,
];
let mut enc = Aes128EcbEnc::new_from_slice(&key).expect("key init failed");
let mut block = cipher::Block::<Aes128EcbEnc>::try_from(&plaintext[..]).unwrap();
enc.encrypt_block(&mut block);
assert_eq!(block.as_slice(), &expected);
}
/// Test AES-128-ECB decryption matches the plaintext after encryption.
#[test]
#[cfg(all(feature = "cipher", aes_ecb))]
fn test_aes128_ecb_dec_block_decrypt() {
use cipher::{BlockModeDecrypt, BlockModeEncrypt, KeyInit};
use wolfssl_wolfcrypt::aes::{Aes128EcbDec, Aes128EcbEnc};
let key: [u8; 16] = *b"0123456789abcdef";
let plaintext: [u8; 16] = [
0x6e, 0x6f, 0x77, 0x20, 0x69, 0x73, 0x20, 0x74,
0x68, 0x65, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x20,
];
let mut enc = Aes128EcbEnc::new_from_slice(&key).expect("enc init failed");
let mut dec = Aes128EcbDec::new_from_slice(&key).expect("dec init failed");
let mut block = cipher::Block::<Aes128EcbEnc>::try_from(&plaintext[..]).unwrap();
enc.encrypt_block(&mut block);
let mut block2 = cipher::Block::<Aes128EcbDec>::try_from(block.as_slice()).unwrap();
dec.decrypt_block(&mut block2);
assert_eq!(block2.as_slice(), &plaintext);
}
/// Test AES-256-ECB encryption and decryption roundtrip.
#[test]
#[cfg(all(feature = "cipher", aes_ecb))]
fn test_aes256_ecb_roundtrip() {
use cipher::{BlockModeDecrypt, BlockModeEncrypt, KeyInit};
use wolfssl_wolfcrypt::aes::{Aes256EcbDec, Aes256EcbEnc};
let key = [0xabu8; 32];
let plaintext = [0x5cu8; 16];
let mut enc = Aes256EcbEnc::new_from_slice(&key).expect("enc init failed");
let mut dec = Aes256EcbDec::new_from_slice(&key).expect("dec init failed");
let mut block = cipher::Block::<Aes256EcbEnc>::try_from(&plaintext[..]).unwrap();
enc.encrypt_block(&mut block);
assert_ne!(block.as_slice(), &plaintext, "encrypted block should differ from plaintext");
let mut block2 = cipher::Block::<Aes256EcbDec>::try_from(block.as_slice()).unwrap();
dec.decrypt_block(&mut block2);
assert_eq!(block2.as_slice(), &plaintext);
}
/// Test AES-128-CTR `apply_keystream` against the NIST CTR test vector.
#[test]
#[cfg(all(feature = "cipher", aes_ctr))]
fn test_aes128_ctr_apply_keystream() {
use cipher::{KeyIvInit, StreamCipher};
use wolfssl_wolfcrypt::aes::Aes128Ctr;
let key: [u8; 16] = [
0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c,
];
let iv: [u8; 16] = [
0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
];
let plaintext: [u8; 64] = [
0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96,
0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a,
0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c,
0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51,
0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11,
0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef,
0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17,
0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10,
];
let expected_ciphertext: [u8; 64] = [
0x87, 0x4d, 0x61, 0x91, 0xb6, 0x20, 0xe3, 0x26,
0x1b, 0xef, 0x68, 0x64, 0x99, 0x0d, 0xb6, 0xce,
0x98, 0x06, 0xf6, 0x6b, 0x79, 0x70, 0xfd, 0xff,
0x86, 0x17, 0x18, 0x7b, 0xb9, 0xff, 0xfd, 0xff,
0x5a, 0xe4, 0xdf, 0x3e, 0xdb, 0xd5, 0xd3, 0x5e,
0x5b, 0x4f, 0x09, 0x02, 0x0d, 0xb0, 0x3e, 0xab,
0x1e, 0x03, 0x1d, 0xda, 0x2f, 0xbe, 0x03, 0xd1,
0x79, 0x21, 0x70, 0xa0, 0xf3, 0x00, 0x9c, 0xee,
];
let key_arr = cipher::Key::<Aes128Ctr>::try_from(&key[..]).unwrap();
let iv_arr = cipher::Iv::<Aes128Ctr>::try_from(&iv[..]).unwrap();
let mut enc = Aes128Ctr::new(&key_arr, &iv_arr);
let mut data = plaintext;
enc.apply_keystream(&mut data);
assert_eq!(data, expected_ciphertext);
// apply_keystream is self-inverse: applying again must recover plaintext.
let mut dec = Aes128Ctr::new(&key_arr, &iv_arr);
dec.apply_keystream(&mut data);
assert_eq!(data, plaintext);
}
/// Test AES-256-CTR roundtrip via `apply_keystream`.
#[test]
#[cfg(all(feature = "cipher", aes_ctr))]
fn test_aes256_ctr_roundtrip() {
use cipher::{KeyIvInit, StreamCipher};
use wolfssl_wolfcrypt::aes::Aes256Ctr;
let key = [0x01u8; 32];
let iv = [0x02u8; 16];
let plaintext = [0x55u8; 48];
let key_arr = cipher::Key::<Aes256Ctr>::try_from(&key[..]).unwrap();
let iv_arr = cipher::Iv::<Aes256Ctr>::try_from(&iv[..]).unwrap();
let mut enc = Aes256Ctr::new(&key_arr, &iv_arr);
let mut data = plaintext;
enc.apply_keystream(&mut data);
assert_ne!(data, plaintext);
let mut dec = Aes256Ctr::new(&key_arr, &iv_arr);
dec.apply_keystream(&mut data);
assert_eq!(data, plaintext);
}
/// Test AES-256-OFB `apply_keystream` against the known OFB test vector.
#[test]
#[cfg(all(feature = "cipher", aes_ofb))]
fn test_aes256_ofb_apply_keystream() {
use cipher::{KeyIvInit, StreamCipher};
use wolfssl_wolfcrypt::aes::Aes256Ofb;
let key: [u8; 32] = [
0xc4, 0xc7, 0xfa, 0xd6, 0x53, 0x5c, 0xb8, 0x71,
0x4a, 0x5c, 0x40, 0x77, 0x9a, 0x8b, 0xa1, 0xd2,
0x53, 0x3e, 0x23, 0xb4, 0xb2, 0x58, 0x73, 0x2a,
0x5b, 0x78, 0x01, 0xf4, 0xe3, 0x71, 0xa7, 0x94,
];
let iv: [u8; 16] = [
0x5e, 0xb9, 0x33, 0x13, 0xb8, 0x71, 0xff, 0x16,
0xb9, 0x8a, 0x9b, 0xcb, 0x43, 0x33, 0x0d, 0x6f,
];
let plaintext: [u8; 48] = [
0x6d, 0x0b, 0xb0, 0x79, 0x63, 0x84, 0x71, 0xe9,
0x39, 0xd4, 0x53, 0x14, 0x86, 0xc1, 0x4c, 0x25,
0x9a, 0xee, 0xc6, 0xf3, 0xc0, 0x0d, 0xfd, 0xd6,
0xc0, 0x50, 0xa8, 0xba, 0xa8, 0x20, 0xdb, 0x71,
0xcc, 0x12, 0x2c, 0x4e, 0x0c, 0x17, 0x15, 0xef,
0x55, 0xf3, 0x99, 0x5a, 0x6b, 0xf0, 0x2a, 0x4c,
];
let expected_ciphertext: [u8; 48] = [
0x0f, 0x54, 0x61, 0x71, 0x59, 0xd0, 0x3f, 0xfc,
0x1b, 0xfa, 0xfb, 0x60, 0x29, 0x30, 0xd7, 0x00,
0xf4, 0xa4, 0xa8, 0xe6, 0xdd, 0x93, 0x94, 0x46,
0x64, 0xd2, 0x19, 0xc4, 0xc5, 0x4d, 0xde, 0x1b,
0x04, 0x53, 0xe1, 0x73, 0xf5, 0x18, 0x74, 0xae,
0xfd, 0x64, 0xa2, 0xe1, 0xe2, 0x76, 0x13, 0xb0,
];
let key_arr = cipher::Key::<Aes256Ofb>::try_from(&key[..]).unwrap();
let iv_arr = cipher::Iv::<Aes256Ofb>::try_from(&iv[..]).unwrap();
let mut enc = Aes256Ofb::new(&key_arr, &iv_arr);
let mut data = plaintext;
enc.apply_keystream(&mut data);
assert_eq!(data, expected_ciphertext);
// apply_keystream is self-inverse for OFB (same keystream for enc/dec).
let mut dec = Aes256Ofb::new(&key_arr, &iv_arr);
dec.apply_keystream(&mut data);
assert_eq!(data, plaintext);
}
/// Test AES-128-OFB roundtrip via `apply_keystream`.
#[test]
#[cfg(all(feature = "cipher", aes_ofb))]
fn test_aes128_ofb_roundtrip() {
use cipher::{KeyIvInit, StreamCipher};
use wolfssl_wolfcrypt::aes::Aes128Ofb;
let key = [0xddu8; 16];
let iv = [0xeeu8; 16];
let plaintext = [0x42u8; 32];
let key_arr = cipher::Key::<Aes128Ofb>::try_from(&key[..]).unwrap();
let iv_arr = cipher::Iv::<Aes128Ofb>::try_from(&iv[..]).unwrap();
let mut enc = Aes128Ofb::new(&key_arr, &iv_arr);
let mut data = plaintext;
enc.apply_keystream(&mut data);
assert_ne!(data, plaintext);
let mut dec = Aes128Ofb::new(&key_arr, &iv_arr);
dec.apply_keystream(&mut data);
assert_eq!(data, plaintext);
}
/// Test AES-128-CBC encryption against a known vector (same as test_cbc_encrypt_decrypt).
#[test]
#[cfg(all(feature = "cipher", aes_cbc))]
fn test_aes128_cbc_enc_block_mode() {
use cipher::{BlockModeEncrypt, KeyIvInit};
use wolfssl_wolfcrypt::aes::Aes128CbcEnc;
let key: [u8; 16] = *b"0123456789abcdef";
let iv: [u8; 16] = *b"1234567890abcdef";
let plaintext: [u8; 16] = [
0x6e, 0x6f, 0x77, 0x20, 0x69, 0x73, 0x20, 0x74,
0x68, 0x65, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x20,
];
let expected: [u8; 16] = [
0x95, 0x94, 0x92, 0x57, 0x5f, 0x42, 0x81, 0x53,
0x2c, 0xcc, 0x9d, 0x46, 0x77, 0xa2, 0x33, 0xcb,
];
let key_arr = cipher::Key::<Aes128CbcEnc>::try_from(&key[..]).unwrap();
let iv_arr = cipher::Iv::<Aes128CbcEnc>::try_from(&iv[..]).unwrap();
let mut enc = Aes128CbcEnc::new(&key_arr, &iv_arr);
let mut block = cipher::Block::<Aes128CbcEnc>::try_from(&plaintext[..]).unwrap();
enc.encrypt_block(&mut block);
assert_eq!(block.as_slice(), &expected);
}
/// Test AES-128-CBC decryption roundtrip.
#[test]
#[cfg(all(feature = "cipher", aes_cbc))]
fn test_aes128_cbc_dec_block_mode() {
use cipher::{BlockModeDecrypt, BlockModeEncrypt, KeyIvInit};
use wolfssl_wolfcrypt::aes::{Aes128CbcDec, Aes128CbcEnc};
let key: [u8; 16] = *b"0123456789abcdef";
let iv: [u8; 16] = *b"1234567890abcdef";
let plaintext: [u8; 16] = [
0x6e, 0x6f, 0x77, 0x20, 0x69, 0x73, 0x20, 0x74,
0x68, 0x65, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x20,
];
let key_arr = cipher::Key::<Aes128CbcEnc>::try_from(&key[..]).unwrap();
let iv_arr = cipher::Iv::<Aes128CbcEnc>::try_from(&iv[..]).unwrap();
let mut enc = Aes128CbcEnc::new(&key_arr, &iv_arr);
let mut block = cipher::Block::<Aes128CbcEnc>::try_from(&plaintext[..]).unwrap();
enc.encrypt_block(&mut block);
let key_arr = cipher::Key::<Aes128CbcDec>::try_from(&key[..]).unwrap();
let iv_arr = cipher::Iv::<Aes128CbcDec>::try_from(&iv[..]).unwrap();
let mut dec = Aes128CbcDec::new(&key_arr, &iv_arr);
dec.decrypt_block(&mut block);
assert_eq!(block.as_slice(), &plaintext);
}
/// Test AES-256-CBC encryption/decryption roundtrip across multiple blocks.
#[test]
#[cfg(all(feature = "cipher", aes_cbc))]
fn test_aes256_cbc_roundtrip() {
use cipher::{BlockModeDecrypt, BlockModeEncrypt, KeyIvInit};
use wolfssl_wolfcrypt::aes::{Aes256CbcDec, Aes256CbcEnc};
let key = [0xabu8; 32];
let iv = [0xcdu8; 16];
let plaintext = [[0x5cu8; 16], [0x3au8; 16], [0x1eu8; 16]];
let key_arr = cipher::Key::<Aes256CbcEnc>::try_from(&key[..]).unwrap();
let iv_arr = cipher::Iv::<Aes256CbcEnc>::try_from(&iv[..]).unwrap();
let mut enc = Aes256CbcEnc::new(&key_arr, &iv_arr);
let mut blocks: [cipher::Block<Aes256CbcEnc>; 3] = plaintext
.iter()
.map(|b| cipher::Block::<Aes256CbcEnc>::try_from(b.as_ref()).unwrap())
.collect::<Vec<_>>()
.try_into()
.unwrap();
for block in blocks.iter_mut() {
enc.encrypt_block(block);
}
// Ciphertext must differ from plaintext due to key and IV mixing.
assert!(blocks.iter().zip(plaintext.iter()).any(|(c, p)| c.as_slice() != p));
let key_arr = cipher::Key::<Aes256CbcDec>::try_from(&key[..]).unwrap();
let iv_arr = cipher::Iv::<Aes256CbcDec>::try_from(&iv[..]).unwrap();
let mut dec = Aes256CbcDec::new(&key_arr, &iv_arr);
for block in blocks.iter_mut() {
dec.decrypt_block(block);
}
for (block, expected) in blocks.iter().zip(plaintext.iter()) {
assert_eq!(block.as_slice(), expected);
}
}