Browse Source

run aes encryption in background isolate.

remove-cryptography-dependency
Herbert Poul 5 years ago
parent
commit
b225ce509e
  1. 2
      bin/kdbx.dart
  2. 38
      lib/src/crypto/key_encrypter_kdf.dart
  3. 8
      lib/src/crypto/protected_value.dart
  4. 5
      lib/src/internal/crypto_utils.dart
  5. 47
      lib/src/kdbx_format.dart
  6. 1
      pubspec.yaml
  7. 2
      test/kdbx4_test.dart

2
bin/kdbx.dart

@ -91,7 +91,7 @@ abstract class KdbxFileCommand extends Command<void> {
final keyFile = argResults['keyfile'] as String; final keyFile = argResults['keyfile'] as String;
final keyFileData = final keyFileData =
keyFile == null ? null : await File(keyFile).readAsBytes(); keyFile == null ? null : await File(keyFile).readAsBytes();
;
final file = await KdbxFormat(Argon2Test()).read( final file = await KdbxFormat(Argon2Test()).read(
bytes, bytes,
Credentials.composite(ProtectedValue.fromString(password), keyFileData), Credentials.composite(ProtectedValue.fromString(password), keyFileData),

38
lib/src/crypto/key_encrypter_kdf.dart

@ -2,6 +2,7 @@ import 'dart:convert';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:crypto/crypto.dart' as crypto; import 'package:crypto/crypto.dart' as crypto;
import 'package:isolate/isolate_runner.dart';
import 'package:kdbx/kdbx.dart'; import 'package:kdbx/kdbx.dart';
import 'package:kdbx/src/crypto/argon2.dart'; import 'package:kdbx/src/crypto/argon2.dart';
import 'package:kdbx/src/internal/byte_utils.dart'; import 'package:kdbx/src/internal/byte_utils.dart';
@ -91,7 +92,7 @@ class KeyEncrypterKdf {
break; break;
case KdfType.Aes: case KdfType.Aes:
_logger.fine('Must be using aes'); _logger.fine('Must be using aes');
return encryptAes(key, kdfParameters); return await encryptAes(key, kdfParameters);
} }
throw UnsupportedError( throw UnsupportedError(
'unsupported KDF Type UUID ${ByteUtils.toHexList(uuid)}.'); 'unsupported KDF Type UUID ${ByteUtils.toHexList(uuid)}.');
@ -112,16 +113,43 @@ class KeyEncrypterKdf {
)); ));
} }
Uint8List encryptAes(Uint8List key, VarDictionary kdfParameters) { Future<Uint8List> encryptAes(
Uint8List key, VarDictionary kdfParameters) async {
final encryptionKey = KdfField.salt.read(kdfParameters); final encryptionKey = KdfField.salt.read(kdfParameters);
final rounds = KdfField.rounds.read(kdfParameters); final rounds = KdfField.rounds.read(kdfParameters);
assert(encryptionKey.length == 32); assert(encryptionKey.length == 32);
return await encryptAesAsync(EncryptAesArgs(encryptionKey, key, rounds));
}
static Future<Uint8List> 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()) final cipher = ECBBlockCipher(AESFastEngine())
..init(true, KeyParameter(encryptionKey)); ..init(true, KeyParameter(args.encryptionKey));
var transformedKey = key; var transformedKey = args.key;
for (int i = 0; i < rounds; i++) {
final rounds = args.rounds;
for (var i = 0; i < rounds; i++) {
transformedKey = AesHelper.processBlocks(cipher, transformedKey); transformedKey = AesHelper.processBlocks(cipher, transformedKey);
} }
return crypto.sha256.convert(transformedKey).bytes as Uint8List; 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;
}

8
lib/src/crypto/protected_value.dart

@ -35,14 +35,14 @@ class ProtectedValue implements StringValue {
ProtectedValue(this._value, this._salt); ProtectedValue(this._value, this._salt);
factory ProtectedValue.fromString(String value) { factory ProtectedValue.fromString(String value) {
final Uint8List valueBytes = utf8.encode(value) as Uint8List; final valueBytes = utf8.encode(value) as Uint8List;
final Uint8List salt = _randomBytes(valueBytes.length); final salt = _randomBytes(valueBytes.length);
return ProtectedValue(_xor(valueBytes, salt), salt); return ProtectedValue(_xor(valueBytes, salt), salt);
} }
factory ProtectedValue.fromBinary(Uint8List value) { factory ProtectedValue.fromBinary(Uint8List value) {
final Uint8List salt = _randomBytes(value.length); final salt = _randomBytes(value.length);
return ProtectedValue(_xor(value, salt), salt); return ProtectedValue(_xor(value, salt), salt);
} }
@ -63,7 +63,7 @@ class ProtectedValue implements StringValue {
static Uint8List _xor(Uint8List a, Uint8List b) { static Uint8List _xor(Uint8List a, Uint8List b) {
assert(a.length == b.length); assert(a.length == b.length);
final ret = Uint8List(a.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]; ret[i] = a[i] ^ b[i];
} }
return ret; return ret;

5
lib/src/internal/crypto_utils.dart

@ -1,10 +1,7 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:logging/logging.dart';
import 'package:pointycastle/export.dart'; import 'package:pointycastle/export.dart';
final _logger = Logger('crypto_utils');
/// https://gist.github.com/proteye/e54eef1713e1fe9123d1eb04c0a5cf9b /// https://gist.github.com/proteye/e54eef1713e1fe9123d1eb04c0a5cf9b
class AesHelper { class AesHelper {
static const CBC_MODE = 'CBC'; static const CBC_MODE = 'CBC';
@ -87,11 +84,9 @@ class AesHelper {
static Uint8List processBlocks(BlockCipher cipher, Uint8List inp) { static Uint8List processBlocks(BlockCipher cipher, Uint8List inp) {
final out = Uint8List(inp.lengthInBytes); final out = Uint8List(inp.lengthInBytes);
_logger.finest('Starting processBlocks. (${inp.lengthInBytes})');
for (var offset = 0; offset < inp.lengthInBytes;) { for (var offset = 0; offset < inp.lengthInBytes;) {
offset += cipher.processBlock(inp, offset, out, offset); offset += cipher.processBlock(inp, offset, out, offset);
} }
_logger.finest('Done processBlocks.');
return out; return out;
} }

47
lib/src/kdbx_format.dart

@ -162,7 +162,7 @@ class KdbxFile {
final gen = ProtectedSaltGenerator(streamKey); final gen = ProtectedSaltGenerator(streamKey);
body.meta.headerHash.set(headerHash.buffer); body.meta.headerHash.set(headerHash.buffer);
body.writeV3(writer, this, gen); await body.writeV3(writer, this, gen);
} else if (header.versionMajor <= 4) { } else if (header.versionMajor <= 4) {
final headerBytes = writer.output.toBytes(); final headerBytes = writer.output.toBytes();
writer.writeBytes(headerHash); writer.writeBytes(headerHash);
@ -227,16 +227,15 @@ class KdbxBody extends KdbxNode {
final KdbxMeta meta; final KdbxMeta meta;
final KdbxGroup rootGroup; final KdbxGroup rootGroup;
void writeV3(WriterHelper writer, KdbxFile kdbxFile, Future<void> writeV3(WriterHelper writer, KdbxFile kdbxFile,
ProtectedSaltGenerator saltGenerator) { ProtectedSaltGenerator saltGenerator) async {
final xml = generateXml(saltGenerator); final xml = generateXml(saltGenerator);
final xmlBytes = utf8.encode(xml.toXmlString()); final xmlBytes = utf8.encode(xml.toXmlString());
final Uint8List compressedBytes = final compressedBytes = (kdbxFile.header.compression == Compression.gzip
(kdbxFile.header.compression == Compression.gzip
? GZipCodec().encode(xmlBytes) ? GZipCodec().encode(xmlBytes)
: xmlBytes) as Uint8List; : xmlBytes) as Uint8List;
final encrypted = _encryptV3(kdbxFile, compressedBytes); final encrypted = await _encryptV3(kdbxFile, compressedBytes);
writer.writeBytes(encrypted); writer.writeBytes(encrypted);
} }
@ -246,8 +245,7 @@ class KdbxBody extends KdbxNode {
final xml = generateXml(saltGenerator); final xml = generateXml(saltGenerator);
kdbxFile.header.writeInnerHeader(bodyWriter); kdbxFile.header.writeInnerHeader(bodyWriter);
bodyWriter.writeBytes(utf8.encode(xml.toXmlString()) as Uint8List); bodyWriter.writeBytes(utf8.encode(xml.toXmlString()) as Uint8List);
final Uint8List compressedBytes = final compressedBytes = (kdbxFile.header.compression == Compression.gzip
(kdbxFile.header.compression == Compression.gzip
? GZipCodec().encode(bodyWriter.output.toBytes()) ? GZipCodec().encode(bodyWriter.output.toBytes())
: bodyWriter.output.toBytes()) as Uint8List; : bodyWriter.output.toBytes()) as Uint8List;
final encrypted = _encryptV4( final encrypted = _encryptV4(
@ -260,15 +258,16 @@ class KdbxBody extends KdbxNode {
writer.writeBytes(transformed); writer.writeBytes(transformed);
} }
Uint8List _encryptV3(KdbxFile kdbxFile, Uint8List compressedBytes) { Future<Uint8List> _encryptV3(
KdbxFile kdbxFile, Uint8List compressedBytes) async {
final byteWriter = WriterHelper(); final byteWriter = WriterHelper();
byteWriter.writeBytes( byteWriter.writeBytes(
kdbxFile.header.fields[HeaderFields.StreamStartBytes].bytes); kdbxFile.header.fields[HeaderFields.StreamStartBytes].bytes);
HashedBlockReader.writeBlocks(ReaderHelper(compressedBytes), byteWriter); HashedBlockReader.writeBlocks(ReaderHelper(compressedBytes), byteWriter);
final bytes = byteWriter.output.toBytes(); final bytes = byteWriter.output.toBytes();
final masterKey = final masterKey = await KdbxFormat._generateMasterKeyV3(
KdbxFormat._generateMasterKeyV3(kdbxFile.header, kdbxFile.credentials); kdbxFile.header, kdbxFile.credentials);
final encrypted = KdbxFormat._encryptDataAes(masterKey, bytes, final encrypted = KdbxFormat._encryptDataAes(masterKey, bytes,
kdbxFile.header.fields[HeaderFields.EncryptionIV].bytes); kdbxFile.header.fields[HeaderFields.EncryptionIV].bytes);
return encrypted; return encrypted;
@ -369,7 +368,7 @@ class KdbxFormat {
final reader = ReaderHelper(input); final reader = ReaderHelper(input);
final header = KdbxHeader.read(reader); final header = KdbxHeader.read(reader);
if (header.versionMajor == 3) { if (header.versionMajor == 3) {
return _loadV3(header, reader, credentials); return await _loadV3(header, reader, credentials);
} else if (header.versionMajor == 4) { } else if (header.versionMajor == 4) {
return await _loadV4(header, reader, credentials); return await _loadV4(header, reader, credentials);
} else { } else {
@ -380,10 +379,10 @@ class KdbxFormat {
} }
} }
KdbxFile _loadV3( Future<KdbxFile> _loadV3(
KdbxHeader header, ReaderHelper reader, Credentials credentials) { KdbxHeader header, ReaderHelper reader, Credentials credentials) async {
// _getMasterKeyV3(header, credentials); // _getMasterKeyV3(header, credentials);
final masterKey = _generateMasterKeyV3(header, credentials); final masterKey = await _generateMasterKeyV3(header, credentials);
final encryptedPayload = reader.readRemaining(); final encryptedPayload = reader.readRemaining();
final content = _decryptContent(header, masterKey, encryptedPayload); final content = _decryptContent(header, masterKey, encryptedPayload);
final blocks = HashedBlockReader.readBlocks(ReaderHelper(content)); final blocks = HashedBlockReader.readBlocks(ReaderHelper(content));
@ -442,7 +441,7 @@ class KdbxFormat {
final writer = WriterHelper(); final writer = WriterHelper();
final reader = ReaderHelper(data); final reader = ReaderHelper(data);
const blockSize = 1024 * 1024; const blockSize = 1024 * 1024;
int blockIndex = 0; var blockIndex = 0;
while (true) { while (true) {
final blockData = reader.readBytesUpTo(blockSize); final blockData = reader.readBytesUpTo(blockSize);
final calculatedHash = _hmacHashForBlock(hmacKey, blockIndex, blockData); final calculatedHash = _hmacHashForBlock(hmacKey, blockIndex, blockData);
@ -481,7 +480,7 @@ class KdbxFormat {
Uint8List hmacBlockTransformer(Uint8List hmacKey, ReaderHelper reader) { Uint8List hmacBlockTransformer(Uint8List hmacKey, ReaderHelper reader) {
final ret = <int>[]; final ret = <int>[];
int blockIndex = 0; var blockIndex = 0;
while (true) { while (true) {
final blockHash = reader.readBytes(32); final blockHash = reader.readBytes(32);
final blockLength = reader.readUint32(); final blockLength = reader.readUint32();
@ -654,23 +653,17 @@ class KdbxFormat {
return AesHelper.processBlocks(encryptCypher, paddedBytes); return AesHelper.processBlocks(encryptCypher, paddedBytes);
} }
static Uint8List _generateMasterKeyV3( static Future<Uint8List> _generateMasterKeyV3(
KdbxHeader header, Credentials credentials) { KdbxHeader header, Credentials credentials) async {
final rounds = ReaderHelper.singleUint64( final rounds = ReaderHelper.singleUint64(
header.fields[HeaderFields.TransformRounds].bytes); header.fields[HeaderFields.TransformRounds].bytes);
final seed = header.fields[HeaderFields.TransformSeed].bytes; final seed = header.fields[HeaderFields.TransformSeed].bytes;
final masterSeed = header.fields[HeaderFields.MasterSeed].bytes; final masterSeed = header.fields[HeaderFields.MasterSeed].bytes;
_logger.finer( _logger.finer(
'Rounds: $rounds (${ByteUtils.toHexList(header.fields[HeaderFields.TransformRounds].bytes)})'); '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 final masterKey = crypto.sha256
.convert(Uint8List.fromList(masterSeed + transformedKey)) .convert(Uint8List.fromList(masterSeed + transformedKey))
.bytes as Uint8List; .bytes as Uint8List;

1
pubspec.yaml

@ -19,6 +19,7 @@ dependencies:
meta: '>=1.0.0 <2.0.0' meta: '>=1.0.0 <2.0.0'
clock: '>=1.0.0 <2.0.0' clock: '>=1.0.0 <2.0.0'
convert: '>=2.0.0 <3.0.0' convert: '>=2.0.0 <3.0.0'
isolate: '>=2.0.3 <3.0.0'
collection: '>=1.14.0 <2.0.0' collection: '>=1.14.0 <2.0.0'

2
test/kdbx4_test.dart

@ -93,7 +93,7 @@ void main() {
final file = await kdbxFormat.read( final file = await kdbxFormat.read(
data, Credentials(ProtectedValue.fromString('asdf'))); data, Credentials(ProtectedValue.fromString('asdf')));
expect(file.body.rootGroup.entries, hasLength(1)); expect(file.body.rootGroup.entries, hasLength(1));
}); }, skip: 'Takes tooo long, too many iterations.');
}); });
group('Writing', () { group('Writing', () {
test('Create and save', () async { test('Create and save', () async {

Loading…
Cancel
Save