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. 55
      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 keyFileData =
keyFile == null ? null : await File(keyFile).readAsBytes();
;
final file = await KdbxFormat(Argon2Test()).read(
bytes,
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 '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<Uint8List> 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<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())
..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;
}

8
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;

5
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;
}

55
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<void> 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<Uint8List> _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<KdbxFile> _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>[];
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<Uint8List> _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;

1
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'

2
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 {

Loading…
Cancel
Save