Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions stdlib/src/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use aes_gcm::{
use bcrypt;
use sha2::Digest;
use std::collections::HashMap;
use rand::Rng;
use std::rc::Rc;
use techscript_runtime::{error::RuntimeError, error::RuntimeErrorKind, value::RuntimeValue};

Expand Down Expand Up @@ -39,8 +40,10 @@ impl StdlibRegistry {
)
})?;

// Nonce is 12-byte zero nonce for simple FFI compatibility
let nonce = Nonce::from_slice(&[0u8; 12]);
// Generate a random 12-byte nonce for secure encryption
let mut nonce_bytes = [0u8; 12];
rand::thread_rng().fill(&mut nonce_bytes);
let nonce = Nonce::from_slice(&nonce_bytes);

let ciphertext = cipher.encrypt(nonce, text.as_bytes()).map_err(|e| {
RuntimeError::new(
Expand All @@ -53,8 +56,12 @@ impl StdlibRegistry {
)
})?;

// Prepend nonce to ciphertext
let mut combined = nonce_bytes.to_vec();
combined.extend_from_slice(&ciphertext);

// Hex encode ciphertext
let hex_ciphertext = ciphertext
let hex_ciphertext = combined
.iter()
.map(|b| format!("{:02x}", b))
.collect::<String>();
Expand Down Expand Up @@ -105,10 +112,22 @@ impl StdlibRegistry {
)
})?;

let nonce = Nonce::from_slice(&[0u8; 12]);
if ciphertext.len() < 12 {
return Err(RuntimeError::new(
RuntimeErrorKind::InvalidOperation(
"Ciphertext too short (missing nonce)".to_string(),
),
None,
None,
));
}

let nonce_bytes = &ciphertext[0..12];
let actual_ciphertext = &ciphertext[12..];
let nonce = Nonce::from_slice(nonce_bytes);

let plaintext_bytes =
cipher.decrypt(nonce, ciphertext.as_slice()).map_err(|e| {
cipher.decrypt(nonce, actual_ciphertext).map_err(|e| {
RuntimeError::new(
RuntimeErrorKind::InvalidOperation(format!(
"AES decryption error: {}",
Expand Down
27 changes: 22 additions & 5 deletions stdlib/tests/stdlib_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -893,13 +893,30 @@ fn test_crypto_hash_and_compression() {
let key = RuntimeValue::Str("my_secret_key_123".to_string());
let plain = RuntimeValue::Str("hello crypto world".to_string());

let encrypted = aes_enc
.call(&mut ctx_unprivileged, vec![key.clone(), plain])
let encrypted1 = aes_enc
.call(&mut ctx_unprivileged, vec![key.clone(), plain.clone()])
.unwrap();
let decrypted = aes_dec
.call(&mut ctx_unprivileged, vec![key, encrypted])

let encrypted2 = aes_enc
.call(&mut ctx_unprivileged, vec![key.clone(), plain.clone()])
.unwrap();

// Verify deterministic encryption is fixed (random nonce is working)
assert_ne!(
encrypted1.as_string().unwrap(),
encrypted2.as_string().unwrap(),
"Encrypting the same plaintext with the same key should produce different ciphertexts"
);

let decrypted1 = aes_dec
.call(&mut ctx_unprivileged, vec![key.clone(), encrypted1])
.unwrap();
assert_eq!(decrypted1.as_string(), Some("hello crypto world"));

let decrypted2 = aes_dec
.call(&mut ctx_unprivileged, vec![key.clone(), encrypted2])
.unwrap();
assert_eq!(decrypted.as_string(), Some("hello crypto world"));
assert_eq!(decrypted2.as_string(), Some("hello crypto world"));

let bcrypt_hash = crypto.exports.get("bcrypt_hash").unwrap();
let bcrypt_verify = crypto.exports.get("bcrypt_verify").unwrap();
Expand Down
Loading