diff --git a/lib/core/vault_crypto.dart b/lib/core/vault_crypto.dart index 0954e63..86e99ce 100644 --- a/lib/core/vault_crypto.dart +++ b/lib/core/vault_crypto.dart @@ -9,8 +9,8 @@ import '../app/master_lock.dart' show pbkdf2Sha256; /// X-plore 비교 5-2 암호 보관함 (사용자 결정 10/21, 관리자 결정 · 설계안: 위키 프로젝트/암호 보관함 · 루트 설계안 (10-21)). /// 암호 부분만 (파일 · 화면 없음 - 시험하기 쉽게): -/// - 비밀번호 → PBKDF2-SHA256 (반복 수는 vault.json 에, 처음 30만 번) → 열쇠 감싸는 열쇠 (KEK) → AES-256-GCM 으로 보관함 열쇠 (무작위 32바이트) 를 감쌈 -/// - 파일마다 무작위 id (16바이트) → 파일 열쇠 = HMAC-SHA256(보관함 열쇠, "jjv-file|" + id) +/// - 비밀번호 → PBKDF2-SHA256 (반복 수는 vault.json 에, 처음 60만 번 - OWASP 권장, 관리자 결정 10/21) → 열쇠 감싸는 열쇠 (KEK) → AES-256-GCM 으로 보관함 열쇠 (무작위 32바이트) 를 감쌈 +/// - 파일마다 (넣을 때마다 · 목록을 다시 쓸 때마다) 새 무작위 id (16바이트) → 파일 열쇠 - 조각 nonce 가 (파일 열쇠, 조각 번호) 라 같은 id 를 다시 쓰지 않는다 = HMAC-SHA256(보관함 열쇠, "jjv-file|" + id) /// - 파일 = 머리 + 조각들. 조각마다 AES-256-GCM (nonce = 0 8바이트 + 조각 번호, AAD = 머리 + 조각 번호 + 마지막 표시) - /// 조각을 바꾸거나 · 바꿔 끼우거나 · 끝을 자르면 풀 때 알아챈다 /// 암호 라이브러리: pointycastle (MIT, dartssh2 를 통해 이미 앱에 있던 판) @@ -18,7 +18,7 @@ class VaultCrypto { static const magic = 'JJV1'; static const version = 1; static const defaultChunk = 1024 * 1024; - static const defaultIterations = 300000; + static const defaultIterations = 600000; static const _tag = 16; static final _rng = Random.secure(); diff --git a/test/vault_test.dart b/test/vault_test.dart index 44315fb..450f86b 100644 --- a/test/vault_test.dart +++ b/test/vault_test.dart @@ -134,6 +134,25 @@ void main() { await expectLater(Vault.unlockWithKey(dir, VaultCrypto.random(32)), throwsA(isA())); }); + test('같은 파일 id 를 다시 쓰지 않음: 같은 원본을 두 번 넣어도 · 목록을 다시 쓸 때마다 새 무작위 id (조각 nonce 가 (파일 열쇠, 조각 번호) 이므로)', () async { + final v = await Vault.create(dir, 'pw', iterations: 1000); + final s = src('same.bin', 2000); + Uint8List idOf(String name) => Uint8List.fromList( + File(p.join(dir.path, name)).readAsBytesSync().sublist(9, VaultCrypto.headerLength)); + final ids = {}; + final indexIds = {toHex(idOf('index.jjv'))}; + for (var i = 0; i < 5; i++) { + final e = await v.add(s.path, chunk: 1024); + ids.add(toHex(idOf('${e.blob}.jjv'))); + indexIds.add(toHex(idOf('index.jjv'))); + } + await v.rename(v.entries.first, 'x'); + indexIds.add(toHex(idOf('index.jjv'))); + expect(ids.length, 5, reason: '넣을 때마다 다른 id'); + expect(indexIds.length, 7, reason: '목록도 다시 쓸 때마다 다른 id'); + expect(ids.intersection(indexIds), isEmpty); + }); + test('바뀐 조각이 있는 파일은 꺼내지 않고 (부분 파일도 남기지 않음) 오류', () async { final v = await Vault.create(dir, 'pw', iterations: 1000); final e = await v.add(src('a.bin', 3000).path, chunk: 1024);