brokkr-broker::utils::encryption Rust
Encryption utilities for protecting sensitive data at rest.
This module provides AES-256-GCM encryption and decryption functionality for webhook URLs and authentication headers stored in the database.
Structs
brokkr-broker::utils::encryption::EncryptionKey
pub
Encryption key wrapper with AES-256-GCM cipher.
Fields
| Name | Type | Description |
|---|---|---|
key | [u8 ; 32] | The raw 32-byte key. |
cipher | Aes256Gcm | Pre-initialized AES-256-GCM cipher |
Methods
new pub
#![allow(unused)]
fn main() {
fn new (key : [u8 ; 32]) -> Self
}
Creates a new encryption key from raw bytes.
Source
#![allow(unused)]
fn main() {
pub fn new(key: [u8; 32]) -> Self {
let cipher = Aes256Gcm::new_from_slice(&key).expect("valid key size");
Self { key, cipher }
}
}
generate pub
#![allow(unused)]
fn main() {
fn generate () -> Self
}
Creates a new random encryption key.
Source
#![allow(unused)]
fn main() {
pub fn generate() -> Self {
let mut key = [0u8; 32];
rand::thread_rng().fill_bytes(&mut key);
Self::new(key)
}
}
from_hex pub
#![allow(unused)]
fn main() {
fn from_hex (hex : & str) -> Result < Self , String >
}
Creates a key from a hex-encoded string.
Source
#![allow(unused)]
fn main() {
pub fn from_hex(hex: &str) -> Result<Self, String> {
let bytes = hex::decode(hex).map_err(|e| format!("Invalid hex encoding: {}", e))?;
if bytes.len() != 32 {
return Err(format!("Key must be 32 bytes, got {} bytes", bytes.len()));
}
let mut key = [0u8; 32];
key.copy_from_slice(&bytes);
Ok(Self::new(key))
}
}
fingerprint pub
#![allow(unused)]
fn main() {
fn fingerprint (& self) -> String
}
Returns the key as a hex string (for logging key fingerprint only).
Source
#![allow(unused)]
fn main() {
pub fn fingerprint(&self) -> String {
let hash = Sha256::digest(self.key);
hex::encode(&hash[..8])
}
}
encrypt pub
#![allow(unused)]
fn main() {
fn encrypt (& self , plaintext : & [u8]) -> Result < Vec < u8 > , EncryptionError >
}
Encrypts data using AES-256-GCM.
Source
#![allow(unused)]
fn main() {
pub fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>, EncryptionError> {
// Generate random nonce
let mut nonce_bytes = [0u8; AES_GCM_NONCE_SIZE];
rand::thread_rng().fill_bytes(&mut nonce_bytes);
let nonce = Nonce::from_slice(&nonce_bytes);
// Encrypt with AES-256-GCM
let ciphertext = self
.cipher
.encrypt(nonce, plaintext)
.map_err(|_| EncryptionError::EncryptionFailed)?;
// Build output: version || nonce || ciphertext (includes auth tag)
let mut output = Vec::with_capacity(1 + AES_GCM_NONCE_SIZE + ciphertext.len());
output.push(VERSION_AES_GCM);
output.extend_from_slice(&nonce_bytes);
output.extend(ciphertext);
Ok(output)
}
}
decrypt pub
#![allow(unused)]
fn main() {
fn decrypt (& self , data : & [u8]) -> Result < Vec < u8 > , EncryptionError >
}
Decrypts data, automatically detecting the encryption version.
Supports:
- Version 0x01: AES-256-GCM
- Version 0x00 or no version byte: Legacy XOR (for migration)
Source
#![allow(unused)]
fn main() {
pub fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, EncryptionError> {
if data.is_empty() {
return Err(EncryptionError::InvalidData("Empty data".to_string()));
}
// Check version byte
let version = data[0];
match version {
VERSION_AES_GCM => self.decrypt_aes_gcm(&data[1..]),
VERSION_LEGACY_XOR => self.decrypt_legacy_xor(&data[1..]),
_ => {
// No version byte - assume legacy XOR format
// Legacy format: nonce (16 bytes) || ciphertext
if data.len() >= LEGACY_XOR_NONCE_SIZE {
self.decrypt_legacy_xor(data)
} else {
Err(EncryptionError::InvalidData("Data too short".to_string()))
}
}
}
}
}
decrypt_aes_gcm private
#![allow(unused)]
fn main() {
fn decrypt_aes_gcm (& self , data : & [u8]) -> Result < Vec < u8 > , EncryptionError >
}
Decrypts AES-256-GCM encrypted data.
Source
#![allow(unused)]
fn main() {
fn decrypt_aes_gcm(&self, data: &[u8]) -> Result<Vec<u8>, EncryptionError> {
if data.len() < AES_GCM_NONCE_SIZE {
return Err(EncryptionError::InvalidData(
"Ciphertext too short (missing nonce)".to_string(),
));
}
let (nonce_bytes, ciphertext) = data.split_at(AES_GCM_NONCE_SIZE);
let nonce = Nonce::from_slice(nonce_bytes);
self.cipher
.decrypt(nonce, ciphertext)
.map_err(|_| EncryptionError::DecryptionFailed)
}
}
decrypt_legacy_xor private
#![allow(unused)]
fn main() {
fn decrypt_legacy_xor (& self , data : & [u8]) -> Result < Vec < u8 > , EncryptionError >
}
Decrypts legacy XOR-encrypted data (for migration support).
Source
#![allow(unused)]
fn main() {
fn decrypt_legacy_xor(&self, data: &[u8]) -> Result<Vec<u8>, EncryptionError> {
if data.len() < LEGACY_XOR_NONCE_SIZE {
return Err(EncryptionError::InvalidData(
"Legacy ciphertext too short (missing nonce)".to_string(),
));
}
// Extract nonce and actual ciphertext
let nonce = &data[..LEGACY_XOR_NONCE_SIZE];
let encrypted = &data[LEGACY_XOR_NONCE_SIZE..];
// Derive same mask using SHA-256
let mut hasher = Sha256::new();
hasher.update(self.key);
hasher.update(nonce);
let mask = hasher.finalize();
// XOR to decrypt
let plaintext: Vec<u8> = encrypted
.iter()
.enumerate()
.map(|(i, &b)| b ^ mask[i % mask.len()])
.collect();
Ok(plaintext)
}
}
Enums
brokkr-broker::utils::encryption::EncryptionError pub
Encryption error types
Variants
EncryptionFailed- Encryption operation failedDecryptionFailed- Decryption operation failed (wrong key or corrupted data)InvalidData- Invalid data formatUnsupportedVersion- Unsupported encryption version
Functions
brokkr-broker::utils::encryption::init_encryption_key
pub
#![allow(unused)]
fn main() {
fn init_encryption_key (key_hex : Option < & str >) -> Result < () , String >
}
Initializes the global encryption key from configuration.
This should be called once during broker startup.
Parameters:
| Name | Type | Description |
|---|---|---|
key_hex | - | Optional hex-encoded 32-byte key. If None, a random key is generated. |
Returns:
Ok(()) if initialization succeeded, Err if already initialized or key is invalid.
Source
#![allow(unused)]
fn main() {
pub fn init_encryption_key(key_hex: Option<&str>) -> Result<(), String> {
let key = match key_hex {
Some(hex) if !hex.is_empty() => {
info!("Initializing encryption key from configuration");
EncryptionKey::from_hex(hex)?
}
_ => {
warn!(
"No encryption key configured, generating random key. \
Configure BROKKR__BROKER__WEBHOOK_ENCRYPTION_KEY for production use."
);
EncryptionKey::generate()
}
};
info!("Encryption key fingerprint: {}", key.fingerprint());
ENCRYPTION_KEY
.set(Arc::new(key))
.map_err(|_| "Encryption key already initialized".to_string())
}
}
brokkr-broker::utils::encryption::get_encryption_key
pub
#![allow(unused)]
fn main() {
fn get_encryption_key () -> Arc < EncryptionKey >
}
Gets the global encryption key.
Raises:
| Exception | Description |
|---|---|
Panic | Panics if called before init_encryption_key(). |
Source
#![allow(unused)]
fn main() {
pub fn get_encryption_key() -> Arc<EncryptionKey> {
ENCRYPTION_KEY
.get()
.expect("Encryption key not initialized. Call init_encryption_key() first.")
.clone()
}
}
brokkr-broker::utils::encryption::encrypt_string
pub
#![allow(unused)]
fn main() {
fn encrypt_string (value : & str) -> Result < Vec < u8 > , EncryptionError >
}
Encrypts a string value for storage.
Parameters:
| Name | Type | Description |
|---|---|---|
value | - | The plaintext string to encrypt. |
Returns:
The encrypted bytes, or an error if encryption fails.
Source
#![allow(unused)]
fn main() {
pub fn encrypt_string(value: &str) -> Result<Vec<u8>, EncryptionError> {
get_encryption_key().encrypt(value.as_bytes())
}
}
brokkr-broker::utils::encryption::decrypt_string
pub
#![allow(unused)]
fn main() {
fn decrypt_string (encrypted : & [u8]) -> Result < String , String >
}
Decrypts bytes back to a string.
Parameters:
| Name | Type | Description |
|---|---|---|
encrypted | - | The encrypted bytes. |
Returns:
The decrypted string, or an error if decryption fails.
Source
#![allow(unused)]
fn main() {
pub fn decrypt_string(encrypted: &[u8]) -> Result<String, String> {
let bytes = get_encryption_key()
.decrypt(encrypted)
.map_err(|e| e.to_string())?;
String::from_utf8(bytes).map_err(|e| format!("Decrypted value is not valid UTF-8: {}", e))
}
}