backup_suite/crypto/
encryption.rs

1//! # AES-256-GCM 暗号化エンジン
2//!
3//! 認証付き暗号化を提供する高セキュリティ暗号化システム
4
5use super::key_management::MasterKey;
6use crate::error::{BackupError, Result};
7use aes_gcm::aead::Aead;
8use aes_gcm::{Aes256Gcm, Key, KeyInit, Nonce};
9use rand::RngCore;
10use std::io::{Read, Write};
11
12// デバッグビルド専用: Nonce衝突検出トラッカー
13// リリースビルドではコンパイル時に完全削除される(オーバーヘッドゼロ)
14#[cfg(debug_assertions)]
15use std::collections::HashSet;
16#[cfg(debug_assertions)]
17use std::sync::Mutex;
18
19#[cfg(debug_assertions)]
20static NONCE_TRACKER: Mutex<Option<HashSet<[u8; 12]>>> = Mutex::new(None);
21
22/// 暗号化設定
23#[derive(Debug, Clone)]
24pub struct EncryptionConfig {
25    /// チャンクサイズ(バイト) - 大容量ファイル処理用
26    pub chunk_size: usize,
27    /// バッファサイズ(バイト)
28    pub buffer_size: usize,
29}
30
31impl Default for EncryptionConfig {
32    fn default() -> Self {
33        Self {
34            chunk_size: 1024 * 1024, // 1MB チャンク
35            buffer_size: 64 * 1024,  // 64KB バッファ
36        }
37    }
38}
39
40/// 暗号化されたデータ
41#[derive(Debug, Clone)]
42pub struct EncryptedData {
43    /// ナンス(12バイト)
44    pub nonce: [u8; 12],
45    /// ソルト(16バイト)
46    pub salt: [u8; 16],
47    /// 暗号化されたデータ + 認証タグ
48    pub ciphertext: Vec<u8>,
49    /// ファイル長(元のデータサイズ)
50    pub original_size: u64,
51}
52
53impl EncryptedData {
54    /// バイナリ形式にシリアライズ
55    #[must_use]
56    pub fn to_bytes(&self) -> Vec<u8> {
57        let mut result = Vec::with_capacity(44 + self.ciphertext.len());
58        result.extend_from_slice(&self.nonce); // 12バイト
59        result.extend_from_slice(&self.salt); // 16バイト
60        result.extend_from_slice(&self.original_size.to_le_bytes()); // 8バイト
61        result.extend_from_slice(&(self.ciphertext.len() as u64).to_le_bytes()); // 8バイト
62        result.extend_from_slice(&self.ciphertext); // 可変長
63        result
64    }
65
66    /// バイナリ形式からデシリアライズ
67    pub fn from_bytes(data: &[u8]) -> Result<Self> {
68        if data.len() < 44 {
69            return Err(BackupError::EncryptionError(
70                "暗号化データが短すぎます".to_string(),
71            ));
72        }
73
74        let mut nonce = [0u8; 12];
75        nonce.copy_from_slice(&data[0..12]);
76
77        let mut salt = [0u8; 16];
78        salt.copy_from_slice(&data[12..28]);
79
80        let original_size = u64::from_le_bytes([
81            data[28], data[29], data[30], data[31], data[32], data[33], data[34], data[35],
82        ]);
83
84        let ciphertext_len = u64::from_le_bytes([
85            data[36], data[37], data[38], data[39], data[40], data[41], data[42], data[43],
86        ]) as usize;
87
88        // オーバーフロー対策: checked_add を使用
89        let expected_len = match 44usize.checked_add(ciphertext_len) {
90            Some(len) => len,
91            None => {
92                return Err(BackupError::EncryptionError(
93                    "暗号化データの長さが不正です(オーバーフロー)".to_string(),
94                ));
95            }
96        };
97
98        if data.len() != expected_len {
99            return Err(BackupError::EncryptionError(
100                "暗号化データの長さが一致しません".to_string(),
101            ));
102        }
103
104        let ciphertext = data[44..].to_vec();
105
106        Ok(Self {
107            nonce,
108            salt,
109            ciphertext,
110            original_size,
111        })
112    }
113}
114
115/// AES-256-GCM 暗号化エンジン
116pub struct EncryptionEngine {
117    config: EncryptionConfig,
118}
119
120impl EncryptionEngine {
121    /// 新しい暗号化エンジンを作成
122    #[must_use]
123    pub fn new(config: EncryptionConfig) -> Self {
124        Self { config }
125    }
126
127    /// ランダムなナンスを生成(内部用)
128    ///
129    /// デバッグビルドでは、生成された全Nonceを追跡し、衝突を検出します。
130    /// リリースビルドでは、追跡コードはコンパイル時に完全削除されます(オーバーヘッドゼロ)。
131    fn generate_nonce() -> [u8; 12] {
132        let mut nonce = [0u8; 12];
133        rand::rng().fill_bytes(&mut nonce);
134
135        // デバッグビルド専用: Nonce衝突検出
136        // リリースビルドではこのブロック全体がコンパイル時に削除される
137        #[cfg(debug_assertions)]
138        {
139            let mut tracker = NONCE_TRACKER.lock().unwrap();
140            let set = tracker.get_or_insert_with(HashSet::new);
141
142            assert!(set.insert(nonce),
143                    "\n\
144                    ╔══════════════════════════════════════════════════════════════╗\n\
145                    ║  🚨 CRITICAL SECURITY VIOLATION: Nonce Collision Detected! ║\n\
146                    ╚══════════════════════════════════════════════════════════════╝\n\
147                    \n\
148                    Nonce (hex): {:02x?}\n\
149                    \n\
150                    ⚠️  SECURITY IMPACT:\n\
151                    This is a CRITICAL security vulnerability in AES-256-GCM encryption.\n\
152                    Nonce reuse completely breaks the confidentiality and authenticity guarantees.\n\
153                    \n\
154                    An attacker can:\n\
155                    - Decrypt encrypted data without the key\n\
156                    - Forge authenticated messages\n\
157                    - Recover the encryption key\n\
158                    \n\
159                    📊 STATISTICS:\n\
160                    - Total unique nonces generated so far: {}\n\
161                    - Collision detected on nonce #{}\n\
162                    \n\
163                    ℹ️  DEBUG BUILD ONLY:\n\
164                    This panic only occurs in debug builds to help detect bugs during development.\n\
165                    Release builds have zero overhead (this code is removed at compile time).\n\
166                    \n\
167                    🔧 NEXT STEPS:\n\
168                    1. Check if this is a test scenario (intentional collision test)\n\
169                    2. If not, investigate random number generation (rand crate)\n\
170                    3. Review recent changes to generate_nonce() function\n\
171                    4. Run mutation testing to verify detection works correctly\n\
172                    \n",
173                    nonce,
174                    set.len(),
175                    set.len() + 1
176                );
177        }
178
179        nonce
180    }
181
182    /// ランダムなナンスを生成(公開API、pipeline.rsから使用)
183    #[must_use]
184    pub fn generate_nonce_internal() -> [u8; 12] {
185        Self::generate_nonce()
186    }
187
188    /// チャンクサイズを取得(公開API、pipeline.rsから使用)
189    #[must_use]
190    pub fn get_chunk_size(&self) -> usize {
191        self.config.chunk_size
192    }
193
194    /// データを暗号化
195    ///
196    /// # Errors
197    ///
198    /// 以下の場合にエラーを返します:
199    /// - AES-256-GCM暗号化処理が失敗した場合 (`BackupError::EncryptionError`)
200    #[allow(deprecated)]
201    pub fn encrypt(
202        &self,
203        data: &[u8],
204        master_key: &MasterKey,
205        salt: [u8; 16],
206    ) -> Result<EncryptedData> {
207        let nonce_bytes = Self::generate_nonce();
208
209        // AES-256-GCM 暗号化
210        let key = Key::<Aes256Gcm>::from_slice(master_key.as_bytes());
211        let cipher = Aes256Gcm::new(key);
212        let nonce = Nonce::from_slice(&nonce_bytes);
213
214        let ciphertext = cipher
215            .encrypt(nonce, data)
216            .map_err(|e| BackupError::EncryptionError(format!("暗号化エラー: {e}")))?;
217
218        Ok(EncryptedData {
219            nonce: nonce_bytes,
220            salt, // 渡されたsaltを使用(新しく生成しない)
221            ciphertext,
222            original_size: data.len() as u64,
223        })
224    }
225
226    /// データを復号化
227    ///
228    /// # Errors
229    ///
230    /// 以下の場合にエラーを返します:
231    /// - AES-256-GCM復号化処理が失敗した場合 (`BackupError::EncryptionError`)
232    ///   - 認証タグの検証に失敗した場合(データが改ざんされている可能性)
233    ///   - 不正なマスターキーが使用された場合
234    #[allow(deprecated)]
235    pub fn decrypt(
236        &self,
237        encrypted_data: &EncryptedData,
238        master_key: &MasterKey,
239    ) -> Result<Vec<u8>> {
240        let key = Key::<Aes256Gcm>::from_slice(master_key.as_bytes());
241        let cipher = Aes256Gcm::new(key);
242        let nonce = Nonce::from_slice(&encrypted_data.nonce);
243
244        let plaintext = cipher
245            .decrypt(nonce, encrypted_data.ciphertext.as_ref())
246            .map_err(|e| BackupError::EncryptionError(format!("復号化エラー: {e}")))?;
247
248        Ok(plaintext)
249    }
250
251    /// ストリーミング暗号化(大容量ファイル用)
252    ///
253    /// # Errors
254    ///
255    /// 以下の場合にエラーを返します:
256    /// - ファイル読み込みエラー (`BackupError::IoError`)
257    /// - ファイル書き込みエラー (`BackupError::IoError`)
258    /// - チャンク毎のAES-256-GCM暗号化処理が失敗した場合 (`BackupError::EncryptionError`)
259    #[allow(deprecated)]
260    pub fn encrypt_stream<R: Read, W: Write>(
261        &self,
262        mut reader: R,
263        mut writer: W,
264        master_key: &MasterKey,
265    ) -> Result<EncryptedData> {
266        let nonce_bytes = Self::generate_nonce();
267        let salt = crate::crypto::key_management::KeyDerivation::generate_salt();
268
269        // ヘッダー情報を書き込み
270        writer.write_all(&nonce_bytes)?;
271        writer.write_all(&salt)?;
272
273        let key = Key::<Aes256Gcm>::from_slice(master_key.as_bytes());
274        let cipher = Aes256Gcm::new(key);
275
276        let mut total_size = 0u64;
277        let mut encrypted_chunks = Vec::new();
278        let mut buffer = vec![0u8; self.config.chunk_size];
279
280        loop {
281            let bytes_read = reader.read(&mut buffer)?;
282            if bytes_read == 0 {
283                break;
284            }
285
286            total_size += bytes_read as u64;
287
288            // チャンク毎に異なるナンスを使用(u64カウンター)
289            let mut chunk_nonce = nonce_bytes;
290            let chunk_index = encrypted_chunks.len() as u64;
291            chunk_nonce[4..12].copy_from_slice(&chunk_index.to_le_bytes());
292
293            #[allow(deprecated)]
294            let nonce = Nonce::from_slice(&chunk_nonce);
295            let chunk_ciphertext = cipher
296                .encrypt(nonce, &buffer[..bytes_read])
297                .map_err(|e| BackupError::EncryptionError(format!("チャンク暗号化エラー: {e}")))?;
298
299            // チャンクサイズと暗号化データを書き込み
300            writer.write_all(&(chunk_ciphertext.len() as u32).to_le_bytes())?;
301            writer.write_all(&chunk_ciphertext)?;
302
303            encrypted_chunks.push(chunk_ciphertext);
304        }
305
306        Ok(EncryptedData {
307            nonce: nonce_bytes,
308            salt,
309            ciphertext: encrypted_chunks.into_iter().flatten().collect(),
310            original_size: total_size,
311        })
312    }
313
314    /// ストリーミング復号化(大容量ファイル用)
315    ///
316    /// # Errors
317    ///
318    /// 以下の場合にエラーを返します:
319    /// - ヘッダー情報(ナンス・ソルト)の読み取りエラー (`BackupError::IoError`)
320    /// - チャンクサイズまたはチャンクデータの読み取りエラー (`BackupError::IoError`)
321    /// - ファイル書き込みエラー (`BackupError::IoError`)
322    /// - チャンク毎のAES-256-GCM復号化処理が失敗した場合 (`BackupError::EncryptionError`)
323    ///   - 認証タグの検証に失敗した場合(データが改ざんされている可能性)
324    ///   - 不正なマスターキーが使用された場合
325    #[allow(deprecated)]
326    pub fn decrypt_stream<R: Read, W: Write>(
327        &self,
328        mut reader: R,
329        mut writer: W,
330        master_key: &MasterKey,
331    ) -> Result<u64> {
332        // ヘッダー情報を読み取り
333        let mut nonce_bytes = [0u8; 12];
334        let mut salt = [0u8; 16];
335        reader.read_exact(&mut nonce_bytes)?;
336        reader.read_exact(&mut salt)?;
337
338        let key = Key::<Aes256Gcm>::from_slice(master_key.as_bytes());
339        let cipher = Aes256Gcm::new(key);
340
341        let mut total_decrypted = 0u64;
342        let mut chunk_index = 0u64;
343
344        loop {
345            // チャンクサイズを読み取り
346            let mut chunk_size_bytes = [0u8; 4];
347            match reader.read_exact(&mut chunk_size_bytes) {
348                Ok(_) => {}
349                Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => break,
350                Err(e) => return Err(BackupError::IoError(e)),
351            }
352
353            let chunk_size = u32::from_le_bytes(chunk_size_bytes) as usize;
354            let mut chunk_data = vec![0u8; chunk_size];
355            reader.read_exact(&mut chunk_data)?;
356
357            // チャンク毎のナンスを再生成(u64カウンター)
358            let mut chunk_nonce = nonce_bytes;
359            chunk_nonce[4..12].copy_from_slice(&chunk_index.to_le_bytes());
360
361            #[allow(deprecated)]
362            let nonce = Nonce::from_slice(&chunk_nonce);
363            let plaintext = cipher
364                .decrypt(nonce, chunk_data.as_ref())
365                .map_err(|e| BackupError::EncryptionError(format!("チャンク復号化エラー: {e}")))?;
366
367            writer.write_all(&plaintext)?;
368            total_decrypted += plaintext.len() as u64;
369            chunk_index += 1;
370        }
371
372        Ok(total_decrypted)
373    }
374}
375
376impl Default for EncryptionEngine {
377    fn default() -> Self {
378        Self::new(EncryptionConfig::default())
379    }
380}
381
382#[cfg(test)]
383mod tests {
384    use super::*;
385
386    #[test]
387    fn test_encryption_decryption() {
388        let engine = EncryptionEngine::default();
389        let master_key = MasterKey::generate();
390        let original_data = b"Hello, World! This is a test message.";
391        let salt = [0u8; 16]; // テスト用のsalt
392
393        let encrypted = engine.encrypt(original_data, &master_key, salt).unwrap();
394        let decrypted = engine.decrypt(&encrypted, &master_key).unwrap();
395
396        assert_eq!(original_data, decrypted.as_slice());
397        assert_eq!(encrypted.original_size, original_data.len() as u64);
398    }
399
400    #[test]
401    fn test_encrypted_data_serialization() {
402        let engine = EncryptionEngine::default();
403        let master_key = MasterKey::generate();
404        let original_data = b"Test data for serialization";
405        let salt = [1u8; 16]; // テスト用のsalt
406
407        let encrypted = engine.encrypt(original_data, &master_key, salt).unwrap();
408        let serialized = encrypted.to_bytes();
409        let deserialized = EncryptedData::from_bytes(&serialized).unwrap();
410
411        let decrypted = engine.decrypt(&deserialized, &master_key).unwrap();
412        assert_eq!(original_data, decrypted.as_slice());
413    }
414
415    #[test]
416    fn test_different_keys_fail() {
417        let engine = EncryptionEngine::default();
418        let master_key1 = MasterKey::generate();
419        let master_key2 = MasterKey::generate();
420        let original_data = b"Secret message";
421        let salt = [2u8; 16]; // テスト用のsalt
422
423        let encrypted = engine.encrypt(original_data, &master_key1, salt).unwrap();
424
425        // 異なるキーでの復号化は失敗する
426        assert!(engine.decrypt(&encrypted, &master_key2).is_err());
427    }
428
429    #[test]
430    fn test_stream_encryption() {
431        use std::io::Cursor;
432
433        let engine = EncryptionEngine::default();
434        let master_key = MasterKey::generate();
435        let original_data = b"This is a longer message for stream testing. ".repeat(100);
436
437        let reader = Cursor::new(&original_data);
438        let mut encrypted_buffer = Vec::new();
439        let encrypted_meta = engine
440            .encrypt_stream(reader, &mut encrypted_buffer, &master_key)
441            .unwrap();
442
443        assert_eq!(encrypted_meta.original_size, original_data.len() as u64);
444
445        let encrypted_reader = Cursor::new(&encrypted_buffer);
446        let mut decrypted_buffer = Vec::new();
447        let decrypted_size = engine
448            .decrypt_stream(encrypted_reader, &mut decrypted_buffer, &master_key)
449            .unwrap();
450
451        assert_eq!(decrypted_size, original_data.len() as u64);
452        assert_eq!(original_data, decrypted_buffer);
453    }
454}