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
use orion::aead::SecretKey;
use super::*;
use anyhow::Context;
fn password_to_key(pw: &str, salt: &[u8]) -> Result<SecretKey> {
use orion::hazardous::stream::chacha20::CHACHA_KEYSIZE;
use orion::kdf::{derive_key, Password, Salt};
let pw = Password::from_slice(pw.as_bytes()).with_context(|| "Password is too short")?;
let salt = Salt::from_slice(salt).with_context(|| "Salt is too short")?;
let kdf_key = derive_key(&pw, &salt, 15, 1024, CHACHA_KEYSIZE as u32)
.with_context(|| "Could not derive key from password")?;
let key = SecretKey::from_slice(kdf_key.unprotected_as_bytes())
.with_context(|| "Could not convert key")?;
Ok(key)
}
pub fn nonce() -> Result<[u8; 24]> {
let mut result = [0u8; 24];
getrandom::getrandom(&mut result)?;
Ok(result)
}
pub fn encrypt(
plaintext: impl AsRef<[u8]>, pw: impl AsRef<str>, nonce: impl AsRef<[u8]>,
) -> Result<Vec<u8>> {
use orion::hazardous::{
aead::xchacha20poly1305::{seal, Nonce, SecretKey as XSecretKey},
mac::poly1305::POLY1305_OUTSIZE,
stream::xchacha20::XCHACHA_NONCESIZE,
};
let plaintext = plaintext.as_ref();
let pw = pw.as_ref();
let nonce = nonce.as_ref();
let key = password_to_key(pw, nonce)?;
let key =
XSecretKey::from_slice(key.unprotected_as_bytes()).with_context(|| "Key is invalid")?;
let nonce = Nonce::from_slice(nonce).with_context(|| "Nonce is too short")?;
let out_len = match plaintext.len().checked_add(XCHACHA_NONCESIZE + POLY1305_OUTSIZE) {
Some(min_out_len) => min_out_len,
None => bail!("Plaintext is too long"),
};
let mut output = vec![0u8; out_len];
output[..XCHACHA_NONCESIZE].copy_from_slice(nonce.as_ref());
seal(&key, &nonce, plaintext, None, &mut output[XCHACHA_NONCESIZE..])
.with_context(|| "Could not convert key")?;
Ok(output)
}
pub fn decrypt(ciphertext: impl AsRef<[u8]>, pw: impl AsRef<str>) -> Result<Vec<u8>> {
use orion::aead::open;
use orion::hazardous::stream::xchacha20::XCHACHA_NONCESIZE;
let ciphertext = ciphertext.as_ref();
let pw = pw.as_ref();
ensure!(ciphertext.len() > XCHACHA_NONCESIZE, "Ciphertext is too short");
let key = password_to_key(pw, &ciphertext[..XCHACHA_NONCESIZE])?;
open(&key, ciphertext).with_context(|| "Ciphertext was tampered with")
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn encrypt_decrypt_roundtrip() -> Result<()> {
let nonce = nonce()?;
let password = "password123";
let message = "Be at the big tree at 5pm tomorrow!";
let plaintext = message.as_bytes().to_owned();
let ciphertext = encrypt(&plaintext, password, &nonce)?;
let plaintext2 = decrypt(&ciphertext, password)?;
assert_eq!(&plaintext2, &plaintext);
assert_eq!(message.len(), 35);
assert_eq!(ciphertext.len(), 35 + 40);
Ok(())
}
}