gaia_crypt/
lib.rs

1//! A hybrid encryption library that provides RSA + AES-GCM encryption and decryption functionality.
2//!
3//! This library implements a hybrid encryption scheme where:
4//! - Data is encrypted with AES-256-GCM (for performance and to handle large data)
5//! - The AES key is encrypted with RSA (for secure key exchange)
6//!
7//! # Examples
8//!
9//! ```
10//! // Generate a new RSA key pair
11//! let (private_key, public_key) = generate_rsa_keypair().unwrap();
12//! let private_key_pem = private_key.to_pkcs1_pem().unwrap();
13//! let public_key_pem = public_key.to_public_key_pem().unwrap();
14//!
15//! // Encrypt data
16//! let data = b"Hello, world!";
17//! let encrypted = encrypt(&public_key_pem, data).unwrap();
18//!
19//! // Decrypt data
20//! let decrypted = decrypt(&private_key_pem, &encrypted).unwrap();
21//! assert_eq!(data, &decrypted[..]);
22//! ```
23
24use aes_gcm::{
25    aead::{Aead, AeadCore, KeyInit, OsRng},
26    Aes256Gcm, Key, Nonce,
27};
28use rsa::{
29    pkcs1::DecodeRsaPrivateKey, pkcs8::DecodePublicKey, Pkcs1v15Encrypt, RsaPrivateKey,
30    RsaPublicKey,
31};
32use serde::{Deserialize, Serialize};
33
34/// Represents an encrypted message using hybrid encryption (RSA + AES-GCM).
35///
36/// Contains three components:
37/// - The RSA-encrypted AES key
38/// - The nonce used for AES-GCM encryption
39/// - The AES-GCM encrypted data
40#[derive(Serialize, Deserialize)]
41struct EncryptedMessage {
42    /// The AES key encrypted with the recipient's RSA public key
43    encrypted_aes_key: Vec<u8>,
44    /// The nonce (number used once) for AES-GCM encryption
45    nonce: Vec<u8>,
46    /// The actual encrypted data (AES-GCM ciphertext)
47    ciphertext: Vec<u8>,
48}
49
50/// Encrypts data using a hybrid RSA + AES-GCM approach.
51///
52/// Takes a PEM-encoded RSA public key and plaintext data, then:
53/// 1. Generates a random AES-256 key
54/// 2. Encrypts the data with the AES key
55/// 3. Encrypts the AES key with the RSA public key
56/// 4. Returns a serialized structure containing everything needed for decryption
57///
58/// # Arguments
59///
60/// * `public_key` - A PEM-encoded RSA public key
61/// * `data` - The plaintext data to encrypt
62///
63/// # Returns
64///
65/// A serialized `EncryptedMessage` containing the encrypted data
66///
67/// # Errors
68///
69/// Returns an error if:
70/// - The public key is invalid or cannot be parsed
71/// - The encryption process fails
72/// - Serialization fails
73pub fn encrypt(public_key: &str, data: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
74    let public_key = RsaPublicKey::from_public_key_pem(&public_key)?;
75    raw_encrypt(&public_key, data)
76}
77
78/// Encrypts data using a hybrid RSA + AES-GCM approach with a pre-parsed RSA public key.
79///
80/// This is similar to `encrypt` but takes an already parsed `RsaPublicKey` object
81/// rather than a PEM-encoded string.
82///
83/// # Arguments
84///
85/// * `public_key` - The RSA public key object
86/// * `data` - The plaintext data to encrypt
87///
88/// # Returns
89///
90/// A serialized `EncryptedMessage` containing the encrypted data
91///
92/// # Errors
93///
94/// Returns an error if:
95/// - The AES encryption fails
96/// - The RSA encryption of the AES key fails
97/// - Serialization fails
98pub fn raw_encrypt(
99    public_key: &RsaPublicKey,
100    data: &[u8],
101) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
102    // Generate a random AES-256 key
103    let aes_key = Aes256Gcm::generate_key(OsRng);
104
105    // Create the AES-GCM cipher with the generated key
106    let cipher = Aes256Gcm::new(&aes_key);
107
108    // Generate a random nonce
109    let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
110
111    // Encrypt the data with AES
112    let ciphertext = cipher
113        .encrypt(&nonce, data)
114        .map_err(|e| format!("AES encryption failed: {}", e))?;
115
116    // Encrypt the AES key with the RSA public key
117    let encrypted_aes_key = public_key
118        .encrypt(&mut OsRng, Pkcs1v15Encrypt::default(), &aes_key)
119        .map_err(|e| format!("RSA encryption failed: {}", e))?;
120
121    // Bundle everything into a struct
122    let message = EncryptedMessage {
123        encrypted_aes_key,
124        nonce: nonce.to_vec(),
125        ciphertext,
126    };
127
128    // Serialize to bytes
129    let serialized = bincode::serialize(&message)?;
130
131    Ok(serialized)
132}
133
134/// Decrypts data that was encrypted with the hybrid RSA + AES-GCM approach.
135///
136/// Takes a PEM-encoded RSA private key and encrypted data, then:
137/// 1. Deserializes the encrypted message structure
138/// 2. Decrypts the AES key using the RSA private key
139/// 3. Uses the decrypted AES key to decrypt the actual data
140///
141/// # Arguments
142///
143/// * `private_key` - A PEM-encoded RSA private key
144/// * `data` - The encrypted data to decrypt (as serialized by `encrypt`)
145///
146/// # Returns
147///
148/// The decrypted plaintext data
149///
150/// # Errors
151///
152/// Returns an error if:
153/// - The private key is invalid or cannot be parsed
154/// - The encrypted data cannot be deserialized
155/// - The RSA decryption of the AES key fails
156/// - The AES decryption of the data fails
157pub fn decrypt(private_key: &str, data: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
158    let private_key = RsaPrivateKey::from_pkcs1_pem(&private_key)?;
159    raw_decrypt(&private_key, data)
160}
161
162/// Decrypts data that was encrypted with the hybrid RSA + AES-GCM approach using a pre-parsed private key.
163///
164/// This is similar to `decrypt` but takes an already parsed `RsaPrivateKey` object
165/// rather than a PEM-encoded string.
166///
167/// # Arguments
168///
169/// * `private_key` - The RSA private key object
170/// * `encrypted_data` - The encrypted data to decrypt (as serialized by `encrypt` or `raw_encrypt`)
171///
172/// # Returns
173///
174/// The decrypted plaintext data
175///
176/// # Errors
177///
178/// Returns an error if:
179/// - The encrypted data cannot be deserialized
180/// - The RSA decryption of the AES key fails
181/// - The AES decryption of the data fails
182pub fn raw_decrypt(
183    private_key: &RsaPrivateKey,
184    encrypted_data: &[u8],
185) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
186    // Deserialize the incoming message
187    let encrypted_message: EncryptedMessage = bincode::deserialize(encrypted_data)?;
188
189    // Decrypt the AES key using the private key
190    let aes_key = private_key
191        .decrypt(
192            Pkcs1v15Encrypt::default(),
193            &encrypted_message.encrypted_aes_key,
194        )
195        .map_err(|e| format!("RSA decryption failed: {}", e))?;
196
197    // Create the AES-GCM cipher with the decrypted key
198    let key = Key::<Aes256Gcm>::from_slice(&aes_key);
199    let cipher = Aes256Gcm::new(key);
200
201    // Create the nonce from the received bytes
202    let nonce = Nonce::from_slice(&encrypted_message.nonce);
203
204    // Decrypt the data
205    let plaintext = cipher
206        .decrypt(nonce, encrypted_message.ciphertext.as_ref())
207        .map_err(|e| format!("AES decryption failed: {}", e))?;
208
209    Ok(plaintext)
210}
211
212/// Generates a new RSA key pair for use with the encryption/decryption functions.
213///
214/// Creates a 2048-bit RSA key pair that can be used for hybrid encryption.
215///
216/// # Returns
217///
218/// A tuple containing the private and public keys: `(RsaPrivateKey, RsaPublicKey)`
219///
220/// # Errors
221///
222/// Returns an error if the key generation process fails
223pub fn generate_rsa_keypair() -> Result<(RsaPrivateKey, RsaPublicKey), Box<dyn std::error::Error>> {
224    let mut rng = rand::thread_rng();
225    let bits = 2048;
226
227    let private_key = RsaPrivateKey::new(&mut rng, bits)?;
228    let public_key = RsaPublicKey::from(&private_key);
229
230    Ok((private_key, public_key))
231}