Rust-crypto: AES-CBC doesn't encrypt blank input?


#1

Hello all,

Found this issue while refactoring my secret-service library. I’m definitely not a crypto expert, so I don’t know if I’m understanding this correctly.

Suggestions for handling crypto (AES-CBC) are also welcome, I was thinking of moving away from rust-crypto since there seem to be concerns about its ongoing maintenance, but haven’t found an easy-to-use replacement.


The github issue: https://github.com/DaGenix/rust-crypto/issues/410

AES-CBC does not encrypt blank input. Basically, the result of input [] is []. I’m not a crypto expert, but from what I’ve read the encryptor should add PKCS7 padding to the input (even an empty block) and then encrypt (this might be called finalize in another lib). At least, for my secret-service library, I’m unable to use a blank input to create items in the vault because of incorrect encryption.

I may not be understanding the architecture of the blockmodes.rs architecture, but it looks like an empty input results in exiting the encrypting function with BlockEngineState::FastMode https://github.com/DaGenix/rust-crypto/blob/master/src/blockmodes.rs#L164

I think this means that in the state machine, https://github.com/DaGenix/rust-crypto/blob/master/src/blockmodes.rs#L246, an empty input will never be able to reach BlockEngineState::LastInput, which would be required to pad an empty block and encrypt it. Non-empty non-full blocks seem to be padded and processed fine.

As a quick example, running https://github.com/DaGenix/rust-crypto/blob/master/examples/symmetriccipher.rs with a println! after encrypting the data and blank message

let message = "";
.
.
.
let encrypted_data = encrypt(message.as_bytes(), &key, &iv).ok().unwrap();

println!("{:?}", encrypted_data);

while switching the state machine transition at https://github.com/DaGenix/rust-crypto/blob/master/src/blockmodes.rs#L164 from

 return BlockEngineState::FastMode;

to

 return BlockEngineState::LastInput;

results in the following output:

mochi:rust-crypto (master|✚2) > cargo run --example symmetriccipher
   Compiling rust-crypto v0.2.36 (file:///home/hwchen/projects/rust/secret-service/rust-crypto)
    Finished dev [unoptimized + debuginfo] target(s) in 8.14 secs
     Running `target/debug/examples/symmetriccipher`
[]
mochi:rust-crypto (master|✚2) > cargo run --example symmetriccipher
   Compiling rust-crypto v0.2.36 (file:///home/hwchen/projects/rust/secret-service/rust-crypto)
    Finished dev [unoptimized + debuginfo] target(s) in 8.39 secs
     Running `target/debug/examples/symmetriccipher`
[98, 8, 226, 87, 130, 36, 3, 95, 121, 177, 74, 250, 126, 11, 199, 41]

Please let me know if I’ve understood this correctly.


#2

A general rule-of-thumb in encryption is “don’t encryption non-information”, as it can assist in known-plaintext attacks.

A classic example of this was a world-war-2 era desert outpost of the Germans, which every day sent an enigma-encrypted “nothing to report” over the air. The Allies used this to quickly test their decryption keys for correctness.

The empty string is probably the quintessential non-information, so I personally believe this is intended behaviour…

Why are you filling your vault with “nothing” anyway? Why not with a random string? Or a (potentially salted) “dummy”.


#3

A classic example of this was a world-war-2 era desert outpost of the Germans, which every day sent an enigma-encrypted “nothing to report” over the air. The Allies used this to quickly test their decryption keys for correctness.

This is called a known plaintext attack and all modern (normal) encryption algorithms are safe against this (to a point).

The problem here is that most crypto schemes don’t hide length. However, IMO, it would be reasonable pad empty messages with a full block of padding (but I’m not really an expert in applied crypto).