RustCrypto / traits

Collection of cryptography-related traits
573 stars 187 forks source link

Support one pass re-encryption? #824

Open newpavlov opened 2 years ago

newpavlov commented 2 years ago

I have encountered a problem in which I need to re-encrypt data. I wonder if we could add some kind of API which would allow "in-flight" re-encryption, i.e. we would decrypt blocks (or a block, if cipher does not support parallel decryption) with an old cipher, immedetely encrypt them with a new cipher, and only then store the result in an output buffer. It should not only help with performance, since data processing is done in one pass, but also improve security a bit, since it would minimize time plaintext spends in RAM.

In my case decryption and encryption is done by a single algorithm, but in general decryption and encryption may be performed by different algorithms (e.g. you decrypt data with AES-GCM and encrypt it with ChaCha20Poly1305).

Unfortunately, right now I don't have good ideas of how such API could look like. Do you have any good ideas and do you think it's worth to support such API in trait crates?

tarcieri commented 2 years ago

Something based on iterators might be useful here, iterating over chunks of the ciphertext, decrypting them, then passing the chunk for re-encryption.

But the real issue is this would require disclosing unauthenticated plaintexts, and would have an odd error case after the input has been re-encrypted in the event MAC verification fails.

If inputs are really so long that a microoptimization like this is needed, another option is to use STREAM to segment them, which avoids disclosing unauthenticated plaintexts.

newpavlov commented 2 years ago

Ideally data would stay in registers without spilling to stack or output buffer. In the stream ciphers case we even could XOR two keystream blocks and apply result to the input ciphertext, thus fundamentally eliminating plaintext exposure.

Also using iterators could be quite difficult in the case of block size mismatch.

But the real issue is this would require disclosing unauthenticated plaintexts, and would have an odd error case after the input has been re-encrypted in the event MAC verification fails.

I think we should avoid one-pass AEAD decryption if possible (i.e. we should check tag first and only then start decryption), even if it damages performance a bit. This applies to the proposed re-encryption as well. Yes, we could zeroize output buffer in the case of tag mismatch and from Rust PoV it would look like we do not expose plaintext, but there are some nasty corner cases like potential panics and operating over shared memory.