Make encrypt_unit report a refused slice, and expand its key once
Two defects in the encrypt_unit promoted to public API last round, both found by round 2 auditing that new code. It returned silently without encrypting when the slice was shorter than ALIGNED_UNIT_LEN. Its own contract requires the caller to set the container's encrypted flag BEFORE calling — the header is the key seed — so a silent no-op leaves a unit advertised as encrypted while still carrying plaintext, with nothing for an authoring caller to check. It now returns bool and is #[must_use], so ignoring the refusal is a compile-time warning; every call site was updated to assert on it. bool rather than Result deliberately: a wrong buffer length is a programming error at a library boundary, not a disc condition, and a new Error variant would mean a new numeric code plus its rendering in another repo. It also drove CBC from the single-block aes_ecb_encrypt, rebuilding the AES key schedule for each of the 383 blocks in a unit — an order of magnitude slower than its inverse, which expands the key once via aes_cbc_decrypt. The missing counterpart aes_cbc_encrypt now exists alongside it, and encrypt_unit calls it, so the two directions are symmetric in structure as well as in result. For an authoring caller encrypting a 90 GB image that removes ~5.6 billion redundant key expansions. New test pins the boundary: ALIGNED_UNIT_LEN - 1 returns false and leaves the buffer byte-identical, ALIGNED_UNIT_LEN succeeds. The existing round-trip and padding-asymmetry tests still pass, so the CBC rewrite is provably the same transform.
This commit is contained in:
@@ -40,6 +40,35 @@ pub(crate) fn aes_ecb_decrypt(key: &[u8; 16], data: &[u8; 16]) -> [u8; 16] {
|
||||
/// Precondition: `data.len()` is a multiple of 16. Any trailing partial
|
||||
/// block is silently ignored; all callers pass aligned regions (6128 and
|
||||
/// 2032 bytes), and the assert documents/enforces that contract.
|
||||
/// AES-128-CBC encrypt in place under the fixed [`AACS_IV`] — the forward
|
||||
/// direction of [`aes_cbc_decrypt`], and its exact inverse.
|
||||
///
|
||||
/// Constructs the cipher ONCE for the whole slice. Driving this from the
|
||||
/// single-block [`aes_ecb_encrypt`] instead rebuilds the AES key schedule per
|
||||
/// 16-byte block, which for a 6144-byte aligned unit is 383 redundant key
|
||||
/// expansions.
|
||||
pub(crate) fn aes_cbc_encrypt(key: &[u8; 16], data: &mut [u8]) {
|
||||
debug_assert!(
|
||||
data.len() % 16 == 0,
|
||||
"aes_cbc_encrypt requires a block-aligned slice"
|
||||
);
|
||||
let cipher = Aes128::new(GenericArray::from_slice(key));
|
||||
let num_blocks = data.len() / 16;
|
||||
let mut prev = AACS_IV;
|
||||
// Forward order: each block is XORed with the PRECEDING ciphertext block.
|
||||
for i in 0..num_blocks {
|
||||
let offset = i * 16;
|
||||
let mut block = [0u8; 16];
|
||||
for j in 0..16 {
|
||||
block[j] = data[offset + j] ^ prev[j];
|
||||
}
|
||||
let mut ga = GenericArray::clone_from_slice(&block);
|
||||
cipher.encrypt_block(&mut ga);
|
||||
data[offset..offset + 16].copy_from_slice(&ga);
|
||||
prev.copy_from_slice(&ga);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn aes_cbc_decrypt(key: &[u8; 16], data: &mut [u8]) {
|
||||
debug_assert!(
|
||||
data.len() % 16 == 0,
|
||||
|
||||
Reference in New Issue
Block a user