Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Crypto backends

The AEAD algorithms run on a crypto library that a feature compiles in. Each backend feature adds a type to kms_aead::backends, and AeadCipher, DeterministicCipher, KmsAeadEnvelope and LocalKmsProvider take that type as their backend type parameter B. A build may enable any number of backends, and there is no default one.

FeatureTypeLibrary
ringRingring
aws-lcAwsLcaws-lc-rs
aws-lc-fipsAwsLc, on the FIPS moduleaws-lc-rs
rustcryptoRustCryptothe RustCrypto AEADs

There is no default because Cargo unifies features: a default chosen by features would change for every crate in a build when one crate enables another backend.

Code generic over the backend

The backend types implement AeadBackend, a sealed trait in kms_aead::backends. Its items are the constant NAME, the backend’s name in spans and logs: ring, aws-lc or rustcrypto, and generate_random_bytes. Code that works with any backend bounds its type parameter with AeadBackend and nothing else:

use kms_aead::backends::{AeadBackend, Ring};
use kms_aead::providers::{LocalKekId, LocalKmsProvider};
use kms_aead::{KmsAeadEnvelope, KmsAeadResult};
use secret_vault_value::SecretValue;

struct Orders<B> {
    envelope: KmsAeadEnvelope<LocalKmsProvider<B>, B>,
}

impl<B: AeadBackend> Orders<B> {
    fn new() -> KmsAeadResult<Self> {
        let provider = LocalKmsProvider::ephemeral(LocalKekId::try_from("orders".to_string())?)?;
        Ok(Self {
            envelope: KmsAeadEnvelope::new(provider)?,
        })
    }

    async fn round_trip(&self, card: &SecretValue) -> KmsAeadResult<SecretValue> {
        let sealed = self.envelope.encrypt(b"orders/1042", card).await?;
        self.envelope.decrypt(b"orders/1042", &sealed).await
    }
}

#[tokio::main(flavor = "current_thread")]
async fn main() -> KmsAeadResult<()> {
let orders = Orders::<Ring>::new()?;
let card = SecretValue::from("4111 1111 1111 1111");
assert_eq!(orders.round_trip(&card).await?, card);
Ok(())
}

B::generate_random_bytes(len) returns len bytes from the CSPRNG of the backend B, for secrets the application generates itself.

Algorithms

AlgorithmKeyRingAwsLcRustCrypto
ChaCha20Poly1305 (RFC 8439)32 bytesyesyesyes
Aes128Gcm16 bytesyesyesyes
Aes256Gcm, the envelope’s default32 bytesyesyesyes
Aes256GcmSiv (RFC 8452)32 bytesnoyesyes

Every algorithm writes [nonce: 12][ciphertext][tag: 16], with a random nonce from the backend’s CSPRNG. A backend that does not implement the algorithm fails at construction, never later, with KmsAeadError::UnsupportedAlgorithm and the code UNSUPPORTED_ALGORITHM:

use kms_aead::backends::Ring;
use kms_aead::{errors::KmsAeadError, AeadAlgorithm, AeadCipher};

let result = AeadCipher::<Ring>::new(AeadAlgorithm::Aes256GcmSiv);
assert!(matches!(result, Err(KmsAeadError::UnsupportedAlgorithm(_))));

AeadAlgorithm reads and writes its names with FromStr and Display: chacha20-poly1305, aes-128-gcm, aes-256-gcm and aes-256-gcm-siv.

AEAD without a KMS

AeadCipher seals and opens values with a key the application holds. encrypt_bare writes the AEAD output alone, an Encrypted<T, Bare>:

use kms_aead::backends::Ring;
use kms_aead::formats::Bare;
use kms_aead::{AeadAlgorithm, AeadCipher, Encrypted};
use secret_vault_value::SecretValue;

fn main() -> kms_aead::KmsAeadResult<()> {
let cipher = AeadCipher::<Ring>::new(AeadAlgorithm::ChaCha20Poly1305)?;
let dek = cipher.generate_dek()?;

let aad = b"orders/1042";
let cipher_text: Encrypted<SecretValue, Bare> =
    cipher.encrypt_bare(aad, &SecretValue::from("secret"), &dek)?;
let decrypted: SecretValue = cipher.decrypt_bare(aad, &cipher_text, &dek)?;

assert_eq!(decrypted, SecretValue::from("secret"));
Ok(())
}

Full example available here.

encrypt writes an Encrypted<T, Randomized>: the same output after Tink’s TINK prefix, which names the key by a KeyId, see One fixed key.

generate_dek makes a DEK without a KMS. generate_encrypted_dek makes a DEK and encrypts it with a provider in one call, for an application that stores the encrypted DEK itself. The other calls of AeadCipher make no KMS call and open no span.

Cross-backend compatibility

Every backend writes the same bytes for the same algorithm, so a value sealed by one backend opens with any other that implements its algorithm. A writer on Ring and a reader on AwsLc work together, and moving an application from one backend to another needs no migration of its data:

use kms_aead::backends::{Ring, RustCrypto};
use kms_aead::formats::Bare;
use kms_aead::{AeadAlgorithm, AeadCipher, Encrypted};
use secret_vault_value::SecretValue;

fn main() -> kms_aead::KmsAeadResult<()> {
let writer = AeadCipher::<Ring>::new(AeadAlgorithm::Aes256Gcm)?;
let reader = AeadCipher::<RustCrypto>::new(AeadAlgorithm::Aes256Gcm)?;

let dek = writer.generate_dek()?;
let cipher_text: Encrypted<SecretValue, Bare> =
    writer.encrypt_bare(b"aad", &SecretValue::from("secret"), &dek)?;

let decrypted: SecretValue = reader.decrypt_bare(b"aad", &cipher_text, &dek)?;
assert_eq!(decrypted, SecretValue::from("secret"));
Ok(())
}

ring

ChaCha20-Poly1305, AES-128-GCM and AES-256-GCM. It has no AES-256-GCM-SIV. ring does not zeroise its expanded key schedules, so they stay in freed memory after each call.

aws-lc-rs and FIPS

aws-lc builds aws-lc-rs with every algorithm. It is the backend that adds no second crypto library to a build with a cloud provider, since the providers already build aws-lc-rs for TLS.

aws-lc-fips runs the same backend on the AWS-LC FIPS module, and builds only the FIPS module, so it does not need aws-lc. It needs CMake and Go to build. Be aware it moves every user of aws-lc-rs in the build to the FIPS module, the rustls aws-lc-rs provider included. A build with both features compiles both C libraries, and aws-lc-rs runs on the FIPS one. AES-256-GCM is the algorithm for FIPS deployments. AES-SIV, the deterministic algorithm, is not FIPS-approved, see Deterministic encryption.

aws-lc-rs copies the key into an AEAD context that AWS-LC allocates with its own allocator, which overwrites every allocation with zeros before freeing it. The expanded key schedule does not stay in freed memory after a call, as long as the application leaves AWS-LC’s allocator in place. The record keys AES-256-GCM-SIV derives for each call live on the stack, and AWS-LC does not clear them.

RustCrypto

Every algorithm, in pure Rust, with getrandom as the CSPRNG. It needs no C compiler. The feature turns on the crates’ zeroize features, so their key objects, and the Poly1305 and POLYVAL keys each call derives, are zeroised when dropped.