Files
dosh/src/crypto.rs
T
Codex 555d738a85
ci / test (push) Has been cancelled
ci / remote-bench (push) Has been cancelled
Initial Dosh implementation
2026-06-11 08:42:28 -04:00

97 lines
2.5 KiB
Rust

use anyhow::{Result, anyhow};
use chacha20poly1305::aead::{Aead, KeyInit, Payload};
use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce};
use hkdf::Hkdf;
use hmac::{Hmac, Mac};
use rand::RngCore;
use sha2::{Digest, Sha256};
pub type HmacSha256 = Hmac<Sha256>;
pub fn random_32() -> [u8; 32] {
let mut out = [0u8; 32];
rand::thread_rng().fill_bytes(&mut out);
out
}
pub fn random_16() -> [u8; 16] {
let mut out = [0u8; 16];
rand::thread_rng().fill_bytes(&mut out);
out
}
pub fn random_12() -> [u8; 12] {
let mut out = [0u8; 12];
rand::thread_rng().fill_bytes(&mut out);
out
}
pub fn hmac_sha256(key: &[u8], parts: &[&[u8]]) -> [u8; 32] {
let mut mac = <HmacSha256 as Mac>::new_from_slice(key).expect("HMAC accepts any key size");
for part in parts {
mac.update(part);
}
mac.finalize().into_bytes().into()
}
pub fn verify_hmac(key: &[u8], parts: &[&[u8]], expected: &[u8; 32]) -> bool {
let actual = hmac_sha256(key, parts);
constant_time_eq(&actual, expected)
}
pub fn sha256(data: &[u8]) -> [u8; 32] {
Sha256::digest(data).into()
}
pub fn hkdf32(secret: &[u8], salt: &[u8], info: &[u8]) -> Result<[u8; 32]> {
let hk = Hkdf::<Sha256>::new(Some(salt), secret);
let mut out = [0u8; 32];
hk.expand(info, &mut out)
.map_err(|_| anyhow!("HKDF expand failed"))?;
Ok(out)
}
pub fn nonce_from(direction: u32, seq: u64) -> [u8; 12] {
let mut nonce = [0u8; 12];
nonce[..4].copy_from_slice(&direction.to_be_bytes());
nonce[4..].copy_from_slice(&seq.to_be_bytes());
nonce
}
pub fn seal(key: &[u8; 32], nonce: &[u8; 12], aad: &[u8], plaintext: &[u8]) -> Result<Vec<u8>> {
let cipher = ChaCha20Poly1305::new(Key::from_slice(key));
cipher
.encrypt(
Nonce::from_slice(nonce),
Payload {
msg: plaintext,
aad,
},
)
.map_err(|_| anyhow!("encrypt failed"))
}
pub fn open(key: &[u8; 32], nonce: &[u8; 12], aad: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>> {
let cipher = ChaCha20Poly1305::new(Key::from_slice(key));
cipher
.decrypt(
Nonce::from_slice(nonce),
Payload {
msg: ciphertext,
aad,
},
)
.map_err(|_| anyhow!("decrypt failed"))
}
fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
if a.len() != b.len() {
return false;
}
let mut diff = 0u8;
for (x, y) in a.iter().zip(b.iter()) {
diff |= x ^ y;
}
diff == 0
}