1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
use crate::prelude::*;
use aes_gcm::{
Key,
aead::{Aead, AeadCore, KeyInit, OsRng},
};
/// AES GCM 256 encryption
#[derive(Clone, Default, PartialEq, Eq, Hash, derive_more::Display, derive_more::Debug)]
pub struct AesGcm256;
impl AesGcm256 {
/// Seals the plaintext using AES GCM 256 encryption with the provided encryption key.
/// Returns an `AesGcmSealedBox` containing the encrypted data and nonce.
///
/// # Arguments
/// * `plaintext` - The data to encrypt.
/// * `encryption_key` - The key used for encryption, must be 32 bytes long.
///
/// # Returns
/// An `AesGcmSealedBox` containing the encrypted data and nonce.
fn _seal(
plaintext: impl AsRef<[u8]>,
encryption_key: Key<aes_gcm::Aes256Gcm>,
) -> AesGcmSealedBox {
let cipher = aes_gcm::Aes256Gcm::new(&encryption_key);
let nonce = aes_gcm::Aes256Gcm::generate_nonce(&mut OsRng); // 12 bytes; unique per message
let cipher_text = cipher
.encrypt(&nonce, plaintext.as_ref())
.expect("AES encrypt never fails for valid nonce.");
let nonce = AesNonce::try_from(nonce.as_slice()).unwrap();
AesGcmSealedBox::builder()
.nonce(nonce)
.cipher_text(cipher_text)
.build()
}
/// Opens the sealed box by decrypting the cipher text using the provided decryption key.
/// Returns the decrypted plaintext.
/// # Arguments
/// * `sealed_box` - The sealed box containing the encrypted data and nonce.
/// * `decryption_key` - The key used for decryption, must be 32 bytes long.
fn _open(
sealed_box: AesGcmSealedBox,
decryption_key: Key<aes_gcm::Aes256Gcm>,
) -> Result<Vec<u8>> {
let cipher = aes_gcm::Aes256Gcm::new(&decryption_key);
let cipher_text = sealed_box.cipher_text();
cipher
.decrypt(sealed_box.nonce().into(), cipher_text.as_ref())
.map_err(|e| {
error!("Failed to AES decrypt data - error: {:?}", e);
Error::AESDecryptionFailed
})
}
}
impl AesGcm256 {
/// Seals the plaintext using AES GCM 256 encryption with the provided encryption key.
/// Returns an `AesGcmSealedBox` containing the encrypted data and nonce
/// # Arguments
/// * `plaintext` - The data to encrypt.
/// * `encryption_key` - The key used for encryption, must be 32 bytes long.
/// # Returns
/// An `AesGcmSealedBox` containing the encrypted data and nonce.
///
/// # Examples
/// ```
/// extern crate klirr_core;
/// use klirr_core::prelude::*;
/// let secret = "super secret data";
/// let encryption_key = EncryptionKey([0xabu8; 32]);
/// // Seal the plaintext
/// let encrypted = AesGcm256::seal(secret.as_bytes(), encryption_key.clone());
/// let decrypted = AesGcm256::open(encrypted, encryption_key).unwrap();
/// assert_eq!(decrypted, secret.as_bytes());
/// ```
pub fn seal(plaintext: impl AsRef<[u8]>, encryption_key: EncryptionKey) -> AesGcmSealedBox {
Self::_seal(plaintext, encryption_key.into())
}
/// Opens the sealed box by decrypting the cipher text using the provided decryption key.
/// Returns the decrypted plaintext.
/// # Arguments
/// * `sealed_box` - The sealed box containing the encrypted data and nonce.
/// * `decryption_key` - The key used for decryption, must be 32 bytes long.
/// # Returns
/// The decrypted plaintext as a `Vec<u8>`.
///
/// # Examples
/// ```
/// extern crate klirr_core;
/// use klirr_core::prelude::*;
/// let secret = "super secret data";
/// let encryption_key = EncryptionKey([0xabu8; 32]);
/// // Seal the plaintext
/// let encrypted = AesGcm256::seal(secret.as_bytes(), encryption_key.clone());
/// let decrypted = AesGcm256::open(encrypted, encryption_key).unwrap();
/// assert_eq!(decrypted, secret.as_bytes());
/// ```
pub fn open(sealed_box: AesGcmSealedBox, decryption_key: EncryptionKey) -> Result<Vec<u8>> {
Self::_open(sealed_box, decryption_key.into())
}
}
#[cfg(test)]
mod tests {
use super::*;
use hex::decode as hex_decode;
type Sut = AesGcm256;
fn sample_encryption_key() -> EncryptionKey {
EncryptionKey([0xabu8; 32])
}
#[test]
fn test_decrypt() {
let combined = hex_decode("ae8e7654ded1c276d5c428b10bef17f2a3b885e156a853e781fabe219fa19e5780c1a57a51a58c7384e69545da6a83bf4f").unwrap();
let sealed_box = AesGcmSealedBox::try_from(combined.as_slice()).unwrap();
let decrypted = Sut::open(sealed_box, sample_encryption_key()).unwrap();
let decrypted_str = String::from_utf8(decrypted).unwrap();
assert_eq!(decrypted_str, "yay decryption worked");
}
#[test]
fn test_roundtrip() {
let plaintext = "so super secret".to_owned();
let encryption_key = sample_encryption_key();
let sealed = Sut::seal(plaintext.clone(), encryption_key.clone());
let decrypted = Sut::open(sealed.clone(), encryption_key).unwrap();
let decrypted_str = String::from_utf8(decrypted).unwrap();
assert_eq!(plaintext, decrypted_str);
}
#[test]
fn test_fail() {
assert_eq!(
Sut::open(
AesGcmSealedBox::builder()
.nonce([0xabu8; 12])
.cipher_text(hex_decode("deadbeef").unwrap())
.build(),
sample_encryption_key()
),
Err(Error::AESDecryptionFailed)
)
}
}