go_server_rust_sdk/
crypto.rs

1//! Cryptographic utilities for the go-server Rust SDK
2//!
3//! This module provides AES-GCM encryption and decryption functionality
4//! compatible with the Go implementation.
5
6use aes_gcm::{Aes256Gcm, Key, Nonce, KeyInit};
7use aes_gcm::aead::Aead;
8use base64::{Engine as _, engine::general_purpose};
9use sha2::{Sha256, Digest};
10use serde_json::Value;
11use crate::error::{Result, SdkError};
12
13/// Encrypts data using AES-GCM with a deterministic IV derived from the key
14/// 
15/// This function is compatible with the Go implementation's `encryptData` function.
16/// 
17/// # Arguments
18/// 
19/// * `data` - The data to encrypt (will be JSON serialized)
20/// * `key` - The encryption key
21/// 
22/// # Returns
23/// 
24/// Base64-encoded encrypted data
25pub fn encrypt_data(data: &Value, key: &str) -> Result<String> {
26    // Serialize data to JSON
27    let data_bytes = serde_json::to_vec(data)?;
28    
29    // Hash the key using SHA-256
30    let mut hasher = Sha256::new();
31    hasher.update(key.as_bytes());
32    let key_hash = hasher.finalize();
33    
34    // Create AES-256-GCM cipher
35    let cipher_key = Key::<Aes256Gcm>::from_slice(&key_hash);
36    let cipher = Aes256Gcm::new(cipher_key);
37    
38    // Generate deterministic IV from key (first 12 bytes of SHA-256 hash)
39    let mut iv_hasher = Sha256::new();
40    iv_hasher.update(key.as_bytes());
41    let iv_hash = iv_hasher.finalize();
42    let nonce = Nonce::from_slice(&iv_hash[..12]);
43    
44    // Encrypt the data
45    let ciphertext = cipher.encrypt(nonce, data_bytes.as_ref())
46        .map_err(|e| SdkError::Crypto(format!("Encryption failed: {:?}", e)))?;
47    
48    // Return base64-encoded result
49    Ok(general_purpose::STANDARD.encode(ciphertext))
50}
51
52/// Encrypts a key using the salt as AES key
53/// 
54/// This function is compatible with the Go implementation's `saltKey` function.
55/// 
56/// # Arguments
57/// 
58/// * `key` - The key to encrypt
59/// * `salt` - The salt value used as encryption key
60/// 
61/// # Returns
62/// 
63/// Base64-encoded encrypted key
64pub fn salt_key(key: &str, salt: i32) -> Result<String> {
65    // Use salt to generate SHA-256 hash as AES key
66    let salt_str = salt.to_string();
67    let mut hasher = Sha256::new();
68    hasher.update(salt_str.as_bytes());
69    let salt_hash = hasher.finalize();
70    
71    // Create AES-256-GCM cipher
72    let cipher_key = Key::<Aes256Gcm>::from_slice(&salt_hash);
73    let cipher = Aes256Gcm::new(cipher_key);
74    
75    // Generate deterministic IV from salt (first 12 bytes of SHA-256 hash)
76    let mut iv_hasher = Sha256::new();
77    iv_hasher.update(salt_str.as_bytes());
78    let iv_hash = iv_hasher.finalize();
79    let nonce = Nonce::from_slice(&iv_hash[..12]);
80    
81    // Encrypt the key
82    let key_bytes = key.as_bytes();
83    let ciphertext = cipher.encrypt(nonce, key_bytes)
84        .map_err(|e| SdkError::Crypto(format!("Key encryption failed: {:?}", e)))?;
85    
86    // Return base64-encoded result
87    Ok(general_purpose::STANDARD.encode(ciphertext))
88}
89
90/// Decrypts data using AES-GCM with a deterministic IV derived from the key
91/// 
92/// This function is compatible with the Go implementation's `decryptData` function.
93/// 
94/// # Arguments
95/// 
96/// * `encrypted_data` - Base64-encoded encrypted data
97/// * `key` - The decryption key
98/// 
99/// # Returns
100/// 
101/// Decrypted JSON value
102pub fn decrypt_data(encrypted_data: &str, key: &str) -> Result<Value> {
103    // Base64 decode
104    let ciphertext = general_purpose::STANDARD.decode(encrypted_data)?;
105    
106    // Hash the key using SHA-256
107    let mut hasher = Sha256::new();
108    hasher.update(key.as_bytes());
109    let key_hash = hasher.finalize();
110    
111    // Create AES-256-GCM cipher
112    let cipher_key = Key::<Aes256Gcm>::from_slice(&key_hash);
113    let cipher = Aes256Gcm::new(cipher_key);
114    
115    // Generate deterministic IV from key (first 12 bytes of SHA-256 hash)
116    let mut iv_hasher = Sha256::new();
117    iv_hasher.update(key.as_bytes());
118    let iv_hash = iv_hasher.finalize();
119    let nonce = Nonce::from_slice(&iv_hash[..12]);
120    
121    // Decrypt the data
122    let plaintext = cipher.decrypt(nonce, ciphertext.as_ref())
123        .map_err(|e| SdkError::Crypto(format!("Decryption failed: {:?}", e)))?;
124    
125    // Parse JSON
126    let value: Value = serde_json::from_slice(&plaintext)?;
127    Ok(value)
128}
129
130/// Decrypts a salted key
131/// 
132/// This function is compatible with the Go implementation's `unsaltKey` function.
133/// 
134/// # Arguments
135/// 
136/// * `salted_key` - Base64-encoded encrypted key
137/// * `salt` - The salt value used for encryption
138/// 
139/// # Returns
140/// 
141/// Original decrypted key
142pub fn unsalt_key(salted_key: &str, salt: i32) -> Result<String> {
143    // Base64 decode
144    let ciphertext = general_purpose::STANDARD.decode(salted_key)?;
145    
146    // Use salt to generate SHA-256 hash as AES key
147    let salt_str = salt.to_string();
148    let mut hasher = Sha256::new();
149    hasher.update(salt_str.as_bytes());
150    let salt_hash = hasher.finalize();
151    
152    // Create AES-256-GCM cipher
153    let cipher_key = Key::<Aes256Gcm>::from_slice(&salt_hash);
154    let cipher = Aes256Gcm::new(cipher_key);
155    
156    // Generate deterministic IV from salt (first 12 bytes of SHA-256 hash)
157    let mut iv_hasher = Sha256::new();
158    iv_hasher.update(salt_str.as_bytes());
159    let iv_hash = iv_hasher.finalize();
160    let nonce = Nonce::from_slice(&iv_hash[..12]);
161    
162    // Decrypt the key
163    let plaintext = cipher.decrypt(nonce, ciphertext.as_ref())
164        .map_err(|e| SdkError::Crypto(format!("Key decryption failed: {:?}", e)))?;
165    
166    // Convert to string
167    String::from_utf8(plaintext)
168        .map_err(|e| SdkError::Crypto(format!("Invalid UTF-8 in decrypted key: {}", e)))
169}
170
171#[cfg(test)]
172mod tests {
173    use super::*;
174    use serde_json::json;
175    
176    #[test]
177    fn test_encrypt_decrypt_data() {
178        let data = json!({
179            "message": "Hello, World!",
180            "number": 42
181        });
182        let key = "test-key-123";
183        
184        let encrypted = encrypt_data(&data, key).unwrap();
185        let decrypted = decrypt_data(&encrypted, key).unwrap();
186        
187        assert_eq!(data, decrypted);
188    }
189    
190    #[test]
191    fn test_salt_unsalt_key() {
192        let key = "my-secret-key";
193        let salt = 123456;
194        
195        let salted = salt_key(key, salt).unwrap();
196        let unsalted = unsalt_key(&salted, salt).unwrap();
197        
198        assert_eq!(key, unsalted);
199    }
200    
201    #[test]
202    fn test_deterministic_encryption() {
203        let data = json!({"test": "value"});
204        let key = "consistent-key";
205        
206        let encrypted1 = encrypt_data(&data, key).unwrap();
207        let encrypted2 = encrypt_data(&data, key).unwrap();
208        
209        // Should be deterministic
210        assert_eq!(encrypted1, encrypted2);
211    }
212}