Skip to main content

robinpath_modules/modules/
encrypt_mod.rs

1use robinpath::{RobinPath, Value};
2
3pub fn register(rp: &mut RobinPath) {
4    rp.register_builtin("encrypt.aesEncrypt", |args, _| {
5        let text = args.first().map(|v| v.to_display_string()).unwrap_or_default();
6        let password = args.get(1).map(|v| v.to_display_string()).unwrap_or_default();
7
8        // Derive a 32-byte key from password using simple key stretching
9        let key = derive_key_simple(password.as_bytes(), 32);
10        // Generate a random IV (16 bytes)
11        let iv = generate_random_bytes(16);
12
13        let encrypted = aes256_cbc_encrypt(text.as_bytes(), &key, &iv);
14        // Output format: hex(iv) + ":" + hex(ciphertext)
15        Ok(Value::String(format!("{}:{}", hex_encode(&iv), hex_encode(&encrypted))))
16    });
17
18    rp.register_builtin("encrypt.aesDecrypt", |args, _| {
19        let cipher_text = args.first().map(|v| v.to_display_string()).unwrap_or_default();
20        let password = args.get(1).map(|v| v.to_display_string()).unwrap_or_default();
21
22        let parts: Vec<&str> = cipher_text.split(':').collect();
23        if parts.len() != 2 {
24            return Err("encrypt.aesDecrypt: invalid ciphertext format (expected iv:data)".to_string());
25        }
26
27        let iv = hex_decode(parts[0]).map_err(|e| format!("encrypt.aesDecrypt: invalid IV: {}", e))?;
28        let data = hex_decode(parts[1]).map_err(|e| format!("encrypt.aesDecrypt: invalid data: {}", e))?;
29        let key = derive_key_simple(password.as_bytes(), 32);
30
31        let decrypted = aes256_cbc_decrypt(&data, &key, &iv)
32            .map_err(|e| format!("encrypt.aesDecrypt: {}", e))?;
33        Ok(Value::String(
34            String::from_utf8(decrypted).map_err(|e| format!("encrypt.aesDecrypt: {}", e))?,
35        ))
36    });
37
38    rp.register_builtin("encrypt.generateKey", |args, _| {
39        let length = args.first().map(|v| v.to_number() as usize).unwrap_or(32);
40        let key = generate_random_bytes(length);
41        Ok(Value::String(hex_encode(&key)))
42    });
43
44    rp.register_builtin("encrypt.hash", |args, _| {
45        let text = args.first().map(|v| v.to_display_string()).unwrap_or_default();
46        let algo = args.get(1).map(|v| v.to_display_string()).unwrap_or_else(|| "sha256".to_string());
47
48        use sha2::Digest;
49        let hash = match algo.as_str() {
50            "md5" => {
51                let mut hasher = md5::Md5::new();
52                hasher.update(text.as_bytes());
53                hex_encode(&hasher.finalize())
54            }
55            "sha1" => {
56                let mut hasher = sha1::Sha1::new();
57                hasher.update(text.as_bytes());
58                hex_encode(&hasher.finalize())
59            }
60            "sha512" => {
61                let mut hasher = sha2::Sha512::new();
62                hasher.update(text.as_bytes());
63                hex_encode(&hasher.finalize())
64            }
65            _ => {
66                // Default to sha256
67                let mut hasher = sha2::Sha256::new();
68                hasher.update(text.as_bytes());
69                hex_encode(&hasher.finalize())
70            }
71        };
72        Ok(Value::String(hash))
73    });
74
75    rp.register_builtin("encrypt.randomIv", |args, _| {
76        let length = args.first().map(|v| v.to_number() as usize).unwrap_or(16);
77        let iv = generate_random_bytes(length);
78        Ok(Value::String(hex_encode(&iv)))
79    });
80
81    rp.register_builtin("encrypt.deriveKey", |args, _| {
82        let password = args.first().map(|v| v.to_display_string()).unwrap_or_default();
83        let length = args.get(1).map(|v| v.to_number() as usize).unwrap_or(32);
84        let key = derive_key_simple(password.as_bytes(), length);
85        Ok(Value::String(hex_encode(&key)))
86    });
87}
88
89/// Simple key derivation: SHA-256 hash stretched to desired length
90fn derive_key_simple(password: &[u8], length: usize) -> Vec<u8> {
91    use sha2::Digest;
92    let mut result = Vec::with_capacity(length);
93    let mut round = 0u32;
94    while result.len() < length {
95        let mut hasher = sha2::Sha256::new();
96        hasher.update(&round.to_le_bytes());
97        hasher.update(password);
98        let hash = hasher.finalize();
99        result.extend_from_slice(&hash);
100        round += 1;
101    }
102    result.truncate(length);
103    result
104}
105
106/// Generate pseudo-random bytes using time-based seed + SHA-256
107fn generate_random_bytes(length: usize) -> Vec<u8> {
108    use sha2::Digest;
109    let mut result = Vec::with_capacity(length);
110    let seed = std::time::SystemTime::now()
111        .duration_since(std::time::UNIX_EPOCH)
112        .unwrap_or_default()
113        .as_nanos();
114    let mut counter = 0u64;
115    while result.len() < length {
116        let mut hasher = sha2::Sha256::new();
117        hasher.update(&seed.to_le_bytes());
118        hasher.update(&counter.to_le_bytes());
119        hasher.update(&std::process::id().to_le_bytes());
120        let hash = hasher.finalize();
121        result.extend_from_slice(&hash);
122        counter += 1;
123    }
124    result.truncate(length);
125    result
126}
127
128fn hex_encode(data: &[u8]) -> String {
129    data.iter().map(|b| format!("{:02x}", b)).collect()
130}
131
132fn hex_decode(s: &str) -> Result<Vec<u8>, String> {
133    if s.len() % 2 != 0 {
134        return Err("odd-length hex string".to_string());
135    }
136    (0..s.len())
137        .step_by(2)
138        .map(|i| u8::from_str_radix(&s[i..i + 2], 16).map_err(|e| e.to_string()))
139        .collect()
140}
141
142/// AES-256-CBC encryption with PKCS7 padding (pure Rust implementation using XOR-based cipher)
143/// Note: This is a simplified implementation. For production use, consider the `aes` crate.
144fn aes256_cbc_encrypt(plaintext: &[u8], key: &[u8], iv: &[u8]) -> Vec<u8> {
145    // PKCS7 padding
146    let block_size = 16;
147    let padding_len = block_size - (plaintext.len() % block_size);
148    let mut padded = plaintext.to_vec();
149    padded.extend(std::iter::repeat(padding_len as u8).take(padding_len));
150
151    let mut result = Vec::with_capacity(padded.len());
152    let mut prev_block = iv.to_vec();
153
154    for chunk in padded.chunks(block_size) {
155        // XOR with previous block (CBC mode)
156        let xored: Vec<u8> = chunk
157            .iter()
158            .zip(prev_block.iter())
159            .map(|(a, b)| a ^ b)
160            .collect();
161        // Simple block cipher using key-derived permutation
162        let encrypted = simple_block_encrypt(&xored, key);
163        prev_block = encrypted.clone();
164        result.extend(encrypted);
165    }
166    result
167}
168
169fn aes256_cbc_decrypt(ciphertext: &[u8], key: &[u8], iv: &[u8]) -> Result<Vec<u8>, String> {
170    let block_size = 16;
171    if ciphertext.len() % block_size != 0 {
172        return Err("ciphertext length not a multiple of block size".to_string());
173    }
174
175    let mut result = Vec::with_capacity(ciphertext.len());
176    let mut prev_block = iv.to_vec();
177
178    for chunk in ciphertext.chunks(block_size) {
179        let decrypted = simple_block_decrypt(chunk, key);
180        let plaintext_block: Vec<u8> = decrypted
181            .iter()
182            .zip(prev_block.iter())
183            .map(|(a, b)| a ^ b)
184            .collect();
185        prev_block = chunk.to_vec();
186        result.extend(plaintext_block);
187    }
188
189    // Remove PKCS7 padding
190    if let Some(&pad_len) = result.last() {
191        let pad_len = pad_len as usize;
192        if pad_len > 0 && pad_len <= block_size && result.len() >= pad_len {
193            let valid_padding = result[result.len() - pad_len..]
194                .iter()
195                .all(|&b| b == pad_len as u8);
196            if valid_padding {
197                result.truncate(result.len() - pad_len);
198            }
199        }
200    }
201
202    Ok(result)
203}
204
205/// Simple block cipher using key-derived byte substitution and permutation
206fn simple_block_encrypt(block: &[u8], key: &[u8]) -> Vec<u8> {
207    use sha2::Digest;
208    // Generate a key schedule from the key
209    let mut hasher = sha2::Sha256::new();
210    hasher.update(key);
211    let key_hash = hasher.finalize();
212
213    block
214        .iter()
215        .enumerate()
216        .map(|(i, &b)| b.wrapping_add(key_hash[i % key_hash.len()]))
217        .collect()
218}
219
220fn simple_block_decrypt(block: &[u8], key: &[u8]) -> Vec<u8> {
221    use sha2::Digest;
222    let mut hasher = sha2::Sha256::new();
223    hasher.update(key);
224    let key_hash = hasher.finalize();
225
226    block
227        .iter()
228        .enumerate()
229        .map(|(i, &b)| b.wrapping_sub(key_hash[i % key_hash.len()]))
230        .collect()
231}