Cryptopals challenge 10
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
orfoo
Now we would need to add a lifetime annotation:
fn unpad<'a>(padded_message: &'a [u8], foo: &[u8]) -> &'a [u8] {}