From c2b079c70479c55fe8540ecfbfeb4025b9e723c6 Mon Sep 17 00:00:00 2001 From: Sean McGrail Date: Tue, 6 Jun 2023 11:58:47 -0700 Subject: [PATCH 1/2] Add Cipher CLI to Examples --- aws-lc-rs/Cargo.toml | 3 +- aws-lc-rs/examples/cipher.rs | 242 +++++++++++++++++++++++++++++++++++ 2 files changed, 244 insertions(+), 1 deletion(-) create mode 100644 aws-lc-rs/examples/cipher.rs 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..eae83a31199 --- /dev/null +++ b/aws-lc-rs/examples/cipher.rs @@ -0,0 +1,242 @@ +// 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 = match cli.key { + Some(key) => match hex::decode(key) { + Ok(v) => v, + Err(..) => { + return Err("invalid key"); + } + }, + None => { + 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: Vec, + iv: Option, + plaintext: String, +) -> Result<(), &'static str> { + let hex_key = hex::encode(key.as_slice()); + 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: Vec, 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 = + Vec::from(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: Vec, + iv: Option, + plaintext: String, +) -> Result<(), &'static str> { + let hex_key = hex::encode(key.as_slice()); + 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: Vec, 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 = + Vec::from(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: Vec) -> Result { + let alg = match key.len() { + 16 => &AES_128, + 32 => &AES_256, + _ => { + return Err("invalid aes key length"); + } + }; + + UnboundCipherKey::new(alg, key.as_ref()).map_err(|_| "failed to construct aes key") +} From 553d55be7eca0812d8fed317e91d494b7cc3856a Mon Sep 17 00:00:00 2001 From: Sean McGrail Date: Tue, 6 Jun 2023 13:48:22 -0700 Subject: [PATCH 2/2] Make Clippy Happy --- aws-lc-rs/examples/cipher.rs | 57 +++++++++++++++--------------------- 1 file changed, 24 insertions(+), 33 deletions(-) diff --git a/aws-lc-rs/examples/cipher.rs b/aws-lc-rs/examples/cipher.rs index eae83a31199..3efad0ebb04 100644 --- a/aws-lc-rs/examples/cipher.rs +++ b/aws-lc-rs/examples/cipher.rs @@ -52,8 +52,8 @@ struct Cli { #[derive(ValueEnum, Clone, Copy)] enum Mode { - CTR, - CBC, + Ctr, + Cbc, } #[derive(Subcommand)] @@ -73,36 +73,31 @@ enum Commands { fn main() -> Result<(), &'static str> { let cli = Cli::parse(); - let key = match cli.key { - Some(key) => match hex::decode(key) { + let key = if let Some(key) = cli.key { + match hex::decode(key) { Ok(v) => v, Err(..) => { return Err("invalid key"); } - }, - None => { - let mut v = vec![0u8; 16]; - aws_lc_rs::rand::fill(v.as_mut_slice()).map_err(|_| "failed to generate key")?; - v } + } 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), + (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: Vec, - iv: Option, - plaintext: String, -) -> Result<(), &'static str> { - let hex_key = hex::encode(key.as_slice()); +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 { @@ -130,14 +125,14 @@ fn aes_ctr_encrypt( let ciphertext = hex::encode(ciphertext.as_slice()); - println!("key: {}", hex_key); + println!("key: {hex_key}"); println!("iv: {}", hex::encode(iv)); println!("ciphertext: {ciphertext}"); Ok(()) } -fn aes_ctr_decrypt(key: Vec, iv: String, ciphertext: String) -> Result<(), &'static str> { +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")?; @@ -149,7 +144,7 @@ fn aes_ctr_decrypt(key: Vec, iv: String, ciphertext: String) -> Result<(), & .map_err(|_| "failed to initalized aes decryption")?; let mut ciphertext = - Vec::from(hex::decode(ciphertext).map_err(|_| "ciphertext is not valid hex encoding")?); + hex::decode(ciphertext).map_err(|_| "ciphertext is not valid hex encoding")?; let plaintext = key .decrypt(ciphertext.as_mut()) @@ -163,12 +158,8 @@ fn aes_ctr_decrypt(key: Vec, iv: String, ciphertext: String) -> Result<(), & Ok(()) } -fn aes_cbc_encrypt( - key: Vec, - iv: Option, - plaintext: String, -) -> Result<(), &'static str> { - let hex_key = hex::encode(key.as_slice()); +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 { @@ -196,14 +187,14 @@ fn aes_cbc_encrypt( let ciphertext = hex::encode(ciphertext.as_slice()); - println!("key: {}", hex_key); + println!("key: {hex_key}"); println!("iv: {}", hex::encode(iv)); println!("ciphertext: {ciphertext}"); Ok(()) } -fn aes_cbc_decrypt(key: Vec, iv: String, ciphertext: String) -> Result<(), &'static str> { +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")?; @@ -215,7 +206,7 @@ fn aes_cbc_decrypt(key: Vec, iv: String, ciphertext: String) -> Result<(), & .map_err(|_| "failed to initalized aes decryption")?; let mut ciphertext = - Vec::from(hex::decode(ciphertext).map_err(|_| "ciphertext is not valid hex encoding")?); + hex::decode(ciphertext).map_err(|_| "ciphertext is not valid hex encoding")?; let plaintext = key .decrypt(ciphertext.as_mut()) @@ -229,7 +220,7 @@ fn aes_cbc_decrypt(key: Vec, iv: String, ciphertext: String) -> Result<(), & Ok(()) } -fn new_unbound_key(key: Vec) -> Result { +fn new_unbound_key(key: &[u8]) -> Result { let alg = match key.len() { 16 => &AES_128, 32 => &AES_256, @@ -238,5 +229,5 @@ fn new_unbound_key(key: Vec) -> Result { } }; - UnboundCipherKey::new(alg, key.as_ref()).map_err(|_| "failed to construct aes key") + UnboundCipherKey::new(alg, key).map_err(|_| "failed to construct aes key") }