diff --git a/aws-lc-rs/Cargo.toml b/aws-lc-rs/Cargo.toml index 38a7662d55d..5ab5a5ba772 100644 --- a/aws-lc-rs/Cargo.toml +++ b/aws-lc-rs/Cargo.toml @@ -37,12 +37,13 @@ mirai-annotations = "1.12.0" [dev-dependencies] paste = "1.0" -criterion = { version = "0.5.0", features = ["csv_output"]} +criterion = { version = "0.5.0", features = ["csv_output"] } ring = "0.16" regex = "1.6.0" lazy_static = "1.4.0" clap = { version = "4.1.8", features = ["derive"] } openssl = { version = "0.10.52", features = ["vendored"] } +hex = "0.4.3" [[bench]] name = "aead_benchmark" diff --git a/aws-lc-rs/examples/cipher.rs b/aws-lc-rs/examples/cipher.rs new file mode 100644 index 00000000000..3efad0ebb04 --- /dev/null +++ b/aws-lc-rs/examples/cipher.rs @@ -0,0 +1,233 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 OR ISC + +//! cipher - Perform symmetric cipher encryption/decryption on utf8 plaintext. +//! +//! *cipher* is an example program demonstrating the `aws_lc_rs::cipher` API for *aws-lc-rs*. +//! It demonstrates CTR & CBC mode encryption using AES 128 or 256 bit keys. +//! +//! The program can be run from the command line using cargo: +//! ```sh +//! $ cargo run --example cipher -- --mode ctr encrypt "Hello World" +//! key: b331133eb742497c67ced9520c9a7de3 +//! iv: 4e967c7b799e0670431888e2e959e154 +//! ciphertext: 88bcbd8d1656d60de739c5 +//! +//! $ cargo run --example cipher -- --mode ctr --key b331133eb742497c67ced9520c9a7de3 decrypt --iv 4e967c7b799e0670431888e2e959e154 88bcbd8d1656d60de739c5 +//! Hello World +//! +//! $ cargo run --example cipher -- --mode cbc encrypt "Hello World" +//! key: 6489d8ce0c4facf18b872705a05d5ee4 +//! iv: 5cd56fb752830ec2459889226c5431bd +//! ciphertext: 6311c14e8104730be124ce1e57e51fe3 +//! +//! $ cargo run --example cipher -- --mode cbc --key 6489d8ce0c4facf18b872705a05d5ee4 decrypt --iv 5cd56fb752830ec2459889226c5431bd 6311c14e8104730be124ce1e57e51fe3 +//! Hello World +//! ``` +use aws_lc_rs::{ + cipher::{ + CipherContext, DecryptingKey, EncryptingKey, PaddedBlockDecryptingKey, + PaddedBlockEncryptingKey, UnboundCipherKey, AES_128, AES_256, + }, + iv::FixedLength, +}; +use clap::{Parser, Subcommand, ValueEnum}; + +#[derive(Parser)] +#[command(author, version, name = "cipher")] +struct Cli { + #[arg( + short, + long, + help = "AES 128 or 256 bit key in hex, if not provided defaults to 128" + )] + key: Option, + + #[arg(short, long, value_enum, help = "AES cipher mode")] + mode: Mode, + + #[command(subcommand)] + command: Commands, +} + +#[derive(ValueEnum, Clone, Copy)] +enum Mode { + Ctr, + Cbc, +} + +#[derive(Subcommand)] +enum Commands { + Encrypt { + #[arg(short, long, help = "Initalization Vector (IV)")] + iv: Option, + plaintext: String, + }, + Decrypt { + #[arg(short, long, help = "Initalization Vector (IV)")] + iv: String, + ciphertext: String, + }, +} + +fn main() -> Result<(), &'static str> { + let cli = Cli::parse(); + + let key = if let Some(key) = cli.key { + match hex::decode(key) { + Ok(v) => v, + Err(..) => { + return Err("invalid key"); + } + } + } else { + let mut v = vec![0u8; 16]; + aws_lc_rs::rand::fill(v.as_mut_slice()).map_err(|_| "failed to generate key")?; + v + }; + + match (cli.command, cli.mode) { + (Commands::Encrypt { iv, plaintext }, Mode::Ctr) => aes_ctr_encrypt(&key, iv, plaintext), + (Commands::Encrypt { iv, plaintext }, Mode::Cbc) => aes_cbc_encrypt(&key, iv, plaintext), + (Commands::Decrypt { iv, ciphertext }, Mode::Ctr) => aes_ctr_decrypt(&key, iv, ciphertext), + (Commands::Decrypt { iv, ciphertext }, Mode::Cbc) => aes_cbc_decrypt(&key, iv, ciphertext), + }?; + + Ok(()) +} + +fn aes_ctr_encrypt(key: &[u8], iv: Option, plaintext: String) -> Result<(), &'static str> { + let hex_key = hex::encode(key); + let key = new_unbound_key(key)?; + + let key = match iv { + Some(iv) => { + let iv = { + let v = hex::decode(iv).map_err(|_| "invalid iv")?; + let v: FixedLength<16> = v.as_slice().try_into().map_err(|_| "invalid iv")?; + v + }; + EncryptingKey::less_safe_ctr(key, CipherContext::Iv128(iv)) + } + None => EncryptingKey::ctr(key), + } + .map_err(|_| "failed to initalized aes encryption")?; + + let mut ciphertext = Vec::from(plaintext); + + let context = key + .encrypt(ciphertext.as_mut()) + .map_err(|_| "Failed to encrypt plaintext")?; + + let iv: &[u8] = (&context) + .try_into() + .map_err(|_| "unexpected encryption context")?; + + let ciphertext = hex::encode(ciphertext.as_slice()); + + println!("key: {hex_key}"); + println!("iv: {}", hex::encode(iv)); + println!("ciphertext: {ciphertext}"); + + Ok(()) +} + +fn aes_ctr_decrypt(key: &[u8], iv: String, ciphertext: String) -> Result<(), &'static str> { + let key = new_unbound_key(key)?; + let iv = { + let v = hex::decode(iv).map_err(|_| "invalid iv")?; + let v: FixedLength<16> = v.as_slice().try_into().map_err(|_| "invalid iv")?; + v + }; + + let key = DecryptingKey::ctr(key, CipherContext::Iv128(iv)) + .map_err(|_| "failed to initalized aes decryption")?; + + let mut ciphertext = + hex::decode(ciphertext).map_err(|_| "ciphertext is not valid hex encoding")?; + + let plaintext = key + .decrypt(ciphertext.as_mut()) + .map_err(|_| "failed to decrypt ciphertext")?; + + let plaintext = + String::from_utf8(plaintext.into()).map_err(|_| "decrypted text was not a utf8 string")?; + + println!("{plaintext}"); + + Ok(()) +} + +fn aes_cbc_encrypt(key: &[u8], iv: Option, plaintext: String) -> Result<(), &'static str> { + let hex_key = hex::encode(key); + let key = new_unbound_key(key)?; + + let key = match iv { + Some(iv) => { + let iv = { + let v = hex::decode(iv).map_err(|_| "invalid iv")?; + let v: FixedLength<16> = v.as_slice().try_into().map_err(|_| "invalid iv")?; + v + }; + PaddedBlockEncryptingKey::less_safe_cbc_pkcs7(key, CipherContext::Iv128(iv)) + } + None => PaddedBlockEncryptingKey::cbc_pkcs7(key), + } + .map_err(|_| "failed to initalized aes encryption")?; + + let mut ciphertext = Vec::from(plaintext); + + let context = key + .encrypt(&mut ciphertext) + .map_err(|_| "Failed to encrypt plaintext")?; + + let iv: &[u8] = (&context) + .try_into() + .map_err(|_| "unexpected encryption context")?; + + let ciphertext = hex::encode(ciphertext.as_slice()); + + println!("key: {hex_key}"); + println!("iv: {}", hex::encode(iv)); + println!("ciphertext: {ciphertext}"); + + Ok(()) +} + +fn aes_cbc_decrypt(key: &[u8], iv: String, ciphertext: String) -> Result<(), &'static str> { + let key = new_unbound_key(key)?; + let iv = { + let v = hex::decode(iv).map_err(|_| "invalid iv")?; + let v: FixedLength<16> = v.as_slice().try_into().map_err(|_| "invalid iv")?; + v + }; + + let key = PaddedBlockDecryptingKey::cbc_pkcs7(key, CipherContext::Iv128(iv)) + .map_err(|_| "failed to initalized aes decryption")?; + + let mut ciphertext = + hex::decode(ciphertext).map_err(|_| "ciphertext is not valid hex encoding")?; + + let plaintext = key + .decrypt(ciphertext.as_mut()) + .map_err(|_| "failed to decrypt ciphertext")?; + + let plaintext = + String::from_utf8(plaintext.into()).map_err(|_| "decrypted text was not a utf8 string")?; + + println!("{plaintext}"); + + Ok(()) +} + +fn new_unbound_key(key: &[u8]) -> Result { + let alg = match key.len() { + 16 => &AES_128, + 32 => &AES_256, + _ => { + return Err("invalid aes key length"); + } + }; + + UnboundCipherKey::new(alg, key).map_err(|_| "failed to construct aes key") +}