Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

NameTypeDescription
key[u8 ; 32]The raw 32-byte key.
cipherAes256GcmPre-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 failed
  • DecryptionFailed - Decryption operation failed (wrong key or corrupted data)
  • InvalidData - Invalid data format
  • UnsupportedVersion - 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:

NameTypeDescription
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:

ExceptionDescription
PanicPanics 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:

NameTypeDescription
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:

NameTypeDescription
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))
}
}