Skip to content

Commit

Permalink
encrypt/decrypt data with secret key
Browse files Browse the repository at this point in the history
  • Loading branch information
va-an committed Jun 3, 2024
1 parent 120d1e7 commit 15ffb17
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 1 deletion.
2 changes: 2 additions & 0 deletions core/lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ tokio-stream = { version = "0.1.6", features = ["signal", "time"] }
cookie = { version = "0.18", features = ["percent-encode"] }
futures = { version = "0.3.30", default-features = false, features = ["std"] }
state = "0.6"
aes-gcm = "0.10.3"
base64 = "0.22.1"

[dependencies.hyper-util]
version = "0.1.3"
Expand Down
78 changes: 78 additions & 0 deletions core/lib/src/config/secret_key.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
use std::fmt;

use aes_gcm::{Aes256Gcm, Nonce};
use aes_gcm::aead::{generic_array::GenericArray, Aead, KeyInit};
use base64::{engine::general_purpose::URL_SAFE, Engine as _};
use rand::RngCore;


use cookie::Key;
use serde::{de, ser, Deserialize, Serialize};

use crate::request::{Outcome, Request, FromRequest};

const NONCE_LEN: usize = 12;
const KEY_LEN: usize = 32;

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
enum Kind {
Zero,
Expand Down Expand Up @@ -178,6 +187,75 @@ impl SecretKey {
{
ser.serialize_bytes(&[0; 32][..])
}

/// Encrypts the given plaintext.
/// Generates a random nonce for each encryption to ensure uniqueness.
/// Returns the base64-encoded string of the concatenated nonce and ciphertext.
///
/// # Example
/// ```rust
/// let plaintext = "I like turtles";
/// let secret_key = SecretKey::generate().expect("error generate key");
///
/// let encrypted = secret_key.encrypt(&plaintext).expect("can't encrypt");
/// let decrypted = secret_key.decrypt(&encrypted).expect("can't decrypt");
///
/// assert_eq!(decrypted, plaintext);
/// ```
pub fn encrypt(&self, plaintext: &str) -> Result<String, &'static str> {
// Convert the encryption key to a fixed-length array
let key: [u8; KEY_LEN] = self.key.encryption().try_into().map_err(|_| "enc key len error")?;

// Create a new AES-256-GCM instance with the provided key
let aead = Aes256Gcm::new(GenericArray::from_slice(&key));

// Generate a random nonce
let mut nonce = [0u8; NONCE_LEN];
let mut rng = rand::thread_rng();
rng.try_fill_bytes(&mut nonce).map_err(|_| "couldn't random fill nonce")?;
let nonce = Nonce::from_slice(&nonce);

// Encrypt the plaintext using the nonce
let ciphertext = aead.encrypt(nonce, plaintext.as_ref()).map_err(|_| "encryption error")?;

// Prepare a vector to hold the nonce and ciphertext
let mut encrypted_data = Vec::with_capacity(NONCE_LEN + ciphertext.len());
encrypted_data.extend_from_slice(nonce);
encrypted_data.extend_from_slice(&ciphertext);

// Return the base64-encoded result
Ok(URL_SAFE.encode(encrypted_data))
}

/// Decrypts the given base64-encoded encrypted data.
/// Extracts the nonce from the data and uses it for decryption.
/// Returns the decrypted plaintext string.
pub fn decrypt(&self, encrypted: &str) -> Result<String, &'static str> {
// Decode the base64-encoded encrypted data
let decoded = URL_SAFE.decode(encrypted).map_err(|_| "bad base64 value")?;

// Check if the length of decoded data is at least the length of the nonce
if decoded.len() < NONCE_LEN {
return Err("length of decoded data is < NONCE_LEN");
}

// Split the decoded data into nonce and ciphertext
let (nonce, ciphertext) = decoded.split_at(NONCE_LEN);
let nonce = Nonce::from_slice(nonce);

// Convert the encryption key to a fixed-length array
let key: [u8; KEY_LEN] = self.key.encryption().try_into().expect("enc key len");

// Create a new AES-256-GCM instance with the provided key
let aead = Aes256Gcm::new(GenericArray::from_slice(&key));

// Decrypt the ciphertext using the nonce
let decrypted = aead.decrypt(nonce, ciphertext)
.map_err(|_| "invalid key/nonce/value: bad seal")?;

// Convert the decrypted bytes to a UTF-8 string
String::from_utf8(decrypted).map_err(|_| "bad unsealed utf8")
}
}

impl PartialEq for SecretKey {
Expand Down
2 changes: 1 addition & 1 deletion examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ members = [
"testing",
"tls",
"upgrade",

"pastebin",
"todo",
"chat",
"private-data",
]
9 changes: 9 additions & 0 deletions examples/private-data/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "private-data"
version = "0.0.0"
workspace = "../"
edition = "2021"
publish = false

[dependencies]
rocket = { path = "../../core/lib", features = ["secrets"] }
2 changes: 2 additions & 0 deletions examples/private-data/Rocket.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[default]
secret_key = "itlYmFR2vYKrOmFhupMIn/hyB6lYCCTXz4yaQX89XVg="
36 changes: 36 additions & 0 deletions examples/private-data/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#[macro_use]
extern crate rocket;

use rocket::{Config, State};
use rocket::fairing::AdHoc;

#[cfg(test)] mod tests;

#[get("/encrypt/<msg>")]
fn encrypt_endpoint(msg: &str, config: &State<Config>) -> String{
let secret_key = config.secret_key.clone();
let encrypted = secret_key.encrypt(msg).unwrap();

info!("received message for encrypt: '{}'", msg);
info!("encrypted msg: '{}'", encrypted);

encrypted
}

#[get("/decrypt/<msg>")]
fn decrypt_endpoint(msg: &str, config: &State<Config>) -> String {
let secret_key = config.secret_key.clone();
let decrypted = secret_key.decrypt(msg).unwrap();

info!("received message for decrypt: '{}'", msg);
info!("decrypted msg: '{}'", decrypted);

decrypted
}

#[launch]
fn rocket() -> _ {
rocket::build()
.mount("/", routes![encrypt_endpoint, decrypt_endpoint])
.attach(AdHoc::config::<Config>())
}
12 changes: 12 additions & 0 deletions examples/private-data/src/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use rocket::local::blocking::Client;

#[test]
fn encrypt_decrypt() {
let client = Client::tracked(super::rocket()).unwrap();
let msg = "some-secret-message";

let encrypted = client.get(format!("/encrypt/{}", msg)).dispatch().into_string().unwrap();
let decrypted = client.get(format!("/decrypt/{}", encrypted)).dispatch().into_string().unwrap();

assert_eq!(msg, decrypted);
}

0 comments on commit 15ffb17

Please sign in to comment.