use crabgraph::{
aead::{AesGcm128, AesGcm256, ChaCha20Poly1305, CrabAead},
encoding::{base64_decode, base64_encode, hex_decode, hex_encode},
hash::{sha256, sha512},
kdf::{hkdf_extract_expand, pbkdf2_derive_sha256, pbkdf2_derive_sha512},
kw::{Kw128, Kw192, Kw256},
mac::{hmac_sha256, hmac_sha256_verify, hmac_sha512},
CrabResult,
};
use proptest::prelude::*;
fn aead_roundtrip_property<C: CrabAead>(
cipher: &C,
plaintext: &[u8],
aad: Option<&[u8]>,
) -> CrabResult<()> {
let ciphertext = cipher.encrypt(plaintext, aad)?;
let decrypted = cipher.decrypt(&ciphertext, aad)?;
assert_eq!(decrypted, plaintext);
Ok(())
}
proptest! {
#[test]
fn prop_aes256gcm_roundtrip(plaintext in prop::collection::vec(any::<u8>(), 0..1024)) {
let key = [0x42u8; 32];
let cipher = AesGcm256::new(&key).unwrap();
aead_roundtrip_property(&cipher, &plaintext, None).unwrap();
}
#[test]
fn prop_aes256gcm_with_aad(
plaintext in prop::collection::vec(any::<u8>(), 0..512),
aad in prop::collection::vec(any::<u8>(), 0..256)
) {
let key = [0x42u8; 32];
let cipher = AesGcm256::new(&key).unwrap();
aead_roundtrip_property(&cipher, &plaintext, Some(&aad)).unwrap();
}
#[test]
fn prop_aes128gcm_roundtrip(plaintext in prop::collection::vec(any::<u8>(), 0..1024)) {
let key = [0x42u8; 16];
let cipher = AesGcm128::new(&key).unwrap();
aead_roundtrip_property(&cipher, &plaintext, None).unwrap();
}
#[test]
fn prop_chacha20poly1305_roundtrip(plaintext in prop::collection::vec(any::<u8>(), 0..1024)) {
let key = [0x42u8; 32];
let cipher = ChaCha20Poly1305::new(&key).unwrap();
aead_roundtrip_property(&cipher, &plaintext, None).unwrap();
}
#[test]
fn prop_chacha20poly1305_with_aad(
plaintext in prop::collection::vec(any::<u8>(), 0..512),
aad in prop::collection::vec(any::<u8>(), 0..256)
) {
let key = [0x42u8; 32];
let cipher = ChaCha20Poly1305::new(&key).unwrap();
aead_roundtrip_property(&cipher, &plaintext, Some(&aad)).unwrap();
}
#[test]
fn prop_aead_nonce_uniqueness(plaintext in prop::collection::vec(any::<u8>(), 1..256)) {
let key = [0x42u8; 32];
let cipher = AesGcm256::new(&key).unwrap();
let ct1 = cipher.encrypt(&plaintext, None).unwrap();
let ct2 = cipher.encrypt(&plaintext, None).unwrap();
prop_assert_ne!(ct1.to_bytes(), ct2.to_bytes());
let dec1 = cipher.decrypt(&ct1, None).unwrap();
let dec2 = cipher.decrypt(&ct2, None).unwrap();
prop_assert_eq!(dec1.as_slice(), plaintext.as_slice());
prop_assert_eq!(dec2.as_slice(), plaintext.as_slice());
}
#[test]
fn prop_aead_aad_integrity(
plaintext in prop::collection::vec(any::<u8>(), 1..256),
aad1 in prop::collection::vec(any::<u8>(), 1..128),
aad2 in prop::collection::vec(any::<u8>(), 1..128)
) {
prop_assume!(aad1 != aad2);
let key = [0x42u8; 32];
let cipher = AesGcm256::new(&key).unwrap();
let ciphertext = cipher.encrypt(&plaintext, Some(&aad1)).unwrap();
let result = cipher.decrypt(&ciphertext, Some(&aad2));
prop_assert!(result.is_err());
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(10))]
#[test]
fn prop_pbkdf2_deterministic(
password in prop::collection::vec(any::<u8>(), 1..32),
salt in prop::collection::vec(any::<u8>(), 8..16),
key_len in 16usize..32usize
) {
let iterations = 10000u32;
let key1 = pbkdf2_derive_sha256(&password, &salt, iterations, key_len).unwrap();
let key2 = pbkdf2_derive_sha256(&password, &salt, iterations, key_len).unwrap();
prop_assert_eq!(key1.as_slice(), key2.as_slice());
}
#[test]
fn prop_pbkdf2_different_passwords(
pass1 in prop::collection::vec(any::<u8>(), 1..32),
pass2 in prop::collection::vec(any::<u8>(), 1..32),
salt in prop::collection::vec(any::<u8>(), 8..16)
) {
prop_assume!(pass1 != pass2);
let key1 = pbkdf2_derive_sha256(&pass1, &salt, 10000, 32).unwrap();
let key2 = pbkdf2_derive_sha256(&pass2, &salt, 10000, 32).unwrap();
prop_assert_ne!(key1.as_slice(), key2.as_slice());
}
#[test]
fn prop_pbkdf2_different_salts(
password in prop::collection::vec(any::<u8>(), 1..32),
salt1 in prop::collection::vec(any::<u8>(), 8..16),
salt2 in prop::collection::vec(any::<u8>(), 8..16)
) {
prop_assume!(salt1 != salt2);
let key1 = pbkdf2_derive_sha256(&password, &salt1, 10000, 32).unwrap();
let key2 = pbkdf2_derive_sha256(&password, &salt2, 10000, 32).unwrap();
prop_assert_ne!(key1.as_slice(), key2.as_slice());
}
#[test]
fn prop_pbkdf2_output_length(
password in prop::collection::vec(any::<u8>(), 1..16),
salt in prop::collection::vec(any::<u8>(), 8..12),
key_len in 16usize..48usize
) {
let key = pbkdf2_derive_sha512(&password, &salt, 10000, key_len).unwrap();
prop_assert_eq!(key.len(), key_len);
}
#[test]
fn prop_hkdf_deterministic(
ikm in prop::collection::vec(any::<u8>(), 16..32),
salt in prop::collection::vec(any::<u8>(), 0..16),
info in prop::collection::vec(any::<u8>(), 0..32),
key_len in 16usize..32usize
) {
let key1 = hkdf_extract_expand(&salt, &ikm, &info, key_len).unwrap();
let key2 = hkdf_extract_expand(&salt, &ikm, &info, key_len).unwrap();
prop_assert_eq!(key1.as_slice(), key2.as_slice());
}
#[test]
fn prop_hkdf_different_ikm(
ikm1 in prop::collection::vec(any::<u8>(), 16..32),
ikm2 in prop::collection::vec(any::<u8>(), 16..32),
salt in prop::collection::vec(any::<u8>(), 8..16)
) {
prop_assume!(ikm1 != ikm2);
let key1 = hkdf_extract_expand(&salt, &ikm1, &[], 32).unwrap();
let key2 = hkdf_extract_expand(&salt, &ikm2, &[], 32).unwrap();
prop_assert_ne!(key1.as_slice(), key2.as_slice());
}
}
proptest! {
#[test]
fn prop_kw256_roundtrip(key in prop::collection::vec(any::<u8>(), 2..8).prop_map(|v| {
let len = (v.len() * 8).max(16);
vec![0x42u8; len]
})) {
let kek = [0x33u8; 32];
let wrapper = Kw256::new(&kek).unwrap();
let wrapped = wrapper.wrap_key(&key).unwrap();
let unwrapped = wrapper.unwrap_key(&wrapped).unwrap();
prop_assert_eq!(unwrapped, key);
}
#[test]
fn prop_kw256_deterministic(key in prop::collection::vec(any::<u8>(), 2..8).prop_map(|v| {
let len = (v.len() * 8).max(16);
vec![0x42u8; len]
})) {
let kek = [0x33u8; 32];
let wrapper = Kw256::new(&kek).unwrap();
let wrapped1 = wrapper.wrap_key(&key).unwrap();
let wrapped2 = wrapper.wrap_key(&key).unwrap();
prop_assert_eq!(wrapped1, wrapped2);
}
#[test]
fn prop_kw128_roundtrip(key in prop::collection::vec(any::<u8>(), 2..8).prop_map(|v| {
let len = (v.len() * 8).max(16);
vec![0x42u8; len]
})) {
let kek = [0x33u8; 16];
let wrapper = Kw128::new(&kek).unwrap();
let wrapped = wrapper.wrap_key(&key).unwrap();
let unwrapped = wrapper.unwrap_key(&wrapped).unwrap();
prop_assert_eq!(unwrapped, key);
}
#[test]
fn prop_kw192_roundtrip(key in prop::collection::vec(any::<u8>(), 2..8).prop_map(|v| {
let len = (v.len() * 8).max(16);
vec![0x42u8; len]
})) {
let kek = [0x33u8; 24];
let wrapper = Kw192::new(&kek).unwrap();
let wrapped = wrapper.wrap_key(&key).unwrap();
let unwrapped = wrapper.unwrap_key(&wrapped).unwrap();
prop_assert_eq!(unwrapped, key);
}
#[test]
fn prop_kw_wrong_kek_fails(key in prop::collection::vec(any::<u8>(), 2..4).prop_map(|v| {
let len = (v.len() * 8).max(16);
vec![0x42u8; len]
})) {
let kek1 = [0x33u8; 32];
let kek2 = [0x44u8; 32];
let wrapper1 = Kw256::new(&kek1).unwrap();
let wrapper2 = Kw256::new(&kek2).unwrap();
let wrapped = wrapper1.wrap_key(&key).unwrap();
let result = wrapper2.unwrap_key(&wrapped);
prop_assert!(result.is_err());
}
}
proptest! {
#[test]
fn prop_base64_roundtrip(data in prop::collection::vec(any::<u8>(), 0..512)) {
let encoded = base64_encode(&data);
let decoded = base64_decode(&encoded).unwrap();
prop_assert_eq!(decoded, data);
}
#[test]
fn prop_hex_roundtrip(data in prop::collection::vec(any::<u8>(), 0..512)) {
let encoded = hex_encode(&data);
let decoded = hex_decode(&encoded).unwrap();
prop_assert_eq!(decoded, data);
}
#[test]
fn prop_hex_valid_chars(data in prop::collection::vec(any::<u8>(), 0..256)) {
let encoded = hex_encode(&data);
for ch in encoded.chars() {
prop_assert!(ch.is_ascii_hexdigit());
}
}
#[test]
fn prop_hex_length(data in prop::collection::vec(any::<u8>(), 0..256)) {
let encoded = hex_encode(&data);
prop_assert_eq!(encoded.len(), data.len() * 2);
}
#[test]
fn prop_base64_valid_chars(data in prop::collection::vec(any::<u8>(), 0..256)) {
let encoded = base64_encode(&data);
for ch in encoded.chars() {
let valid = ch.is_ascii_alphanumeric() || ch == '+' || ch == '/' || ch == '=';
prop_assert!(valid, "Invalid base64 character: {}", ch);
}
}
}
proptest! {
#[test]
fn prop_sha256_deterministic(data in prop::collection::vec(any::<u8>(), 0..1024)) {
let hash1 = sha256(&data);
let hash2 = sha256(&data);
prop_assert_eq!(hash1, hash2);
}
#[test]
fn prop_sha256_length(data in prop::collection::vec(any::<u8>(), 0..1024)) {
let hash = sha256(&data);
prop_assert_eq!(hash.len(), 32);
}
#[test]
fn prop_sha512_deterministic(data in prop::collection::vec(any::<u8>(), 0..1024)) {
let hash1 = sha512(&data);
let hash2 = sha512(&data);
prop_assert_eq!(hash1, hash2);
}
#[test]
fn prop_sha512_length(data in prop::collection::vec(any::<u8>(), 0..1024)) {
let hash = sha512(&data);
prop_assert_eq!(hash.len(), 64);
}
#[test]
fn prop_sha256_avalanche(
data1 in prop::collection::vec(any::<u8>(), 1..256),
data2 in prop::collection::vec(any::<u8>(), 1..256)
) {
prop_assume!(data1 != data2);
let hash1 = sha256(&data1);
let hash2 = sha256(&data2);
prop_assert_ne!(hash1, hash2);
}
}
proptest! {
#[test]
fn prop_hmac_sha256_deterministic(
key in prop::collection::vec(any::<u8>(), 1..64),
message in prop::collection::vec(any::<u8>(), 0..512)
) {
let mac1 = hmac_sha256(&key, &message).unwrap();
let mac2 = hmac_sha256(&key, &message).unwrap();
prop_assert_eq!(mac1, mac2);
}
#[test]
fn prop_hmac_sha256_verify_correct(
key in prop::collection::vec(any::<u8>(), 1..64),
message in prop::collection::vec(any::<u8>(), 0..512)
) {
let mac = hmac_sha256(&key, &message).unwrap();
let result = hmac_sha256_verify(&key, &message, &mac);
prop_assert!(result.is_ok());
}
#[test]
fn prop_hmac_sha512_deterministic(
key in prop::collection::vec(any::<u8>(), 1..64),
message in prop::collection::vec(any::<u8>(), 0..512)
) {
let mac1 = hmac_sha512(&key, &message).unwrap();
let mac2 = hmac_sha512(&key, &message).unwrap();
prop_assert_eq!(mac1, mac2);
}
#[test]
fn prop_hmac_different_keys(
key1 in prop::collection::vec(any::<u8>(), 1..64),
key2 in prop::collection::vec(any::<u8>(), 1..64),
message in prop::collection::vec(any::<u8>(), 0..256)
) {
prop_assume!(key1 != key2);
let mac1 = hmac_sha256(&key1, &message).unwrap();
let mac2 = hmac_sha256(&key2, &message).unwrap();
prop_assert_ne!(mac1, mac2);
}
#[test]
fn prop_hmac_different_messages(
key in prop::collection::vec(any::<u8>(), 1..64),
msg1 in prop::collection::vec(any::<u8>(), 1..256),
msg2 in prop::collection::vec(any::<u8>(), 1..256)
) {
prop_assume!(msg1 != msg2);
let mac1 = hmac_sha256(&key, &msg1).unwrap();
let mac2 = hmac_sha256(&key, &msg2).unwrap();
prop_assert_ne!(mac1, mac2);
}
}
proptest! {
#[test]
fn prop_aead_empty_plaintext(_dummy in any::<u8>()) {
let plaintext = b"";
let aes256 = AesGcm256::new(&[0x42u8; 32]).unwrap();
let ct1 = aes256.encrypt(plaintext, None).unwrap();
let pt1 = aes256.decrypt(&ct1, None).unwrap();
prop_assert_eq!(pt1.as_slice(), plaintext);
let chacha = ChaCha20Poly1305::new(&[0x42u8; 32]).unwrap();
let ct2 = chacha.encrypt(plaintext, None).unwrap();
let pt2 = chacha.decrypt(&ct2, None).unwrap();
prop_assert_eq!(pt2.as_slice(), plaintext);
}
#[test]
fn prop_aead_ciphertext_longer(plaintext in prop::collection::vec(any::<u8>(), 0..512)) {
let cipher = AesGcm256::new(&[0x42u8; 32]).unwrap();
let ciphertext = cipher.encrypt(&plaintext, None).unwrap();
let ct_bytes = ciphertext.to_bytes();
prop_assert!(ct_bytes.len() >= plaintext.len() + 28);
}
}
proptest! {
#[test]
fn prop_single_byte_operations(byte in any::<u8>()) {
let data = vec![byte];
let hash = sha256(&data);
prop_assert_eq!(hash.len(), 32);
let b64 = base64_encode(&data);
let decoded = base64_decode(&b64).unwrap();
prop_assert_eq!(decoded.as_slice(), data.as_slice());
let hex_str = hex_encode(&data);
prop_assert_eq!(hex_str.len(), 2);
}
#[test]
fn prop_large_data_handling(size in 1024usize..4096usize) {
let data = vec![0x42u8; size];
let _ = sha256(&data);
let _ = sha512(&data);
let _ = base64_encode(&data);
let _ = hex_encode(&data);
}
}