Browse Source

add support for decrypting with key file. (and password + keyfile)

remove-cryptography-dependency
Herbert Poul 5 years ago
parent
commit
08711987f5
  1. 5
      lib/src/crypto/protected_value.dart
  2. 74
      lib/src/kdbx_format.dart
  3. 38
      test/kdbx_test.dart
  4. BIN
      test/password-and-keyfile.kdbx
  5. 9
      test/password-and-keyfile.key

5
lib/src/crypto/protected_value.dart

@ -41,6 +41,11 @@ class ProtectedValue implements StringValue {
return ProtectedValue(_xor(valueBytes, salt), salt); return ProtectedValue(_xor(valueBytes, salt), salt);
} }
factory ProtectedValue.fromBinary(Uint8List value) {
final Uint8List salt = _randomBytes(value.length);
return ProtectedValue(_xor(value, salt), salt);
}
static final random = Random.secure(); static final random = Random.secure();
final Uint8List _value; final Uint8List _value;

74
lib/src/kdbx_format.dart

@ -22,7 +22,13 @@ import 'kdbx_object.dart';
final _logger = Logger('kdbx.format'); final _logger = Logger('kdbx.format');
abstract class Credentials { abstract class Credentials {
factory Credentials(ProtectedValue password) => PasswordCredentials(password); factory Credentials(ProtectedValue password) =>
Credentials.composite(password, null); //PasswordCredentials(password);
factory Credentials.composite(ProtectedValue password, Uint8List keyFile) =>
KeyFileComposite(
password: password == null ? null : PasswordCredentials(password),
keyFile: keyFile == null ? null : KeyFileCredentials(keyFile),
);
Credentials._(); Credentials._();
@ -31,18 +37,70 @@ abstract class Credentials {
Uint8List getHash(); Uint8List getHash();
} }
class PasswordCredentials implements Credentials { class KeyFileComposite implements Credentials {
KeyFileComposite({@required this.password, @required this.keyFile});
PasswordCredentials password;
KeyFileCredentials keyFile;
Uint8List getHash() {
final buffer = [...?password?.getBinary(), ...?keyFile?.getBinary()];
return crypto.sha256.convert(buffer).bytes as Uint8List;
// final output = convert.AccumulatorSink<crypto.Digest>();
// final input = crypto.sha256.startChunkedConversion(output);
//// input.add(password.getHash());
// input.add(buffer);
// input.close();
// return output.events.single.bytes as Uint8List;
}
}
abstract class CredentialsPart {
Uint8List getBinary();
}
class KeyFileCredentials implements CredentialsPart {
factory KeyFileCredentials(Uint8List keyFileContents) {
final keyFileAsString = utf8.decode(keyFileContents);
try {
if (_hexValuePattern.hasMatch(keyFileAsString)) {
return KeyFileCredentials._(ProtectedValue.fromBinary(
convert.hex.decode(keyFileAsString) as Uint8List));
}
final xmlContent = xml.parse(keyFileAsString);
final key = xmlContent.findAllElements('Key').single;
final dataString = key.findElements('Data').single;
final dataBytes = base64.decode(dataString.text);
_logger.finer('Decoded base64 of keyfile.');
return KeyFileCredentials._(ProtectedValue.fromBinary(dataBytes));
} catch (e, stackTrace) {
_logger.warning(
'Unable to parse key file as hex or XML, use as is.', e, stackTrace);
final bytes = crypto.sha256.convert(keyFileContents).bytes as Uint8List;
return KeyFileCredentials._(ProtectedValue.fromBinary(bytes));
}
}
KeyFileCredentials._(this._keyFileValue);
static final RegExp _hexValuePattern = RegExp(r'/^[a-f\d]{64}$/i');
final ProtectedValue _keyFileValue;
@override
Uint8List getBinary() {
return _keyFileValue.binaryValue;
// return crypto.sha256.convert(_keyFileValue.binaryValue).bytes as Uint8List;
}
}
class PasswordCredentials implements CredentialsPart {
PasswordCredentials(this._password); PasswordCredentials(this._password);
final ProtectedValue _password; final ProtectedValue _password;
@override @override
Uint8List getHash() { Uint8List getBinary() {
final output = convert.AccumulatorSink<crypto.Digest>(); return _password.hash;
final input = crypto.sha256.startChunkedConversion(output);
input.add(_password.hash);
input.close();
return output.events.single.bytes as Uint8List;
} }
} }

38
test/kdbx_test.dart

@ -15,7 +15,6 @@ class FakeProtectedSaltGenerator implements ProtectedSaltGenerator {
@override @override
String encryptToBase64(String plainValue) => 'fake'; String encryptToBase64(String plainValue) => 'fake';
} }
void main() { void main() {
@ -25,27 +24,46 @@ void main() {
setUp(() {}); setUp(() {});
test('First Test', () async { test('First Test', () async {
final data = await File('test/FooBar.kdbx').readAsBytes() as Uint8List; final data = await File('test/FooBar.kdbx').readAsBytes();
KdbxFormat.read(data, Credentials(ProtectedValue.fromString('FooBar'))); KdbxFormat.read(data, Credentials(ProtectedValue.fromString('FooBar')));
}); });
}); });
group('Composite key', () {
test('Read with PW and keyfile', () async {
final keyFileBytes =
await File('test/password-and-keyfile.key').readAsBytes();
final cred = Credentials.composite(
ProtectedValue.fromString('asdf'), keyFileBytes);
final data = await File('test/password-and-keyfile.kdbx').readAsBytes();
final file = KdbxFormat.read(data, cred);
expect(file.body.rootGroup.entries.length, 1);
});
});
group('Creating', () { group('Creating', () {
test('Simple create', () { test('Simple create', () {
final kdbx = KdbxFormat.create(Credentials(ProtectedValue.fromString('FooBar')), 'CreateTest'); final kdbx = KdbxFormat.create(
Credentials(ProtectedValue.fromString('FooBar')), 'CreateTest');
expect(kdbx, isNotNull); expect(kdbx, isNotNull);
expect(kdbx.body.rootGroup, isNotNull); expect(kdbx.body.rootGroup, isNotNull);
expect(kdbx.body.rootGroup.name.get(), 'CreateTest'); expect(kdbx.body.rootGroup.name.get(), 'CreateTest');
expect(kdbx.body.meta.databaseName.get(), 'CreateTest'); expect(kdbx.body.meta.databaseName.get(), 'CreateTest');
print(kdbx.body.generateXml(FakeProtectedSaltGenerator()).toXmlString(pretty: true)); print(kdbx.body
.generateXml(FakeProtectedSaltGenerator())
.toXmlString(pretty: true));
}); });
test('Create Entry', () { test('Create Entry', () {
final kdbx = KdbxFormat.create(Credentials(ProtectedValue.fromString('FooBar')), 'CreateTest'); final kdbx = KdbxFormat.create(
Credentials(ProtectedValue.fromString('FooBar')), 'CreateTest');
final rootGroup = kdbx.body.rootGroup; final rootGroup = kdbx.body.rootGroup;
final entry = KdbxEntry.create(kdbx, rootGroup); final entry = KdbxEntry.create(kdbx, rootGroup);
rootGroup.addEntry(entry); rootGroup.addEntry(entry);
entry.setString(KdbxKey('Password'), ProtectedValue.fromString('LoremIpsum')); entry.setString(
print(kdbx.body.generateXml(FakeProtectedSaltGenerator()).toXmlString(pretty: true)); KdbxKey('Password'), ProtectedValue.fromString('LoremIpsum'));
print(kdbx.body
.generateXml(FakeProtectedSaltGenerator())
.toXmlString(pretty: true));
}); });
}); });
@ -65,7 +83,11 @@ void main() {
// print(ByteUtils.toHexList(saved)); // print(ByteUtils.toHexList(saved));
final kdbx = KdbxFormat.read(saved, credentials); final kdbx = KdbxFormat.read(saved, credentials);
expect(kdbx.body.rootGroup.entries.first.getString(KdbxKey('Password')).getText(), 'LoremIpsum'); expect(
kdbx.body.rootGroup.entries.first
.getString(KdbxKey('Password'))
.getText(),
'LoremIpsum');
File('test.kdbx').writeAsBytesSync(saved); File('test.kdbx').writeAsBytesSync(saved);
}); });
}); });

BIN
test/password-and-keyfile.kdbx

Binary file not shown.

9
test/password-and-keyfile.key

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<KeyFile>
<Meta>
<Version>1.00</Version>
</Meta>
<Key>
<Data>SZgOtaihUic9M/gfyhLVPkX5HQ1ipuSSYe5ej4SIRv8=</Data>
</Key>
</KeyFile>
Loading…
Cancel
Save