change key length
This commit is contained in:
1 parent
c700f1a695
commit
31ceedc233
4 files changed
+113
-73
No files matched your search
@@ -10,3 +10,8 @@ subtle = "2.6.1"
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.27.0"
|
tempfile = "3.27.0"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
strip = true
|
||||||
|
lto = true
|
||||||
|
panic = "abort"
|
||||||
+6
-8
@@ -1,23 +1,21 @@
|
|||||||
//! Password-to-key derivation.
|
//! Password-to-key derivation.
|
||||||
//!
|
//!
|
||||||
//! This currently uses SHA3-256 directly and truncates the result to 24 bytes
|
//! This currently uses SHA3-512 directly
|
||||||
//! because `KeccakAead` accepts keys up to 24 bytes.
|
|
||||||
//!
|
//!
|
||||||
//! This is not a proper password-based KDF. There is currently no salt,
|
//! This is not a proper password-based KDF. There is currently no salt,
|
||||||
//! work factor, memory hardness, or iteration count.
|
//! work factor, memory hardness, or iteration count.
|
||||||
|
|
||||||
use crate::keccak::sha3_256;
|
use crate::keccak::sha3_512;
|
||||||
|
|
||||||
/// Length of the key produced for `KeccakAead`.
|
/// Length of the key produced for `KeccakAead`.
|
||||||
pub const DERIVED_KEY_LEN: usize = 24;
|
pub const DERIVED_KEY_LEN: usize = 64;
|
||||||
|
|
||||||
/// Derive a 24-byte encryption key from a password.
|
/// Derive a 64-byte encryption key from a password.
|
||||||
///
|
///
|
||||||
/// The password is encoded as UTF-8 and hashed using the project's
|
/// The password is encoded as UTF-8 and hashed using the project's
|
||||||
/// SHA3-256 implementation. The first 24 bytes of the digest are used
|
/// SHA3-512 implementation.
|
||||||
/// as the AEAD key.
|
|
||||||
pub fn derive_file_key(password: &str) -> [u8; DERIVED_KEY_LEN] {
|
pub fn derive_file_key(password: &str) -> [u8; DERIVED_KEY_LEN] {
|
||||||
let digest = sha3_256(password.as_bytes());
|
let digest = sha3_512(password.as_bytes());
|
||||||
|
|
||||||
let mut key = [0u8; DERIVED_KEY_LEN];
|
let mut key = [0u8; DERIVED_KEY_LEN];
|
||||||
key.copy_from_slice(&digest[..DERIVED_KEY_LEN]);
|
key.copy_from_slice(&digest[..DERIVED_KEY_LEN]);
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ use crate::kdf::kdf::DERIVED_KEY_LEN;
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn key_has_expected_length() {
|
fn key_has_expected_length() {
|
||||||
assert_eq!(DERIVED_KEY_LEN, 24);
|
assert_eq!(DERIVED_KEY_LEN, 64);
|
||||||
|
|
||||||
let key = derive_file_key("test password");
|
let key = derive_file_key("test password");
|
||||||
assert_eq!(key.len(), DERIVED_KEY_LEN);
|
assert_eq!(key.len(), DERIVED_KEY_LEN);
|
||||||
|
|||||||
+101
-64
@@ -1,9 +1,11 @@
|
|||||||
use crate::keccak::shake256;
|
use crate::keccak::shake256;
|
||||||
|
|
||||||
const RATE_SIZE: usize = 16;
|
const RATE_SIZE: usize = 16;
|
||||||
const CAPACITY_SIZE: usize = 24;
|
const CAPACITY_SIZE: usize = 64;
|
||||||
const STATE_SIZE: usize = RATE_SIZE + CAPACITY_SIZE;
|
const STATE_SIZE: usize = RATE_SIZE + CAPACITY_SIZE;
|
||||||
const CHUNK_SIZE: usize = 16;
|
const CHUNK_SIZE: usize = 16;
|
||||||
|
const MIN_KEY_SIZE: usize = 16;
|
||||||
|
const MAX_KEY_SIZE: usize = 64;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct EncryptionResult {
|
pub struct EncryptionResult {
|
||||||
@@ -23,13 +25,19 @@ pub struct KeccakAead {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl KeccakAead {
|
impl KeccakAead {
|
||||||
|
#[inline]
|
||||||
|
fn assert_valid_key_size(key: &[u8]) {
|
||||||
|
assert!(
|
||||||
|
(MIN_KEY_SIZE..=MAX_KEY_SIZE).contains(&key.len()),
|
||||||
|
"key must be between {} and {} bytes",
|
||||||
|
MIN_KEY_SIZE,
|
||||||
|
MAX_KEY_SIZE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Initialize the AEAD state.
|
/// Initialize the AEAD state.
|
||||||
pub fn new(iv: &[u8], key: &[u8], nonce: &[u8]) -> Self {
|
pub fn new(iv: &[u8], key: &[u8], nonce: &[u8]) -> Self {
|
||||||
assert!(
|
Self::assert_valid_key_size(key);
|
||||||
key.len() <= CAPACITY_SIZE,
|
|
||||||
"key must be at most {} bytes",
|
|
||||||
CAPACITY_SIZE
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut input = Vec::with_capacity(iv.len() + key.len() + nonce.len());
|
let mut input = Vec::with_capacity(iv.len() + key.len() + nonce.len());
|
||||||
|
|
||||||
@@ -42,8 +50,8 @@ impl KeccakAead {
|
|||||||
let mut state = [0u8; STATE_SIZE];
|
let mut state = [0u8; STATE_SIZE];
|
||||||
state.copy_from_slice(&squeezed);
|
state.copy_from_slice(&squeezed);
|
||||||
|
|
||||||
for i in 0..key.len() {
|
for (i, &byte) in key.iter().enumerate() {
|
||||||
state[RATE_SIZE + i] ^= key[i];
|
state[RATE_SIZE + i] ^= byte;
|
||||||
}
|
}
|
||||||
|
|
||||||
Self { state }
|
Self { state }
|
||||||
@@ -60,8 +68,9 @@ impl KeccakAead {
|
|||||||
input[i] = byte ^ self.state[i];
|
input[i] = byte ^ self.state[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
// c = state[16..40]
|
// c = state[16..48]
|
||||||
input[RATE_SIZE..STATE_SIZE].copy_from_slice(&self.state[RATE_SIZE..STATE_SIZE]);
|
input[RATE_SIZE..STATE_SIZE]
|
||||||
|
.copy_from_slice(&self.state[RATE_SIZE..STATE_SIZE]);
|
||||||
|
|
||||||
self.state = shake256_state(&input);
|
self.state = shake256_state(&input);
|
||||||
}
|
}
|
||||||
@@ -70,7 +79,7 @@ impl KeccakAead {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Encrypt plaintext.
|
/// Encrypt plaintext.
|
||||||
/// Returns the ciphertext split logically into 16-byte chunks, then concatenated
|
/// Returns the ciphertext split logically into 16-byte chunks, then concatenated.
|
||||||
pub fn plaintext_processing(&mut self, plaintext: &[u8]) -> Vec<u8> {
|
pub fn plaintext_processing(&mut self, plaintext: &[u8]) -> Vec<u8> {
|
||||||
let mut ciphertext = Vec::with_capacity(plaintext.len());
|
let mut ciphertext = Vec::with_capacity(plaintext.len());
|
||||||
|
|
||||||
@@ -89,7 +98,8 @@ impl KeccakAead {
|
|||||||
|
|
||||||
input[..chunk.len()].copy_from_slice(&ciphertext[start..]);
|
input[..chunk.len()].copy_from_slice(&ciphertext[start..]);
|
||||||
|
|
||||||
input[RATE_SIZE..STATE_SIZE].copy_from_slice(&self.state[RATE_SIZE..STATE_SIZE]);
|
input[RATE_SIZE..STATE_SIZE]
|
||||||
|
.copy_from_slice(&self.state[RATE_SIZE..STATE_SIZE]);
|
||||||
|
|
||||||
self.state = shake256_state(&input);
|
self.state = shake256_state(&input);
|
||||||
}
|
}
|
||||||
@@ -112,7 +122,8 @@ impl KeccakAead {
|
|||||||
// input = ciphertext || c
|
// input = ciphertext || c
|
||||||
input[..chunk.len()].copy_from_slice(chunk);
|
input[..chunk.len()].copy_from_slice(chunk);
|
||||||
|
|
||||||
input[RATE_SIZE..STATE_SIZE].copy_from_slice(&self.state[RATE_SIZE..STATE_SIZE]);
|
input[RATE_SIZE..STATE_SIZE]
|
||||||
|
.copy_from_slice(&self.state[RATE_SIZE..STATE_SIZE]);
|
||||||
|
|
||||||
self.state = shake256_state(&input);
|
self.state = shake256_state(&input);
|
||||||
}
|
}
|
||||||
@@ -123,11 +134,7 @@ impl KeccakAead {
|
|||||||
/// Finalize and generate the authentication tag.
|
/// Finalize and generate the authentication tag.
|
||||||
/// The tag has the same length as the key.
|
/// The tag has the same length as the key.
|
||||||
pub fn finalize(&mut self, key: &[u8]) -> Vec<u8> {
|
pub fn finalize(&mut self, key: &[u8]) -> Vec<u8> {
|
||||||
assert!(
|
Self::assert_valid_key_size(key);
|
||||||
key.len() <= CAPACITY_SIZE,
|
|
||||||
"key must be at most {} bytes",
|
|
||||||
CAPACITY_SIZE
|
|
||||||
);
|
|
||||||
|
|
||||||
// Construct padded key:
|
// Construct padded key:
|
||||||
let mut padded_key = [0u8; CAPACITY_SIZE];
|
let mut padded_key = [0u8; CAPACITY_SIZE];
|
||||||
@@ -137,22 +144,23 @@ impl KeccakAead {
|
|||||||
padded_key[padding_len..].copy_from_slice(key);
|
padded_key[padding_len..].copy_from_slice(key);
|
||||||
|
|
||||||
// XOR padded key into c.
|
// XOR padded key into c.
|
||||||
for (i, &byte) in padded_key.iter().enumerate().take(CAPACITY_SIZE) {
|
for (state_byte, &key_byte) in self.state[RATE_SIZE..STATE_SIZE]
|
||||||
self.state[RATE_SIZE + i] ^= byte;
|
.iter_mut()
|
||||||
|
.zip(padded_key.iter())
|
||||||
|
{
|
||||||
|
*state_byte ^= key_byte;
|
||||||
}
|
}
|
||||||
|
|
||||||
// SHAKE256(state, 40)
|
// SHAKE256(state, 48)
|
||||||
let output = shake256(&self.state, STATE_SIZE);
|
let output = shake256(&self.state, STATE_SIZE);
|
||||||
|
|
||||||
|
// Take the last `key.len()` bytes from the output.
|
||||||
let tag_start = STATE_SIZE - key.len();
|
let tag_start = STATE_SIZE - key.len();
|
||||||
|
|
||||||
let mut tag = Vec::with_capacity(key.len());
|
key.iter()
|
||||||
|
.zip(&output[tag_start..])
|
||||||
for i in 0..key.len() {
|
.map(|(&key_byte, &output_byte)| key_byte ^ output_byte)
|
||||||
tag.push(key[i] ^ output[tag_start + i]);
|
.collect()
|
||||||
}
|
|
||||||
|
|
||||||
tag
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encrypt:
|
/// Encrypt:
|
||||||
@@ -199,7 +207,7 @@ impl KeccakAead {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// SHAKE256 wrapper returning exactly a 40-byte AEAD state.
|
/// SHAKE256 wrapper returning exactly a 48-byte AEAD state.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn shake256_state(state: &[u8; STATE_SIZE]) -> [u8; STATE_SIZE] {
|
fn shake256_state(state: &[u8; STATE_SIZE]) -> [u8; STATE_SIZE] {
|
||||||
let output = shake256(state, STATE_SIZE);
|
let output = shake256(state, STATE_SIZE);
|
||||||
@@ -217,55 +225,62 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn encrypt_decrypt_roundtrip() {
|
fn encrypt_decrypt_roundtrip() {
|
||||||
let key = [0x00u8; 16];
|
for key_len in [16, 17, 24, 25, 31, 32] {
|
||||||
let iv = [0x11u8; 16];
|
let key = vec![0x00u8; key_len];
|
||||||
let nonce = [0x22u8; 16];
|
let iv = [0x11u8; 16];
|
||||||
|
let nonce = [0x22u8; 16];
|
||||||
|
|
||||||
let associated_data = b"associated data";
|
let associated_data = b"associated data";
|
||||||
|
let plaintext = b"Hello, Keccak AEAD!";
|
||||||
|
|
||||||
let plaintext = b"Hello, Keccak AEAD!";
|
let encrypted =
|
||||||
|
KeccakAead::encrypt(&key, plaintext, &iv, associated_data, &nonce);
|
||||||
|
|
||||||
let encrypted = KeccakAead::encrypt(&key, plaintext, &iv, associated_data, &nonce);
|
let decrypted =
|
||||||
|
KeccakAead::decrypt(&key, &encrypted.cipher, &iv, associated_data, &nonce);
|
||||||
|
|
||||||
let decrypted = KeccakAead::decrypt(&key, &encrypted.cipher, &iv, associated_data, &nonce);
|
assert_eq!(decrypted.plaintext, plaintext);
|
||||||
|
assert_eq!(decrypted.tag, encrypted.tag);
|
||||||
assert_eq!(decrypted.plaintext, plaintext);
|
assert_eq!(encrypted.tag.len(), key_len);
|
||||||
|
}
|
||||||
assert_eq!(decrypted.tag, encrypted.tag);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn handles_empty_plaintext() {
|
fn handles_empty_plaintext() {
|
||||||
let key = [0x42u8; 16];
|
for key_len in [16, 24, 32] {
|
||||||
let iv = [0x11u8; 16];
|
let key = vec![0x42u8; key_len];
|
||||||
let nonce = [0x22u8; 16];
|
let iv = [0x11u8; 16];
|
||||||
|
let nonce = [0x22u8; 16];
|
||||||
|
|
||||||
let result = KeccakAead::encrypt(&key, &[], &iv, b"test", &nonce);
|
let result = KeccakAead::encrypt(&key, &[], &iv, b"test", &nonce);
|
||||||
|
|
||||||
assert_eq!(result.cipher.len(), 0);
|
assert_eq!(result.cipher.len(), 0);
|
||||||
assert_eq!(result.tag.len(), key.len());
|
assert_eq!(result.tag.len(), key_len);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn handles_empty_associated_data() {
|
fn handles_empty_associated_data() {
|
||||||
let key = [0x42u8; 16];
|
for key_len in [16, 24, 32] {
|
||||||
let iv = [0x11u8; 16];
|
let key = vec![0x42u8; key_len];
|
||||||
let nonce = [0x22u8; 16];
|
let iv = [0x11u8; 16];
|
||||||
|
let nonce = [0x22u8; 16];
|
||||||
|
|
||||||
let plaintext = b"test plaintext";
|
let plaintext = b"test plaintext";
|
||||||
|
|
||||||
let result = KeccakAead::encrypt(&key, plaintext, &iv, &[], &nonce);
|
let result = KeccakAead::encrypt(&key, plaintext, &iv, &[], &nonce);
|
||||||
|
|
||||||
let decrypted = KeccakAead::decrypt(&key, &result.cipher, &iv, &[], &nonce);
|
let decrypted =
|
||||||
|
KeccakAead::decrypt(&key, &result.cipher, &iv, &[], &nonce);
|
||||||
|
|
||||||
assert_eq!(decrypted.plaintext, plaintext);
|
assert_eq!(decrypted.plaintext, plaintext);
|
||||||
|
assert_eq!(decrypted.tag, result.tag);
|
||||||
assert_eq!(decrypted.tag, result.tag);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn handles_multiple_chunks() {
|
fn handles_multiple_chunks() {
|
||||||
let key = [0x42u8; 16];
|
let key = [0x42u8; 32];
|
||||||
let iv = [0x11u8; 16];
|
let iv = [0x11u8; 16];
|
||||||
let nonce = [0x22u8; 16];
|
let nonce = [0x22u8; 16];
|
||||||
|
|
||||||
@@ -274,39 +289,61 @@ mod tests {
|
|||||||
|
|
||||||
let associated_data = b"abcdefghijklmnopqrstuvwxyz";
|
let associated_data = b"abcdefghijklmnopqrstuvwxyz";
|
||||||
|
|
||||||
let encrypted = KeccakAead::encrypt(&key, plaintext, &iv, associated_data, &nonce);
|
let encrypted =
|
||||||
|
KeccakAead::encrypt(&key, plaintext, &iv, associated_data, &nonce);
|
||||||
|
|
||||||
assert_eq!(encrypted.cipher.len(), plaintext.len());
|
assert_eq!(encrypted.cipher.len(), plaintext.len());
|
||||||
|
assert_eq!(encrypted.tag.len(), key.len());
|
||||||
|
|
||||||
let decrypted = KeccakAead::decrypt(
|
let decrypted = KeccakAead::decrypt(
|
||||||
&key,
|
&key,
|
||||||
&encrypted.cipher,
|
&encrypted.cipher,
|
||||||
iv.as_slice(),
|
&iv,
|
||||||
associated_data,
|
associated_data,
|
||||||
nonce.as_slice(),
|
&nonce,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(decrypted.plaintext, plaintext);
|
assert_eq!(decrypted.plaintext, plaintext);
|
||||||
|
|
||||||
assert_eq!(decrypted.tag, encrypted.tag);
|
assert_eq!(decrypted.tag, encrypted.tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tag_changes_when_ciphertext_changes() {
|
fn tag_changes_when_ciphertext_changes() {
|
||||||
let key = [0x42u8; 16];
|
let key = [0x42u8; 32];
|
||||||
let iv = [0x11u8; 16];
|
let iv = [0x11u8; 16];
|
||||||
let nonce = [0x22u8; 16];
|
let nonce = [0x22u8; 16];
|
||||||
|
|
||||||
let plaintext = b"test plaintext";
|
let plaintext = b"test plaintext";
|
||||||
|
|
||||||
let encrypted = KeccakAead::encrypt(&key, plaintext, &iv, b"ad", &nonce);
|
let encrypted =
|
||||||
|
KeccakAead::encrypt(&key, plaintext, &iv, b"ad", &nonce);
|
||||||
|
|
||||||
let mut modified = encrypted.cipher.clone();
|
let mut modified = encrypted.cipher.clone();
|
||||||
|
|
||||||
modified[0] ^= 1;
|
modified[0] ^= 1;
|
||||||
|
|
||||||
let decrypted = KeccakAead::decrypt(&key, &modified, &iv, b"ad", &nonce);
|
let decrypted =
|
||||||
|
KeccakAead::decrypt(&key, &modified, &iv, b"ad", &nonce);
|
||||||
|
|
||||||
assert_ne!(decrypted.tag, encrypted.tag);
|
assert_ne!(decrypted.tag, encrypted.tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "key must be between 16 and 64 bytes")]
|
||||||
|
fn rejects_short_key() {
|
||||||
|
let key = [0u8; 15];
|
||||||
|
let iv = [0u8; 16];
|
||||||
|
let nonce = [0u8; 16];
|
||||||
|
|
||||||
|
KeccakAead::encrypt(&key, b"test", &iv, b"", &nonce);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "key must be between 16 and 64 bytes")]
|
||||||
|
fn rejects_long_key() {
|
||||||
|
let key = [0u8; 65];
|
||||||
|
let iv = [0u8; 16];
|
||||||
|
let nonce = [0u8; 16];
|
||||||
|
|
||||||
|
KeccakAead::encrypt(&key, b"test", &iv, b"", &nonce);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user