diff --git a/bin/kdbx.dart b/bin/kdbx.dart index caf1273..85c285b 100644 --- a/bin/kdbx.dart +++ b/bin/kdbx.dart @@ -91,7 +91,7 @@ abstract class KdbxFileCommand extends Command { final keyFile = argResults['keyfile'] as String; final keyFileData = keyFile == null ? null : await File(keyFile).readAsBytes(); - ; + final file = await KdbxFormat(Argon2Test()).read( bytes, Credentials.composite(ProtectedValue.fromString(password), keyFileData), diff --git a/lib/src/crypto/key_encrypter_kdf.dart b/lib/src/crypto/key_encrypter_kdf.dart index 81ff6c8..3e848fb 100644 --- a/lib/src/crypto/key_encrypter_kdf.dart +++ b/lib/src/crypto/key_encrypter_kdf.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:crypto/crypto.dart' as crypto; +import 'package:isolate/isolate_runner.dart'; import 'package:kdbx/kdbx.dart'; import 'package:kdbx/src/crypto/argon2.dart'; import 'package:kdbx/src/internal/byte_utils.dart'; @@ -91,7 +92,7 @@ class KeyEncrypterKdf { break; case KdfType.Aes: _logger.fine('Must be using aes'); - return encryptAes(key, kdfParameters); + return await encryptAes(key, kdfParameters); } throw UnsupportedError( 'unsupported KDF Type UUID ${ByteUtils.toHexList(uuid)}.'); @@ -112,16 +113,43 @@ class KeyEncrypterKdf { )); } - Uint8List encryptAes(Uint8List key, VarDictionary kdfParameters) { + Future encryptAes( + Uint8List key, VarDictionary kdfParameters) async { final encryptionKey = KdfField.salt.read(kdfParameters); final rounds = KdfField.rounds.read(kdfParameters); assert(encryptionKey.length == 32); + return await encryptAesAsync(EncryptAesArgs(encryptionKey, key, rounds)); + } + + static Future encryptAesAsync(EncryptAesArgs args) async { + final runner = await IsolateRunner.spawn(); + try { + _logger + .finest('Starting encryptAes for ${args.rounds} rounds in isolate.'); + return await runner.run(_encryptAesSync, args); + } finally { + _logger.finest('Done aes encrypt.'); + await runner.kill(); + } + } + + static Uint8List _encryptAesSync(EncryptAesArgs args) { final cipher = ECBBlockCipher(AESFastEngine()) - ..init(true, KeyParameter(encryptionKey)); - var transformedKey = key; - for (int i = 0; i < rounds; i++) { + ..init(true, KeyParameter(args.encryptionKey)); + var transformedKey = args.key; + + final rounds = args.rounds; + for (var i = 0; i < rounds; i++) { transformedKey = AesHelper.processBlocks(cipher, transformedKey); } return crypto.sha256.convert(transformedKey).bytes as Uint8List; } } + +class EncryptAesArgs { + EncryptAesArgs(this.encryptionKey, this.key, this.rounds); + + final Uint8List encryptionKey; + final Uint8List key; + final int rounds; +} diff --git a/lib/src/crypto/protected_value.dart b/lib/src/crypto/protected_value.dart index 4c6d6c5..83bfbb7 100644 --- a/lib/src/crypto/protected_value.dart +++ b/lib/src/crypto/protected_value.dart @@ -35,14 +35,14 @@ class ProtectedValue implements StringValue { ProtectedValue(this._value, this._salt); factory ProtectedValue.fromString(String value) { - final Uint8List valueBytes = utf8.encode(value) as Uint8List; - final Uint8List salt = _randomBytes(valueBytes.length); + final valueBytes = utf8.encode(value) as Uint8List; + final salt = _randomBytes(valueBytes.length); return ProtectedValue(_xor(valueBytes, salt), salt); } factory ProtectedValue.fromBinary(Uint8List value) { - final Uint8List salt = _randomBytes(value.length); + final salt = _randomBytes(value.length); return ProtectedValue(_xor(value, salt), salt); } @@ -63,7 +63,7 @@ class ProtectedValue implements StringValue { static Uint8List _xor(Uint8List a, Uint8List b) { assert(a.length == b.length); final ret = Uint8List(a.length); - for (int i = 0; i < a.length; i++) { + for (var i = 0; i < a.length; i++) { ret[i] = a[i] ^ b[i]; } return ret; diff --git a/lib/src/internal/crypto_utils.dart b/lib/src/internal/crypto_utils.dart index 3b59c20..b43d20c 100644 --- a/lib/src/internal/crypto_utils.dart +++ b/lib/src/internal/crypto_utils.dart @@ -1,10 +1,7 @@ import 'dart:typed_data'; -import 'package:logging/logging.dart'; import 'package:pointycastle/export.dart'; -final _logger = Logger('crypto_utils'); - /// https://gist.github.com/proteye/e54eef1713e1fe9123d1eb04c0a5cf9b class AesHelper { static const CBC_MODE = 'CBC'; @@ -87,11 +84,9 @@ class AesHelper { static Uint8List processBlocks(BlockCipher cipher, Uint8List inp) { final out = Uint8List(inp.lengthInBytes); - _logger.finest('Starting processBlocks. (${inp.lengthInBytes})'); for (var offset = 0; offset < inp.lengthInBytes;) { offset += cipher.processBlock(inp, offset, out, offset); } - _logger.finest('Done processBlocks.'); return out; } diff --git a/lib/src/kdbx_format.dart b/lib/src/kdbx_format.dart index ff0d1af..863dc23 100644 --- a/lib/src/kdbx_format.dart +++ b/lib/src/kdbx_format.dart @@ -162,7 +162,7 @@ class KdbxFile { final gen = ProtectedSaltGenerator(streamKey); body.meta.headerHash.set(headerHash.buffer); - body.writeV3(writer, this, gen); + await body.writeV3(writer, this, gen); } else if (header.versionMajor <= 4) { final headerBytes = writer.output.toBytes(); writer.writeBytes(headerHash); @@ -227,16 +227,15 @@ class KdbxBody extends KdbxNode { final KdbxMeta meta; final KdbxGroup rootGroup; - void writeV3(WriterHelper writer, KdbxFile kdbxFile, - ProtectedSaltGenerator saltGenerator) { + Future writeV3(WriterHelper writer, KdbxFile kdbxFile, + ProtectedSaltGenerator saltGenerator) async { final xml = generateXml(saltGenerator); final xmlBytes = utf8.encode(xml.toXmlString()); - final Uint8List compressedBytes = - (kdbxFile.header.compression == Compression.gzip - ? GZipCodec().encode(xmlBytes) - : xmlBytes) as Uint8List; + final compressedBytes = (kdbxFile.header.compression == Compression.gzip + ? GZipCodec().encode(xmlBytes) + : xmlBytes) as Uint8List; - final encrypted = _encryptV3(kdbxFile, compressedBytes); + final encrypted = await _encryptV3(kdbxFile, compressedBytes); writer.writeBytes(encrypted); } @@ -246,10 +245,9 @@ class KdbxBody extends KdbxNode { final xml = generateXml(saltGenerator); kdbxFile.header.writeInnerHeader(bodyWriter); bodyWriter.writeBytes(utf8.encode(xml.toXmlString()) as Uint8List); - final Uint8List compressedBytes = - (kdbxFile.header.compression == Compression.gzip - ? GZipCodec().encode(bodyWriter.output.toBytes()) - : bodyWriter.output.toBytes()) as Uint8List; + final compressedBytes = (kdbxFile.header.compression == Compression.gzip + ? GZipCodec().encode(bodyWriter.output.toBytes()) + : bodyWriter.output.toBytes()) as Uint8List; final encrypted = _encryptV4( kdbxFile, compressedBytes, @@ -260,15 +258,16 @@ class KdbxBody extends KdbxNode { writer.writeBytes(transformed); } - Uint8List _encryptV3(KdbxFile kdbxFile, Uint8List compressedBytes) { + Future _encryptV3( + KdbxFile kdbxFile, Uint8List compressedBytes) async { final byteWriter = WriterHelper(); byteWriter.writeBytes( kdbxFile.header.fields[HeaderFields.StreamStartBytes].bytes); HashedBlockReader.writeBlocks(ReaderHelper(compressedBytes), byteWriter); final bytes = byteWriter.output.toBytes(); - final masterKey = - KdbxFormat._generateMasterKeyV3(kdbxFile.header, kdbxFile.credentials); + final masterKey = await KdbxFormat._generateMasterKeyV3( + kdbxFile.header, kdbxFile.credentials); final encrypted = KdbxFormat._encryptDataAes(masterKey, bytes, kdbxFile.header.fields[HeaderFields.EncryptionIV].bytes); return encrypted; @@ -369,7 +368,7 @@ class KdbxFormat { final reader = ReaderHelper(input); final header = KdbxHeader.read(reader); if (header.versionMajor == 3) { - return _loadV3(header, reader, credentials); + return await _loadV3(header, reader, credentials); } else if (header.versionMajor == 4) { return await _loadV4(header, reader, credentials); } else { @@ -380,10 +379,10 @@ class KdbxFormat { } } - KdbxFile _loadV3( - KdbxHeader header, ReaderHelper reader, Credentials credentials) { + Future _loadV3( + KdbxHeader header, ReaderHelper reader, Credentials credentials) async { // _getMasterKeyV3(header, credentials); - final masterKey = _generateMasterKeyV3(header, credentials); + final masterKey = await _generateMasterKeyV3(header, credentials); final encryptedPayload = reader.readRemaining(); final content = _decryptContent(header, masterKey, encryptedPayload); final blocks = HashedBlockReader.readBlocks(ReaderHelper(content)); @@ -442,7 +441,7 @@ class KdbxFormat { final writer = WriterHelper(); final reader = ReaderHelper(data); const blockSize = 1024 * 1024; - int blockIndex = 0; + var blockIndex = 0; while (true) { final blockData = reader.readBytesUpTo(blockSize); final calculatedHash = _hmacHashForBlock(hmacKey, blockIndex, blockData); @@ -481,7 +480,7 @@ class KdbxFormat { Uint8List hmacBlockTransformer(Uint8List hmacKey, ReaderHelper reader) { final ret = []; - int blockIndex = 0; + var blockIndex = 0; while (true) { final blockHash = reader.readBytes(32); final blockLength = reader.readUint32(); @@ -654,23 +653,17 @@ class KdbxFormat { return AesHelper.processBlocks(encryptCypher, paddedBytes); } - static Uint8List _generateMasterKeyV3( - KdbxHeader header, Credentials credentials) { + static Future _generateMasterKeyV3( + KdbxHeader header, Credentials credentials) async { final rounds = ReaderHelper.singleUint64( header.fields[HeaderFields.TransformRounds].bytes); final seed = header.fields[HeaderFields.TransformSeed].bytes; final masterSeed = header.fields[HeaderFields.MasterSeed].bytes; _logger.finer( 'Rounds: $rounds (${ByteUtils.toHexList(header.fields[HeaderFields.TransformRounds].bytes)})'); + final transformedKey = await KeyEncrypterKdf.encryptAesAsync( + EncryptAesArgs(seed, credentials.getHash(), rounds)); - final cipher = ECBBlockCipher(AESFastEngine()) - ..init(true, KeyParameter(seed)); - final pwHash = credentials.getHash(); - var transformedKey = pwHash; - for (int i = 0; i < rounds; i++) { - transformedKey = AesHelper.processBlocks(cipher, transformedKey); - } - transformedKey = crypto.sha256.convert(transformedKey).bytes as Uint8List; final masterKey = crypto.sha256 .convert(Uint8List.fromList(masterSeed + transformedKey)) .bytes as Uint8List; diff --git a/pubspec.yaml b/pubspec.yaml index 394e98f..13318b9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,6 +19,7 @@ dependencies: meta: '>=1.0.0 <2.0.0' clock: '>=1.0.0 <2.0.0' convert: '>=2.0.0 <3.0.0' + isolate: '>=2.0.3 <3.0.0' collection: '>=1.14.0 <2.0.0' diff --git a/test/kdbx4_test.dart b/test/kdbx4_test.dart index f8a3d15..4555494 100644 --- a/test/kdbx4_test.dart +++ b/test/kdbx4_test.dart @@ -93,7 +93,7 @@ void main() { final file = await kdbxFormat.read( data, Credentials(ProtectedValue.fromString('asdf'))); expect(file.body.rootGroup.entries, hasLength(1)); - }); + }, skip: 'Takes tooo long, too many iterations.'); }); group('Writing', () { test('Create and save', () async {