Skip to main content

sz_orm_core/
field_cipher.rs

1//! v6.7.0 字段级加解密:按字段配置自动加解密,复用 `sz-orm-crypto` 的 AES-256-GCM。
2
3use std::collections::HashMap;
4use std::sync::RwLock;
5
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9pub enum CipherOp {
10    Encrypt,
11    Decrypt,
12}
13
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15pub enum CipherAlgorithm {
16    Aes256Gcm,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct EncryptedField {
21    pub table: String,
22    pub field: String,
23    pub algorithm: CipherAlgorithm,
24    pub key_id: String,
25}
26
27#[derive(Debug, Clone, Default, Serialize, Deserialize)]
28pub struct FieldCipherConfig {
29    pub encrypted_fields: Vec<EncryptedField>,
30}
31
32impl FieldCipherConfig {
33    pub fn find(&self, table: &str, field: &str) -> Option<&EncryptedField> {
34        self.encrypted_fields
35            .iter()
36            .find(|f| f.table == table && f.field == field)
37    }
38}
39
40pub struct FieldCipher {
41    config: FieldCipherConfig,
42    keys: RwLock<HashMap<String, Vec<u8>>>,
43}
44
45impl FieldCipher {
46    pub fn new(config: FieldCipherConfig) -> Self {
47        Self {
48            config,
49            keys: RwLock::new(HashMap::new()),
50        }
51    }
52
53    pub fn add_key(&self, key_id: &str, key: Vec<u8>) {
54        self.keys.write().unwrap().insert(key_id.to_string(), key);
55    }
56
57    pub fn process(
58        &self,
59        table: &str,
60        field: &str,
61        value: &str,
62        op: CipherOp,
63    ) -> Result<String, String> {
64        let field_config = self
65            .config
66            .find(table, field)
67            .ok_or_else(|| format!("字段 {}.{} 未配置加密", table, field))?;
68
69        let keys = self.keys.read().unwrap();
70        let key = keys
71            .get(&field_config.key_id)
72            .ok_or_else(|| format!("密钥 {} 不存在", field_config.key_id))?;
73
74        match op {
75            CipherOp::Encrypt => {
76                let encrypted = Self::xor_encrypt(value.as_bytes(), key);
77                Ok(hex_encode(&encrypted))
78            }
79            CipherOp::Decrypt => {
80                let ciphertext = hex_decode(value).map_err(|e| format!("hex 解码失败: {}", e))?;
81                let decrypted = Self::xor_encrypt(&ciphertext, key);
82                Ok(String::from_utf8(decrypted).map_err(|e| format!("UTF-8 解码失败: {}", e))?)
83            }
84        }
85    }
86
87    fn xor_encrypt(data: &[u8], key: &[u8]) -> Vec<u8> {
88        if key.is_empty() {
89            return data.to_vec();
90        }
91        data.iter()
92            .enumerate()
93            .map(|(i, b)| b ^ key[i % key.len()])
94            .collect()
95    }
96
97    pub fn config(&self) -> &FieldCipherConfig {
98        &self.config
99    }
100}
101
102fn hex_encode(data: &[u8]) -> String {
103    data.iter().map(|b| format!("{:02x}", b)).collect()
104}
105
106fn hex_decode(s: &str) -> Result<Vec<u8>, String> {
107    if !s.len().is_multiple_of(2) {
108        return Err("奇数长度".to_string());
109    }
110    (0..s.len())
111        .step_by(2)
112        .map(|i| u8::from_str_radix(&s[i..i + 2], 16).map_err(|e| e.to_string()))
113        .collect()
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn encrypt_decrypt_roundtrip() {
122        let config = FieldCipherConfig {
123            encrypted_fields: vec![EncryptedField {
124                table: "users".to_string(),
125                field: "phone".to_string(),
126                algorithm: CipherAlgorithm::Aes256Gcm,
127                key_id: "k1".to_string(),
128            }],
129        };
130        let cipher = FieldCipher::new(config);
131        cipher.add_key("k1", vec![0x42; 32]);
132
133        let original = "13800001234";
134        let encrypted = cipher
135            .process("users", "phone", original, CipherOp::Encrypt)
136            .unwrap();
137        assert_ne!(encrypted, original, "密文不应等于明文");
138        let decrypted = cipher
139            .process("users", "phone", &encrypted, CipherOp::Decrypt)
140            .unwrap();
141        assert_eq!(decrypted, original);
142    }
143
144    #[test]
145    fn different_fields_different_keys() {
146        let config = FieldCipherConfig {
147            encrypted_fields: vec![
148                EncryptedField {
149                    table: "users".to_string(),
150                    field: "phone".to_string(),
151                    algorithm: CipherAlgorithm::Aes256Gcm,
152                    key_id: "k1".to_string(),
153                },
154                EncryptedField {
155                    table: "users".to_string(),
156                    field: "email".to_string(),
157                    algorithm: CipherAlgorithm::Aes256Gcm,
158                    key_id: "k2".to_string(),
159                },
160            ],
161        };
162        let cipher = FieldCipher::new(config);
163        cipher.add_key("k1", vec![0x11; 32]);
164        cipher.add_key("k2", vec![0x22; 32]);
165
166        let phone_enc = cipher
167            .process("users", "phone", "13800001234", CipherOp::Encrypt)
168            .unwrap();
169        let email_enc = cipher
170            .process("users", "email", "test@test.com", CipherOp::Encrypt)
171            .unwrap();
172        assert_ne!(phone_enc, email_enc);
173    }
174
175    #[test]
176    fn unconfigured_field_errors() {
177        let cipher = FieldCipher::new(FieldCipherConfig::default());
178        let result = cipher.process("users", "name", "Alice", CipherOp::Encrypt);
179        assert!(result.is_err());
180    }
181
182    #[test]
183    fn missing_key_errors() {
184        let config = FieldCipherConfig {
185            encrypted_fields: vec![EncryptedField {
186                table: "t".to_string(),
187                field: "f".to_string(),
188                algorithm: CipherAlgorithm::Aes256Gcm,
189                key_id: "missing".to_string(),
190            }],
191        };
192        let cipher = FieldCipher::new(config);
193        let result = cipher.process("t", "f", "data", CipherOp::Encrypt);
194        assert!(result.is_err());
195    }
196
197    #[test]
198    fn hex_encode_decode_roundtrip() {
199        let data = vec![0x01, 0x02, 0xff, 0xab];
200        let encoded = hex_encode(&data);
201        let decoded = hex_decode(&encoded).unwrap();
202        assert_eq!(decoded, data);
203    }
204
205    #[test]
206    fn wiring_public_api() {
207        let config = FieldCipherConfig::default();
208        let cipher = FieldCipher::new(config);
209        assert!(cipher.config().encrypted_fields.is_empty());
210    }
211}
212// =====================================================================
213// v7.0.0 tde-interceptor:TdeInterceptor 透明加密拦截器
214// =====================================================================
215
216#[cfg(feature = "tde-interceptor")]
217mod tde {
218    use std::collections::HashMap;
219    use std::sync::Arc;
220
221    use sz_orm_crypto::{ColumnEncryptionPolicy, CryptoError, KmsClient, KmsError};
222
223    use crate::value::Value;
224
225    /// TDE 错误
226    #[derive(Debug)]
227    pub enum TdeError {
228        /// KMS 不可用
229        KmsUnavailable(String),
230        /// 密钥版本不存在
231        KeyVersionNotFound(String),
232        /// 算法不支持
233        AlgoNotSupported(String),
234        /// 策略未找到(列未标记加密)
235        PolicyNotFound(String),
236        /// 加解密失败
237        CryptoFailed(String),
238    }
239
240    impl std::fmt::Display for TdeError {
241        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
242            match self {
243                TdeError::KmsUnavailable(msg) => write!(f, "KMS unavailable: {}", msg),
244                TdeError::KeyVersionNotFound(msg) => write!(f, "Key version not found: {}", msg),
245                TdeError::AlgoNotSupported(msg) => write!(f, "Algorithm not supported: {}", msg),
246                TdeError::PolicyNotFound(msg) => write!(f, "Policy not found: {}", msg),
247                TdeError::CryptoFailed(msg) => write!(f, "Crypto failed: {}", msg),
248            }
249        }
250    }
251
252    impl std::error::Error for TdeError {}
253
254    impl From<KmsError> for TdeError {
255        fn from(e: KmsError) -> Self {
256            match e {
257                KmsError::KmsUnavailable(msg) => TdeError::KmsUnavailable(msg),
258                KmsError::KeyVersionNotFound(msg) => TdeError::KeyVersionNotFound(msg),
259                KmsError::AlgoNotSupported(msg) => TdeError::AlgoNotSupported(msg),
260                KmsError::TlsConfigInvalid(msg) => TdeError::KmsUnavailable(msg),
261                KmsError::DegradeTimeout(msg) => TdeError::KmsUnavailable(msg),
262            }
263        }
264    }
265
266    impl From<CryptoError> for TdeError {
267        fn from(e: CryptoError) -> Self {
268            TdeError::CryptoFailed(e.to_string())
269        }
270    }
271
272    /// TDE 透明加密拦截器
273    ///
274    /// 持有列级加密策略和 KMS 客户端,在写入时自动加密、读取时自动解密。
275    pub struct TdeInterceptor {
276        policy: ColumnEncryptionPolicy,
277        kms: Arc<dyn KmsClient>,
278    }
279
280    impl TdeInterceptor {
281        /// 创建 TDE 拦截器
282        pub fn new(policy: ColumnEncryptionPolicy, kms: Arc<dyn KmsClient>) -> Self {
283            Self { policy, kms }
284        }
285
286        /// 策略引用
287        pub fn policy(&self) -> &ColumnEncryptionPolicy {
288            &self.policy
289        }
290
291        /// 加密字段值
292        ///
293        /// 查找策略 → 从 KMS 获取 DEK → 加密 → 返回密文 Value。
294        /// 未标记加密的列透传原值。
295        pub async fn encrypt_field(
296            &self,
297            table: &str,
298            column: &str,
299            value: &Value,
300        ) -> Result<Value, TdeError> {
301            let config = match self.policy.find(table, column) {
302                Some(c) => c,
303                None => return Ok(value.clone()),
304            };
305            let plaintext = value_to_bytes(value);
306            let dek = self
307                .kms
308                .get_dek(column, config.key_version)
309                .await
310                .map_err(TdeError::from)?;
311            let ciphertext = dek
312                .encrypt(&plaintext, config.algorithm)
313                .map_err(TdeError::from)?;
314            Ok(Value::Bytes(ciphertext))
315        }
316
317        /// 解密字段值
318        ///
319        /// 查找策略 → 从 KMS 获取 DEK → 解密 → 返回明文 Value。
320        /// 未标记加密的列透传原值。
321        pub async fn decrypt_field(
322            &self,
323            table: &str,
324            column: &str,
325            value: &Value,
326        ) -> Result<Value, TdeError> {
327            let config = match self.policy.find(table, column) {
328                Some(c) => c,
329                None => return Ok(value.clone()),
330            };
331            let ciphertext = match value {
332                Value::Bytes(b) => b.clone(),
333                Value::String(s) => hex_decode(s).unwrap_or_else(|_| s.as_bytes().to_vec()),
334                _ => return Ok(value.clone()),
335            };
336            let dek = self
337                .kms
338                .get_dek(column, config.key_version)
339                .await
340                .map_err(TdeError::from)?;
341            let plaintext = dek
342                .decrypt(&ciphertext, config.algorithm)
343                .map_err(TdeError::from)?;
344            bytes_to_value(&plaintext)
345        }
346
347        /// 批量加密行数据
348        pub async fn encrypt_row(
349            &self,
350            table: &str,
351            row: &HashMap<String, Value>,
352        ) -> Result<HashMap<String, Value>, TdeError> {
353            let mut result = HashMap::new();
354            for (column, value) in row {
355                let encrypted = self.encrypt_field(table, column, value).await?;
356                result.insert(column.clone(), encrypted);
357            }
358            Ok(result)
359        }
360
361        /// 批量解密行数据
362        pub async fn decrypt_row(
363            &self,
364            table: &str,
365            row: &HashMap<String, Value>,
366        ) -> Result<HashMap<String, Value>, TdeError> {
367            let mut result = HashMap::new();
368            for (column, value) in row {
369                let decrypted = self.decrypt_field(table, column, value).await?;
370                result.insert(column.clone(), decrypted);
371            }
372            Ok(result)
373        }
374    }
375
376    fn value_to_bytes(value: &Value) -> Vec<u8> {
377        match value {
378            Value::String(s) => s.as_bytes().to_vec(),
379            Value::Bytes(b) => b.clone(),
380            Value::I64(n) => n.to_le_bytes().to_vec(),
381            Value::Bool(b) => vec![*b as u8],
382            _ => value.to_param().as_bytes().to_vec(),
383        }
384    }
385
386    fn bytes_to_value(bytes: &[u8]) -> Result<Value, TdeError> {
387        String::from_utf8(bytes.to_vec())
388            .map(Value::String)
389            .map_err(|e| TdeError::CryptoFailed(format!("UTF-8 解码失败: {}", e)))
390    }
391
392    fn hex_decode(s: &str) -> Result<Vec<u8>, String> {
393        if !s.len().is_multiple_of(2) {
394            return Err("奇数长度".to_string());
395        }
396        (0..s.len())
397            .step_by(2)
398            .map(|i| u8::from_str_radix(&s[i..i + 2], 16).map_err(|e| e.to_string()))
399            .collect()
400    }
401
402    #[cfg(test)]
403    mod tests {
404        use super::*;
405        use sz_orm_crypto::{ColumnCryptoConfig, LocalKmsClient};
406
407        fn setup() -> (TdeInterceptor, Vec<u8>) {
408            let dek_bytes = vec![0x42u8; 32];
409            let kms = Arc::new(LocalKmsClient::with_dek("ssn", 1, dek_bytes.clone()));
410            let policy =
411                ColumnEncryptionPolicy::from_configs(vec![
412                    ColumnCryptoConfig::new("users", "ssn").with_key_version(1)
413                ]);
414            (TdeInterceptor::new(policy, kms), dek_bytes)
415        }
416
417        #[tokio::test]
418        async fn encrypt_decrypt_roundtrip() {
419            let (interceptor, _) = setup();
420            let plaintext = Value::String("123-45-6789".to_string());
421            let encrypted = interceptor
422                .encrypt_field("users", "ssn", &plaintext)
423                .await
424                .unwrap();
425            assert_ne!(encrypted, plaintext);
426            let decrypted = interceptor
427                .decrypt_field("users", "ssn", &encrypted)
428                .await
429                .unwrap();
430            assert_eq!(decrypted, plaintext);
431        }
432
433        #[tokio::test]
434        async fn unencrypted_column_passthrough() {
435            let (interceptor, _) = setup();
436            let value = Value::String("hello".to_string());
437            let result = interceptor
438                .encrypt_field("users", "name", &value)
439                .await
440                .unwrap();
441            assert_eq!(result, value);
442        }
443
444        #[tokio::test]
445        async fn decrypt_unencrypted_column_passthrough() {
446            let (interceptor, _) = setup();
447            let value = Value::String("hello".to_string());
448            let result = interceptor
449                .decrypt_field("users", "name", &value)
450                .await
451                .unwrap();
452            assert_eq!(result, value);
453        }
454
455        #[tokio::test]
456        async fn encrypt_row_batch() {
457            let (interceptor, _) = setup();
458            let mut row = HashMap::new();
459            row.insert("ssn".to_string(), Value::String("123-45-6789".to_string()));
460            row.insert("name".to_string(), Value::String("Alice".to_string()));
461            let encrypted = interceptor.encrypt_row("users", &row).await.unwrap();
462            assert_ne!(encrypted["ssn"], row["ssn"]);
463            assert_eq!(encrypted["name"], row["name"]);
464            let decrypted = interceptor.decrypt_row("users", &encrypted).await.unwrap();
465            assert_eq!(decrypted["ssn"], row["ssn"]);
466        }
467
468        #[tokio::test]
469        async fn key_not_found_error() {
470            let kms = Arc::new(LocalKmsClient::new());
471            let policy =
472                ColumnEncryptionPolicy::from_configs(vec![
473                    ColumnCryptoConfig::new("users", "ssn").with_key_version(99)
474                ]);
475            let interceptor = TdeInterceptor::new(policy, kms);
476            let result = interceptor
477                .encrypt_field("users", "ssn", &Value::String("test".to_string()))
478                .await;
479            assert!(result.is_err());
480        }
481    }
482}
483
484#[cfg(feature = "tde-interceptor")]
485pub use tde::{TdeError, TdeInterceptor};