Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Cipher CLI example for CBC/CTR API #142

Merged
merged 2 commits into from
Jun 6, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion aws-lc-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
242 changes: 242 additions & 0 deletions aws-lc-rs/examples/cipher.rs
Original file line number Diff line number Diff line change
@@ -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<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 = 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<u8>,
iv: Option<String>,
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<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 =
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<u8>,
iv: Option<String>,
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<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 =
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<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.as_ref()).map_err(|_| "failed to construct aes key")
}