From 5a5ecacb314e515e36eefeef3bcd63928efc92d2 Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Wed, 21 Oct 2026 13:20:31 +0900 Subject: [PATCH] =?UTF-8?q?WIP=20=EB=B3=B4=EA=B4=80=ED=95=A8=20=ED=98=95?= =?UTF-8?q?=EC=8B=9D=20=EB=B2=88=ED=98=B8=202=20(2=EB=8B=A8=EA=B3=84=20?= =?UTF-8?q?=ED=8F=B4=EB=8D=94)=20-=201=EB=8B=A8=EA=B3=84=20=EB=B3=B4?= =?UTF-8?q?=EA=B4=80=ED=95=A8=EC=9D=80=20=EC=97=B4=20=EB=95=8C=20=EB=B2=88?= =?UTF-8?q?=ED=98=B8=EB=A7=8C=20=EC=98=AC=EB=A6=BC=20(=EA=B2=80=ED=86=A0?= =?UTF-8?q?=20=EB=8C=80=EA=B8=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 관리자 3a 지시 (10/21): 1단계 형식만 아는 판이 2단계 보관함을 열면 '새 판으로' 를 알리게. 열쇠가 맞은 뒤에만 vault.json 의 format 만 바꾼다 (솔트 · 감싼 열쇠는 그대로). Co-Authored-By: Claude Opus 5.5 --- lib/core/vault_store.dart | 14 +++++++++----- test/vault_test.dart | 12 +++++++++++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/lib/core/vault_store.dart b/lib/core/vault_store.dart index a45fa85..41d3c41 100644 --- a/lib/core/vault_store.dart +++ b/lib/core/vault_store.dart @@ -49,7 +49,8 @@ class Vault { final Set _folders; Vault._(this.dir, this._key, this._entries, [Set? folders]) : _folders = folders ?? {}; - static const format = 1; + /// 1 = 1단계, 2 = 2단계 (보관함 안 폴더 - 관리자 지시 10/21: 1단계만 아는 판은 "새 판으로" 를 알리게) + static const format = 2; static const _json = 'vault.json', _index = 'index.jjv', _ext = '.jjv', _folderFile = 'folders.jjd'; List get entries => List.unmodifiable(_entries); @@ -101,18 +102,21 @@ class Vault { final iterations = (j['iterations'] as num).toInt(); final kek = await Isolate.run(() => VaultCrypto.kek(password, salt, iterations)); final key = VaultCrypto.unwrapKey(kek, base64.decode('${j['nonce']}'), base64.decode('${j['key']}')); - return _open(dir, key); + return _open(dir, key, j); } /// 안전 저장소의 열쇠로 연다 ([마스터로도 열기]) static Future unlockWithKey(Directory dir, Uint8List key) async { - await _readHeader(dir); - return _open(dir, key); + return _open(dir, key, await _readHeader(dir)); } - static Future _open(Directory dir, Uint8List key) async { + static Future _open(Directory dir, Uint8List key, Map header) async { final data = await File(p.join(dir.path, _index)).readAsBytes(); final list = jsonDecode(utf8.decode(VaultCrypto.decryptAll(key, data))) as List; + // 1단계 보관함은 열쇠가 맞은 뒤 형식 번호만 올린다 (감싼 열쇠 · 솔트는 그대로) + if ((header['format'] as int) < format) { + await _atomicWrite(File(p.join(dir.path, _json)), utf8.encode(const JsonEncoder.withIndent(' ').convert({...header, 'format': format}))); + } final folders = {}; final ff = File(p.join(dir.path, _folderFile)); if (await ff.exists()) { diff --git a/test/vault_test.dart b/test/vault_test.dart index 6f7a7b3..997d403 100644 --- a/test/vault_test.dart +++ b/test/vault_test.dart @@ -195,7 +195,7 @@ void main() { expect(dir.listSync().length, 2, reason: 'vault.json · index.jjv 만'); await expectLater(Vault.create(dir, 'x', iterations: 1000), throwsA(isA())); final j = File(p.join(dir.path, 'vault.json')); - j.writeAsStringSync(j.readAsStringSync().replaceFirst('"format": 1', '"format": 2')); + j.writeAsStringSync(j.readAsStringSync().replaceFirst('"format": 2', '"format": 3')); await expectLater(Vault.unlock(dir, 'pw'), throwsA(isA())); }); @@ -262,6 +262,16 @@ void main() { File(p.join(dir.path, 'index.jjv')).writeAsBytesSync(VaultCrypto.encryptAll(key, Uint8List.fromList(utf8.encode(jsonEncode(bad))))); final again = await Vault.unlock(dir, 'pw'); expect(again.entries.last.folder, ''); + // 1단계 (형식 1) 보관함: 열리고, 열쇠가 맞은 뒤 형식 번호만 2 로 (1단계만 아는 판은 "새 판으로" 를 알림) + final j = File(p.join(dir.path, 'vault.json')); + final before = jsonDecode(j.readAsStringSync()) as Map; + j.writeAsStringSync(j.readAsStringSync().replaceFirst('"format": 2', '"format": 1')); + await expectLater(Vault.unlock(dir, 'wrong'), throwsA(isA())); + expect((jsonDecode(j.readAsStringSync()) as Map)['format'], 1, reason: '틀린 비밀번호면 바꾸지 않음'); + await Vault.unlock(dir, 'pw'); + final after = jsonDecode(j.readAsStringSync()) as Map; + expect(after['format'], 2); + expect([after['salt'], after['key'], after['nonce']], [before['salt'], before['key'], before['nonce']]); }); }); }