Skip to content

Commit

Permalink
Add Cipher CLI example for CBC/CTR API (aws#142)
Browse files Browse the repository at this point in the history
* Add Cipher CLI to Examples

* Make Clippy Happy
  • Loading branch information
skmcgrail authored and justsmth committed Jun 15, 2023
1 parent 07ec163 commit e1a993e
Show file tree
Hide file tree
Showing 2 changed files with 235 additions and 1 deletion.
3 changes: 2 additions & 1 deletion aws-lc-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,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"
Expand Down
233 changes: 233 additions & 0 deletions aws-lc-rs/examples/cipher.rs
Original file line number Diff line number Diff line change
@@ -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<String>,

#[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<String>,
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<String>, 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<String>, 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<UnboundCipherKey, &'static str> {
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")
}

0 comments on commit e1a993e

Please sign in to comment.