Skip to main content

seq_runtime/crypto/
pbkdf.rs

1//! Password-based key derivation: PBKDF2-HMAC-SHA256.
2
3use crate::seqstring::global_string;
4use crate::stack::{Stack, pop, push};
5use crate::value::Value;
6
7use sha2::Sha256;
8
9use super::{AES_KEY_SIZE, MIN_PBKDF2_ITERATIONS};
10
11/// Derive a key from a password using PBKDF2-SHA256
12///
13/// Stack effect: ( String String Int -- String Bool )
14///
15/// Arguments:
16/// - password: The password string
17/// - salt: Salt string (should be unique per user/context)
18/// - iterations: Number of iterations (recommend 100000+)
19///
20/// Returns:
21/// - key: Hex-encoded 32-byte key (64 hex characters)
22/// - success: Bool indicating success
23///
24/// # Safety
25/// Stack must have String, String, Int values on top
26#[unsafe(no_mangle)]
27pub unsafe extern "C" fn patch_seq_crypto_pbkdf2_sha256(stack: Stack) -> Stack {
28    assert!(!stack.is_null(), "crypto.pbkdf2-sha256: stack is null");
29
30    let (stack, iterations_val) = unsafe { pop(stack) };
31    let (stack, salt_val) = unsafe { pop(stack) };
32    let (stack, password_val) = unsafe { pop(stack) };
33
34    match (password_val, salt_val, iterations_val) {
35        (Value::String(password), Value::String(salt), Value::Int(iterations)) => {
36            // Require minimum iterations for security (100,000+ recommended for production)
37            if iterations < MIN_PBKDF2_ITERATIONS {
38                let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
39                return unsafe { push(stack, Value::Bool(false)) };
40            }
41
42            let key = derive_key_pbkdf2(password.as_str(), salt.as_str(), iterations as u32);
43            let key_hex = hex::encode(key);
44            let stack = unsafe { push(stack, Value::String(global_string(key_hex))) };
45            unsafe { push(stack, Value::Bool(true)) }
46        }
47        _ => panic!("crypto.pbkdf2-sha256: expected String, String, Int on stack"),
48    }
49}
50
51pub(super) fn derive_key_pbkdf2(password: &str, salt: &str, iterations: u32) -> [u8; AES_KEY_SIZE] {
52    let mut key = [0u8; AES_KEY_SIZE];
53    pbkdf2::pbkdf2_hmac::<Sha256>(password.as_bytes(), salt.as_bytes(), iterations, &mut key);
54    key
55}