Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cas-lib"
version = "0.2.87"
version = "0.2.88"
edition = "2021"
description = "A function wrapper layer for RustCrypto and Dalek-Cryptography. Intended to be used in FFI situations with a global heap deallactor at the top level project."
license = "Apache-2.0"
Expand Down
40 changes: 40 additions & 0 deletions docs/EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,46 @@ fn main() {
}
```

### AES GCM Mode with AAD

Both AES types also implement the `CASAES256AadEncryption` / `CASAES128AadEncryption`
traits, which take additional authenticated data (AAD). The AAD is not encrypted, but
it is bound to the authentication tag: decryption only succeeds when the same AAD is
supplied, so any tampering with it is detected.

```rust
use cas_lib::symmetric::{
aes::CASAES256,
cas_symmetric_encryption::{CASAES256AadEncryption, CASAES256Encryption},
};

fn main() {
let plaintext: Vec<u8> = std::fs::read("MikeMulchrone_Resume2024.docx").unwrap();
let aad = b"file-id:1234".to_vec();

let aes_nonce = <CASAES256 as CASAES256Encryption>::generate_nonce();
let aes_key = <CASAES256 as CASAES256Encryption>::generate_key();
let encrypted_bytes = <CASAES256 as CASAES256AadEncryption>::encrypt_plaintext_with_aad(
aes_key.clone(),
aes_nonce.clone(),
plaintext.clone(),
aad.clone(),
)
.unwrap();

// Decryption requires the same AAD; a different AAD returns an error.
let decrypted_bytes = <CASAES256 as CASAES256AadEncryption>::decrypt_ciphertext_with_aad(
aes_key,
aes_nonce,
encrypted_bytes,
aad,
)
.unwrap();

assert_eq!(plaintext, decrypted_bytes);
}
```

### ChaCha20-Poly1305
```rust
use std::{fs::File, io::Write, path::Path};
Expand Down
76 changes: 74 additions & 2 deletions src/symmetric/aes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ use hkdf::Hkdf;
use rand::{RngCore, rngs::OsRng};

use aes_gcm::{
aead::{Aead},
aead::{Aead, Payload},
Aes128Gcm, Aes256Gcm, KeyInit, Nonce,
};
use sha2::Sha256;

use crate::error::{CasError, CasResult};

use super::cas_symmetric_encryption::{CASAES128Encryption, CASAES256Encryption};
use super::cas_symmetric_encryption::{
CASAES128AadEncryption, CASAES128Encryption, CASAES256AadEncryption, CASAES256Encryption,
};

const AES_NONCE_LEN: usize = 12;
const AES128_KEY_LEN: usize = 16;
Expand Down Expand Up @@ -91,6 +93,76 @@ impl CASAES256Encryption for CASAES256 {
}
}

impl CASAES256AadEncryption for CASAES256 {

/// Encrypts with AES-256-GCM taking an aes_key, aes_nonce and additional authenticated data
fn encrypt_plaintext_with_aad(aes_key: Vec<u8>, nonce: Vec<u8>, plaintext: Vec<u8>, aad: Vec<u8>) -> CasResult<Vec<u8>> {
if aes_key.len() != AES256_KEY_LEN {
return Err(CasError::InvalidKey);
}
if nonce.len() != AES_NONCE_LEN {
return Err(CasError::InvalidNonce);
}
let key = Key::<Aes256Gcm>::from_slice(aes_key.as_slice());
let cipher = Aes256Gcm::new(key);
let nonce = Nonce::from_slice(nonce.as_slice());
cipher
.encrypt(nonce, Payload { msg: plaintext.as_slice(), aad: aad.as_slice() })
.map_err(|_| CasError::EncryptionFailed)
}

/// Decrypts with AES-256-GCM taking an aes_key, aes_nonce and additional authenticated data
fn decrypt_ciphertext_with_aad(aes_key: Vec<u8>, nonce: Vec<u8>, ciphertext: Vec<u8>, aad: Vec<u8>) -> CasResult<Vec<u8>> {
if aes_key.len() != AES256_KEY_LEN {
return Err(CasError::InvalidKey);
}
if nonce.len() != AES_NONCE_LEN {
return Err(CasError::InvalidNonce);
}
let key = Key::<Aes256Gcm>::from_slice(aes_key.as_slice());
let cipher = Aes256Gcm::new(key);
let nonce = Nonce::from_slice(nonce.as_slice());
cipher
.decrypt(nonce, Payload { msg: ciphertext.as_slice(), aad: aad.as_slice() })
.map_err(|_| CasError::DecryptionFailed)
}
}

impl CASAES128AadEncryption for CASAES128 {

/// Encrypts with AES-128-GCM taking an aes_key, aes_nonce and additional authenticated data
fn encrypt_plaintext_with_aad(aes_key: Vec<u8>, nonce: Vec<u8>, plaintext: Vec<u8>, aad: Vec<u8>) -> CasResult<Vec<u8>> {
if aes_key.len() != AES128_KEY_LEN {
return Err(CasError::InvalidKey);
}
if nonce.len() != AES_NONCE_LEN {
return Err(CasError::InvalidNonce);
}
let key = Key::<Aes128Gcm>::from_slice(aes_key.as_slice());
let cipher = Aes128Gcm::new(key);
let nonce = Nonce::from_slice(nonce.as_slice());
cipher
.encrypt(nonce, Payload { msg: plaintext.as_slice(), aad: aad.as_slice() })
.map_err(|_| CasError::EncryptionFailed)
}

/// Decrypts with AES-128-GCM taking an aes_key, aes_nonce and additional authenticated data
fn decrypt_ciphertext_with_aad(aes_key: Vec<u8>, nonce: Vec<u8>, ciphertext: Vec<u8>, aad: Vec<u8>) -> CasResult<Vec<u8>> {
if aes_key.len() != AES128_KEY_LEN {
return Err(CasError::InvalidKey);
}
if nonce.len() != AES_NONCE_LEN {
return Err(CasError::InvalidNonce);
}
let key = Key::<Aes128Gcm>::from_slice(aes_key.as_slice());
let cipher = Aes128Gcm::new(key);
let nonce = Nonce::from_slice(nonce.as_slice());
cipher
.decrypt(nonce, Payload { msg: ciphertext.as_slice(), aad: aad.as_slice() })
.map_err(|_| CasError::DecryptionFailed)
}
}

impl CASAES128Encryption for CASAES128 {

/// Generates an AES128 key from a vector.
Expand Down
10 changes: 10 additions & 0 deletions src/symmetric/cas_symmetric_encryption.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ pub trait CASAES128Encryption {
fn key_from_vec(key_slice: Vec<u8>) -> CasResult<Vec<u8>>;
}

pub trait CASAES256AadEncryption {
fn encrypt_plaintext_with_aad(aes_key: Vec<u8>, nonce: Vec<u8>, plaintext: Vec<u8>, aad: Vec<u8>) -> CasResult<Vec<u8>>;
fn decrypt_ciphertext_with_aad(aes_key: Vec<u8>, nonce: Vec<u8>, ciphertext: Vec<u8>, aad: Vec<u8>) -> CasResult<Vec<u8>>;
}

pub trait CASAES128AadEncryption {
fn encrypt_plaintext_with_aad(aes_key: Vec<u8>, nonce: Vec<u8>, plaintext: Vec<u8>, aad: Vec<u8>) -> CasResult<Vec<u8>>;
fn decrypt_ciphertext_with_aad(aes_key: Vec<u8>, nonce: Vec<u8>, ciphertext: Vec<u8>, aad: Vec<u8>) -> CasResult<Vec<u8>>;
}

pub trait CASAES256SIVEncryption {
fn generate_key() -> Vec<u8>;
fn encrypt_plaintext(aes_key: Vec<u8>, nonce: Vec<u8>, plaintext: Vec<u8>) -> CasResult<Vec<u8>>;
Expand Down
Loading
Loading