WIP 암호 보관함: PBKDF2 처음 60만 번 (관리자 결정 10/21 - OWASP 권장, 반복 수는 vault.json 에 있어 기기 확인 뒤 다시 정할 수 있음) · 시험: 같은 원본을 다시 넣어도 · 목록을 다시 쓸 때마다 새 무작위 파일 id (조각 nonce 가 (파일 열쇠, 조각 번호) 이므로 같은 id 를 다시 쓰지 않음을 보임) (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>xplore_p3b
parent
b51a17a82b
commit
9c72320737
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -134,6 +134,25 @@ void main() {
|
|||
await expectLater(Vault.unlockWithKey(dir, VaultCrypto.random(32)), throwsA(isA<VaultAuthException>()));
|
||||
});
|
||||
|
||||
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 = <String>{};
|
||||
final indexIds = <String>{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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue