Crate encrypted_message

source ·
Expand description

Safely encrypt & store serializable data using AES-256-GCM.

§Key configuration

First, you need to create a key configuration that implements the KeyConfig trait. If your key configuration implements the Default trait, you can use the shorthand methods of the EncryptedMessage struct.

The first key provided is considered the primary key, & is always used to encrypt new payloads. The following keys are used in the order provided when the primary key can’t decrypt a payload. This allows you to rotate keys.

Two key decoders are provided, HexKeyDecoder & Base64KeyDecoder, to help you store your key(s) as strings.

use encrypted_message::{
    key_config::Secret,
    utilities::key_decoder::HexKeyDecoder,
};

#[derive(Debug, Default)]
struct KeyConfig;
impl encrypted_message::KeyConfig for KeyConfig {
    fn keys(&self) -> Vec<Secret<[u8; 32]>> {
        // Load the keys from an environment variable, & wrap them in a `Secret`.
        let keys = std::env::var("ENCRYPTION_KEYS").unwrap()
            .split(", ")
            .map(|key| Secret::new(key.to_string()))
            .collect();

        HexKeyDecoder::decode_keys(keys)
    }
}

You can generate secure 32-byte keys using the openssl command-line tool. Remember to use HexKeyDecoder to decode the key.

openssl rand -hex 32

§Encryption strategies

Two encryption strategies are provided, Deterministic & Randomized.

  • Deterministic encryption will always produce the same encrypted message for the same payload, allowing you to query encrypted data.
  • Randomized encryption will always produce a different encrypted message for the same payload. More secure than Deterministic, but impossible to query without decrypting all data.

It’s recommended to use different keys for each encryption strategy.

§Defining encrypted fields

You can now define your encrypted fields using the EncryptedMessage struct. The first type parameter is the payload type, the second is the encryption strategy, & the third is the key configuration type.

use encrypted_message::{EncryptedMessage, strategy::Randomized};

struct User {
    diary: EncryptedMessage<String, Randomized, KeyConfig>,
}

§Encrypting & decrypting payloads

If your KeyConfig implements the Default trait (like above), you can use the shorthand methods:

// Encrypt a user's diary.
let mut user = User {
    diary: EncryptedMessage::encrypt("Very personal stuff".to_string()).unwrap(),
};

// Decrypt the user's diary.
let decrypted: String = user.diary.decrypt().unwrap();

// Update the user's diary using the same encryption strategy & key config.
user.diary = user.diary.with_new_payload("More personal stuff".to_string()).unwrap();

If your KeyConfig depends on external data:

use encrypted_message::{
    EncryptedMessage,
    key_config::Secret,
    strategy::Randomized,
    utilities::key_generation::derive_key_from,
};
use secrecy::{ExposeSecret as _, SecretString};

#[derive(Debug)]
struct UserKeyConfig {
    user_password: SecretString,
    salt: SecretString,
}

impl encrypted_message::KeyConfig for UserKeyConfig {
    fn keys(&self) -> Vec<Secret<[u8; 32]>> {
        let raw_key = self.user_password.expose_secret().as_bytes();
        let salt = self.salt.expose_secret().as_bytes();
        vec![derive_key_from(&raw_key, &salt, 2_u32.pow(16))]
    }
}

struct User {
    diary: EncryptedMessage<String, Randomized, UserKeyConfig>,
}

// Define the user's key configuration.
let key_config = UserKeyConfig {
    user_password: "human-password-that-should-be-derived".to_string().into(),
    salt: "unique-salt".to_string().into(),
};

// Encrypt a user's diary.
let mut user = User {
    diary: EncryptedMessage::encrypt_with_key_config("Very personal stuff".to_string(), &key_config).unwrap(),
};

// Decrypt the user's diary.
let decrypted: String = user.diary.decrypt_with_key_config(&key_config).unwrap();

// Update the user's diary using the same encryption strategy & key config.
user.diary = user.diary.with_new_payload_and_key_config("More personal stuff".to_string(), &key_config).unwrap();

§Integration with Diesel

EncryptedMessage implements FromSql & ToSql, allowing you to use EncryptedMessage as a field type in your models.

  • MySQL: Enable the diesel & diesel-mysql features. Supports the Json type.
  • PostgreSQL: Enable the diesel & diesel-postgres features. Supports the Json & Jsonb types.

Re-exports§

Modules§

Structs§

  • Used to safely handle & transport encrypted data within your application. It contains an encrypted payload, along with a nonce & tag that are used in the encryption & decryption processes.