Goal
Last week I got ensnared in complicates of my own making, by trying to do too much in one session.
Today's goal is to get more comfortable with the aes-gcm-siv
crate
and make an ergonomic wrapper for it. I'll work in a separate Git
repository to avoid any previous mess I've created.
I want to have a function to encrypt (given plain text, key) and decrypt (getting encryption return value and key), with some unit tests to have some confidence this works.
Plan
- Create a type for the encryption key (
Key
), and a method generate a random one. - Create a function to encrypt:
fn encrypt(plain: &[u8], key: &Key) -> Result<Encrypted, CipherErrror>
- And the reverse operation:
fn decrypt(encrypted: &Encrypted, key: &Key) -> Result<Vec<u8>, CipherError>
- I don't intend to expose the nonce in this interface.
Notes
- Apparently "plain text" is the more conventional term for what I've called "clear text", so I'll change terminology.
- Create a new Rust project (
cargo init aead-toy
). - For simplicity, I'll keep everything in
src/main.rs
for now. - Add
aes-gcm-siv
as a dependency. - Create a new key type.
- Generate key using helper function:
Aes256GcmSiv::generate_key(&mut OsRng).to_vec()
- Add a type for a nonce. We won't expose this, but it's good to have it for type safely. A nonce also needs to be random. Unlike a key, however, it does not need to be kept secret. It's like a salt used when storing passwords: it means each repeated encryption output is different, even if the input don't change. This makes dictionary attacks hard.
- Add an error type. From previous work I know that the
aes-gcm-siv
crate doesn't have helpful error values. This is presumably so they don't leak information that might be helpful to an attacker. However, I want my type to at least reveal what operation failed. - Add a type for the result of a successful encryption. This needs to contain the ciphertext, but also the nonce, as the nonce is needed for decryption, and I don't want the caller to have to deal with that.
- Then implement encryption and decryption. With a little test.
- This works, but it lacks associated data support. It's also not well packaged, so it'll be inconvenient to use if I transplant it into Obnam 3, but I can worry about that when I get back to that.
- To add associated data, I'll add an
ad: &{u8]
argument to my encryption and decryption functions. - The associated data will be not in the
Encrypted
struct. It needs to be known by the caller ofdecrypt
via an out of band method, so that it can be authenticated. If it's part ofEncrypted
, there's not point in having any. - This is subtly different from having the associated data as part of a chunk. It needs to be part of the chunk, so the backup store can use it for finding chunks, but outside of the encrypted data, so that the repository store doesn't need to try to interpret that.
- Add a module so that I can be careful of what is exposed.
- With that I believe I have something I understand and is reasonably ergonomic to use. I'll need to review the types I've made for encryption in Obnam 3, but that's for next time.
Summary
I set a more modest goal this time and I achieved that. Today I didn't make a lot of progress, but I made progress. That's better than what happened last time, when I set a more ambitious goal and got into a situation where I tried to change too many things at once and ended up with not getting anything working.
Comments?
If you have feedback on this development session, please use the following fediverse thread: https://toot.liw.fi/@liw/114368614423532083,