#[cfg(test)]
mod tests;
use aes_gcm::aead::{Aead, KeyInit};
use aes_gcm::{Aes256Gcm, Nonce};
use hmac::{Hmac, Mac};
use rand_core::{OsRng, RngCore};
use sha2::{Digest, Sha256};
type HmacSha256 = Hmac<Sha256>;
pub fn signed_cookie(value: &str, secret: &[u8]) -> String {
let sig = hmac_sha256(secret, value.as_bytes());
format!("{}.{}", value, to_hex(&sig))
}
pub fn verify_signed_cookie(signed: &str, secret: &[u8]) -> Option<String> {
let (value, sig_hex) = signed.rsplit_once('.')?;
let given_sig = from_hex(sig_hex)?;
let mut mac = <HmacSha256 as Mac>::new_from_slice(secret).ok()?;
mac.update(value.as_bytes());
mac.verify_slice(&given_sig).ok()?;
Some(value.to_string())
}
pub fn encrypted_cookie(value: &str, key: &[u8]) -> String {
let cipher = Aes256Gcm::new_from_slice(&Sha256::digest(key))
.expect("SHA-256 digest is always 32 bytes, the size AES-256-GCM requires");
let mut nonce_bytes = [0u8; 12];
OsRng.fill_bytes(&mut nonce_bytes);
let nonce = Nonce::from_slice(&nonce_bytes);
let ciphertext = cipher
.encrypt(nonce, value.as_bytes())
.expect("AES-256-GCM encryption of a cookie-sized value cannot fail");
format!("{}.{}", to_hex(&nonce_bytes), to_hex(&ciphertext))
}
pub fn decrypt_cookie(encrypted: &str, key: &[u8]) -> Option<String> {
let (nonce_hex, ciphertext_hex) = encrypted.split_once('.')?;
let nonce_bytes = from_hex(nonce_hex)?;
if nonce_bytes.len() != 12 {
return None;
}
let ciphertext = from_hex(ciphertext_hex)?;
let cipher = Aes256Gcm::new_from_slice(&Sha256::digest(key)).ok()?;
let nonce = Nonce::from_slice(&nonce_bytes);
let plaintext = cipher.decrypt(nonce, ciphertext.as_ref()).ok()?;
String::from_utf8(plaintext).ok()
}
fn hmac_sha256(secret: &[u8], message: &[u8]) -> Vec<u8> {
let mut mac = <HmacSha256 as Mac>::new_from_slice(secret).expect("HMAC accepts any key size");
mac.update(message);
mac.finalize().into_bytes().to_vec()
}
fn to_hex(bytes: &[u8]) -> String {
bytes.iter().map(|b| format!("{:02x}", b)).collect()
}
fn from_hex(s: &str) -> Option<Vec<u8>> {
if s.is_empty() || s.len() % 2 != 0 {
return None;
}
(0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16).ok())
.collect()
}