From 9c72320737c11c69aa6f70f6fd7265105fd71b6f Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Wed, 21 Oct 2026 11:22:49 +0900 Subject: [PATCH] =?UTF-8?q?WIP=20=EC=95=94=ED=98=B8=20=EB=B3=B4=EA=B4=80?= =?UTF-8?q?=ED=95=A8:=20PBKDF2=20=EC=B2=98=EC=9D=8C=2060=EB=A7=8C=20?= =?UTF-8?q?=EB=B2=88=20(=EA=B4=80=EB=A6=AC=EC=9E=90=20=EA=B2=B0=EC=A0=95?= =?UTF-8?q?=2010/21=20-=20OWASP=20=EA=B6=8C=EC=9E=A5,=20=EB=B0=98=EB=B3=B5?= =?UTF-8?q?=20=EC=88=98=EB=8A=94=20vault.json=20=EC=97=90=20=EC=9E=88?= =?UTF-8?q?=EC=96=B4=20=EA=B8=B0=EA=B8=B0=20=ED=99=95=EC=9D=B8=20=EB=92=A4?= =?UTF-8?q?=20=EB=8B=A4=EC=8B=9C=20=EC=A0=95=ED=95=A0=20=EC=88=98=20?= =?UTF-8?q?=EC=9E=88=EC=9D=8C)=20=C2=B7=20=EC=8B=9C=ED=97=98:=20=EA=B0=99?= =?UTF-8?q?=EC=9D=80=20=EC=9B=90=EB=B3=B8=EC=9D=84=20=EB=8B=A4=EC=8B=9C=20?= =?UTF-8?q?=EB=84=A3=EC=96=B4=EB=8F=84=20=C2=B7=20=EB=AA=A9=EB=A1=9D?= =?UTF-8?q?=EC=9D=84=20=EB=8B=A4=EC=8B=9C=20=EC=93=B8=20=EB=95=8C=EB=A7=88?= =?UTF-8?q?=EB=8B=A4=20=EC=83=88=20=EB=AC=B4=EC=9E=91=EC=9C=84=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20id=20(=EC=A1=B0=EA=B0=81=20nonce=20=EA=B0=80=20(?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=97=B4=EC=87=A0,=20=EC=A1=B0=EA=B0=81?= =?UTF-8?q?=20=EB=B2=88=ED=98=B8)=20=EC=9D=B4=EB=AF=80=EB=A1=9C=20?= =?UTF-8?q?=EA=B0=99=EC=9D=80=20id=20=EB=A5=BC=20=EB=8B=A4=EC=8B=9C=20?= =?UTF-8?q?=EC=93=B0=EC=A7=80=20=EC=95=8A=EC=9D=8C=EC=9D=84=20=EB=B3=B4?= =?UTF-8?q?=EC=9E=84)=20(=EA=B2=80=ED=86=A0=20=EB=8C=80=EA=B8=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- lib/core/vault_crypto.dart | 6 +++--- test/vault_test.dart | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) 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);