gaia-crypt 0.1.0

A cryptographic library for secure communication in the GaiaNet ecosystem
Documentation
//! A hybrid encryption library that provides RSA + AES-GCM encryption and decryption functionality.
//!
//! This library implements a hybrid encryption scheme where:
//! - Data is encrypted with AES-256-GCM (for performance and to handle large data)
//! - The AES key is encrypted with RSA (for secure key exchange)
//!
//! # Examples
//!
//! ```
//! // Generate a new RSA key pair
//! let (private_key, public_key) = generate_rsa_keypair().unwrap();
//! let private_key_pem = private_key.to_pkcs1_pem().unwrap();
//! let public_key_pem = public_key.to_public_key_pem().unwrap();
//!
//! // Encrypt data
//! let data = b"Hello, world!";
//! let encrypted = encrypt(&public_key_pem, data).unwrap();
//!
//! // Decrypt data
//! let decrypted = decrypt(&private_key_pem, &encrypted).unwrap();
//! assert_eq!(data, &decrypted[..]);
//! ```

use aes_gcm::{
    aead::{Aead, AeadCore, KeyInit, OsRng},
    Aes256Gcm, Key, Nonce,
};
use rsa::{
    pkcs1::DecodeRsaPrivateKey, pkcs8::DecodePublicKey, Pkcs1v15Encrypt, RsaPrivateKey,
    RsaPublicKey,
};
use serde::{Deserialize, Serialize};

/// Represents an encrypted message using hybrid encryption (RSA + AES-GCM).
///
/// Contains three components:
/// - The RSA-encrypted AES key
/// - The nonce used for AES-GCM encryption
/// - The AES-GCM encrypted data
#[derive(Serialize, Deserialize)]
struct EncryptedMessage {
    /// The AES key encrypted with the recipient's RSA public key
    encrypted_aes_key: Vec<u8>,
    /// The nonce (number used once) for AES-GCM encryption
    nonce: Vec<u8>,
    /// The actual encrypted data (AES-GCM ciphertext)
    ciphertext: Vec<u8>,
}

/// Encrypts data using a hybrid RSA + AES-GCM approach.
///
/// Takes a PEM-encoded RSA public key and plaintext data, then:
/// 1. Generates a random AES-256 key
/// 2. Encrypts the data with the AES key
/// 3. Encrypts the AES key with the RSA public key
/// 4. Returns a serialized structure containing everything needed for decryption
///
/// # Arguments
///
/// * `public_key` - A PEM-encoded RSA public key
/// * `data` - The plaintext data to encrypt
///
/// # Returns
///
/// A serialized `EncryptedMessage` containing the encrypted data
///
/// # Errors
///
/// Returns an error if:
/// - The public key is invalid or cannot be parsed
/// - The encryption process fails
/// - Serialization fails
pub fn encrypt(public_key: &str, data: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
    let public_key = RsaPublicKey::from_public_key_pem(&public_key)?;
    raw_encrypt(&public_key, data)
}

/// Encrypts data using a hybrid RSA + AES-GCM approach with a pre-parsed RSA public key.
///
/// This is similar to `encrypt` but takes an already parsed `RsaPublicKey` object
/// rather than a PEM-encoded string.
///
/// # Arguments
///
/// * `public_key` - The RSA public key object
/// * `data` - The plaintext data to encrypt
///
/// # Returns
///
/// A serialized `EncryptedMessage` containing the encrypted data
///
/// # Errors
///
/// Returns an error if:
/// - The AES encryption fails
/// - The RSA encryption of the AES key fails
/// - Serialization fails
pub fn raw_encrypt(
    public_key: &RsaPublicKey,
    data: &[u8],
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
    // Generate a random AES-256 key
    let aes_key = Aes256Gcm::generate_key(OsRng);

    // Create the AES-GCM cipher with the generated key
    let cipher = Aes256Gcm::new(&aes_key);

    // Generate a random nonce
    let nonce = Aes256Gcm::generate_nonce(&mut OsRng);

    // Encrypt the data with AES
    let ciphertext = cipher
        .encrypt(&nonce, data)
        .map_err(|e| format!("AES encryption failed: {}", e))?;

    // Encrypt the AES key with the RSA public key
    let encrypted_aes_key = public_key
        .encrypt(&mut OsRng, Pkcs1v15Encrypt::default(), &aes_key)
        .map_err(|e| format!("RSA encryption failed: {}", e))?;

    // Bundle everything into a struct
    let message = EncryptedMessage {
        encrypted_aes_key,
        nonce: nonce.to_vec(),
        ciphertext,
    };

    // Serialize to bytes
    let serialized = bincode::serialize(&message)?;

    Ok(serialized)
}

/// Decrypts data that was encrypted with the hybrid RSA + AES-GCM approach.
///
/// Takes a PEM-encoded RSA private key and encrypted data, then:
/// 1. Deserializes the encrypted message structure
/// 2. Decrypts the AES key using the RSA private key
/// 3. Uses the decrypted AES key to decrypt the actual data
///
/// # Arguments
///
/// * `private_key` - A PEM-encoded RSA private key
/// * `data` - The encrypted data to decrypt (as serialized by `encrypt`)
///
/// # Returns
///
/// The decrypted plaintext data
///
/// # Errors
///
/// Returns an error if:
/// - The private key is invalid or cannot be parsed
/// - The encrypted data cannot be deserialized
/// - The RSA decryption of the AES key fails
/// - The AES decryption of the data fails
pub fn decrypt(private_key: &str, data: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
    let private_key = RsaPrivateKey::from_pkcs1_pem(&private_key)?;
    raw_decrypt(&private_key, data)
}

/// Decrypts data that was encrypted with the hybrid RSA + AES-GCM approach using a pre-parsed private key.
///
/// This is similar to `decrypt` but takes an already parsed `RsaPrivateKey` object
/// rather than a PEM-encoded string.
///
/// # Arguments
///
/// * `private_key` - The RSA private key object
/// * `encrypted_data` - The encrypted data to decrypt (as serialized by `encrypt` or `raw_encrypt`)
///
/// # Returns
///
/// The decrypted plaintext data
///
/// # Errors
///
/// Returns an error if:
/// - The encrypted data cannot be deserialized
/// - The RSA decryption of the AES key fails
/// - The AES decryption of the data fails
pub fn raw_decrypt(
    private_key: &RsaPrivateKey,
    encrypted_data: &[u8],
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
    // Deserialize the incoming message
    let encrypted_message: EncryptedMessage = bincode::deserialize(encrypted_data)?;

    // Decrypt the AES key using the private key
    let aes_key = private_key
        .decrypt(
            Pkcs1v15Encrypt::default(),
            &encrypted_message.encrypted_aes_key,
        )
        .map_err(|e| format!("RSA decryption failed: {}", e))?;

    // Create the AES-GCM cipher with the decrypted key
    let key = Key::<Aes256Gcm>::from_slice(&aes_key);
    let cipher = Aes256Gcm::new(key);

    // Create the nonce from the received bytes
    let nonce = Nonce::from_slice(&encrypted_message.nonce);

    // Decrypt the data
    let plaintext = cipher
        .decrypt(nonce, encrypted_message.ciphertext.as_ref())
        .map_err(|e| format!("AES decryption failed: {}", e))?;

    Ok(plaintext)
}

/// Generates a new RSA key pair for use with the encryption/decryption functions.
///
/// Creates a 2048-bit RSA key pair that can be used for hybrid encryption.
///
/// # Returns
///
/// A tuple containing the private and public keys: `(RsaPrivateKey, RsaPublicKey)`
///
/// # Errors
///
/// Returns an error if the key generation process fails
pub fn generate_rsa_keypair() -> Result<(RsaPrivateKey, RsaPublicKey), Box<dyn std::error::Error>> {
    let mut rng = rand::thread_rng();
    let bits = 2048;

    let private_key = RsaPrivateKey::new(&mut rng, bits)?;
    let public_key = RsaPublicKey::from(&private_key);

    Ok((private_key, public_key))
}