The challenge: https://cryptopals.com/sets/2/challenges/10

My solution: https://github.com/CJStadler/cryptopals-rust/commit/8d44e31

The challenge’s description of CBC had some ambiguities, but the Wikipedia article was very helpful. Once I understood CBC the implementation was pretty straightforward and I only ran into two issues.

The first was that I had an error in my solution to challenge 9, as discussed in my previous post.

The second was that my function for decoding AES-128 in ECB mode (from challenge 7) was panicking. The message wasn’t very helpful (“bad decrypt”) but I suspected the issue was that it expected a full ciphertext (including padding), and I was instead trying to decode a single block at a time.

Instead of using openssl::symm::decrypt I needed the lower level openssl::symm::Crypter, which allowed me to disable padding.

One curious thing about this method was that it required me to allocate a Vec twice as long as the block length, even though I was only trying to decrypt a single block (documentation). I then had to call Vec#truncate(block_size) to remove the extra bytes.

Rust

I hit one compilation error that was confusing. The code was essentially this:

let mut copy: Vec<usize> = vec![0, 0];
let original: Vec<u8> = vec![1, 2];
copy.clone_from_slice(&original);

Which produces the error

|     copy.clone_from_slice(&original);
|                           ^^^^^^^^^ expected slice, found struct `std::vec::Vec`
|
= note: expected type `&[usize]`
           found type `&std::vec::Vec<u8>`

I read “expected slice, found struct std::vec::Vec” and was confused because I thought I was implicitly taking a slice with &original. It took me a while to carefully read the note below and notice that the type parameters were different: usize vs. u8. Ideally the compiler would be able to tell that the issue is not &[T] vs &Vec<T>, and highlight for the user the different type parameters.

I also ran into an interesting issue that forced me to think about lifetimes for the first time, although I avoided needing to annotate any explicitly. Implementing pkcs_unpad I started with this signature:

pub fn pkcs_unpad(padded_message: &[u8]) -> Vec<u8> {}

The returned vector should be a sub-slice of the padded_message though, so we should be able to avoid allocating a new Vec. At first I thought “why isn’t there some way to make a new Vec, but backed by the memory of a slice?” That would create multiple “owners” of the data though, so it is correctly prevented by the compiler.

Instead of returning a Vec<u8> what we want is &[u8]. I reached that conclusion by somewhat blindly following the compiler suggestions, but it makes sense: the unpadded message should be slice of the padded message. I think this was the first time I had to return a reference from a function so it felt a little awkward. What is this reference borrowed from?

This is where lifetimes come in. A reference can’t live longer than what it points to, so if a function returns a reference it needs to know which parameter the reference is taken from. In our case above this is unambiguous because there is only one parameter, but if we add another parameter the compiler gives us an error:

this function’s return type contains a borrowed value, but the signature does not say whether it is borrowed from padded_message or foo

Now we would need to add a lifetime annotation:

fn unpad<'a>(padded_message: &'a [u8], foo: &[u8]) -> &'a [u8] {}