Skip to content

A constant-time version of aes-gcm decrypt-in-place-detached? #184

@cbeck88

Description

@cbeck88

Hi, I have a use-case that is roughly the following (simplified):

  • A private key exists only in an SGX enclave
  • A stream of ciphertexts are passed to the enclave, which it attempts to decrypt
  • It must remain a secret which ones were decryptable by the enclave and which ones aren't, taking into account side-channel leakage via code and data access patterns.

Unfortunately I can't quite do that with the aes-gcm code as is:

    fn decrypt_in_place_detached(
        &self,
        nonce: &GenericArray<u8, NonceSize>,
        associated_data: &[u8],
        buffer: &mut [u8],
        tag: &Tag,
    ) -> Result<(), Error> {
        if buffer.len() as u64 > C_MAX || associated_data.len() as u64 > A_MAX {
            return Err(Error);
        }

        // TODO(tarcieri): interleave encryption with GHASH
        // See: <https://github.com/RustCrypto/AEADs/issues/74>
        let mut expected_tag = self.compute_tag(associated_data, buffer);
        let mut ctr = self.init_ctr(nonce);
        ctr.apply_keystream(&self.cipher, expected_tag.as_mut_slice());

        use subtle::ConstantTimeEq;
        if expected_tag.ct_eq(&tag).unwrap_u8() == 1 {
            ctr.apply_keystream(&self.cipher, buffer);
            Ok(())
        } else {
            Err(Error)
        }
    }

What I would really like is a version of this function that looks like this for example:

    fn ct_decrypt_in_place_detached(
        &self,
        nonce: &GenericArray<u8, NonceSize>,
        associated_data: &[u8],
        buffer: &mut [u8],
        tag: &Tag,
    ) -> bool {
        if buffer.len() as u64 > C_MAX || associated_data.len() as u64 > A_MAX {
            return false;
        }

        // TODO(tarcieri): interleave encryption with GHASH
        // See: <https://github.com/RustCrypto/AEADs/issues/74>
        let mut expected_tag = self.compute_tag(associated_data, buffer);
        let mut ctr = self.init_ctr(nonce);
        ctr.apply_keystream(&self.cipher, expected_tag.as_mut_slice());
        ctr.apply_keystream(&self.cipher, buffer);

        use subtle::ConstantTimeEq;
        bool::from(expected_tag.ct_eq(&tag))
    }

The key differences being:

  • ctr.apply_keystream(&self.cipher, buffer); happens unconditionally and we never branch on the outcome of mac check
  • Instead of returning Result, we return bool, or perhaps some subtle type. Because afaik there is no branchless way to access Result. (I don't know that I can assume that result.is_ok() won't contain a branch? I assume it boils down to a rust match against an enum, and would contain a branch unless the compiler optimized the branch away. I would hope that llvm can optimize this though.) But also, it's not clear that in rust, you can create Result type without branching?

What do you think? Would you take a patch that
(1) Adds ct_detached_in_place_decrypt to trait AeadInPlace in RustCrypto/traits? (and possibly, makes detached_in_place_decrypt chain to this by default?)
OR
(2) Adds a new trait AeadInPlaceCt or similar in RustCrypto/traits , and adds the function there, and implements it on AesGcm struct?

Slight preference for not putting subtle types in an API because it will ease versioning?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions