Herbert Poul
5 years ago
14 changed files with 641 additions and 37 deletions
@ -1,5 +1,5 @@ |
|||||||
import 'package:kdbx/kdbx.dart'; |
import 'package:kdbx/kdbx.dart'; |
||||||
|
|
||||||
void main() { |
void main() { |
||||||
KdbxFormat.read(null, null); |
KdbxFormat().read(null, null); |
||||||
} |
} |
||||||
|
@ -0,0 +1,113 @@ |
|||||||
|
import 'dart:convert'; |
||||||
|
import 'dart:typed_data'; |
||||||
|
|
||||||
|
import 'package:kdbx/kdbx.dart'; |
||||||
|
import 'package:kdbx/src/internal/byte_utils.dart'; |
||||||
|
import 'package:kdbx/src/kdbx_var_dictionary.dart'; |
||||||
|
import 'package:logging/logging.dart'; |
||||||
|
|
||||||
|
final _logger = Logger('key_encrypter_kdf'); |
||||||
|
|
||||||
|
enum KdfType { |
||||||
|
Argon2, |
||||||
|
Aes, |
||||||
|
} |
||||||
|
|
||||||
|
class KdfField<T> { |
||||||
|
KdfField(this.field, this.type); |
||||||
|
|
||||||
|
final String field; |
||||||
|
final ValueType<T> type; |
||||||
|
|
||||||
|
static final salt = KdfField('S', ValueType.typeBytes); |
||||||
|
static final parallelism = KdfField('P', ValueType.typeUInt32); |
||||||
|
static final memory = KdfField('M', ValueType.typeUInt64); |
||||||
|
static final iterations = KdfField('I', ValueType.typeUInt64); |
||||||
|
static final version = KdfField('V', ValueType.typeUInt32); |
||||||
|
static final secretKey = KdfField('K', ValueType.typeBytes); |
||||||
|
static final assocData = KdfField('A', ValueType.typeBytes); |
||||||
|
static final rounds = KdfField('R', ValueType.typeInt64); |
||||||
|
|
||||||
|
static final fields = [ |
||||||
|
salt, |
||||||
|
parallelism, |
||||||
|
memory, |
||||||
|
iterations, |
||||||
|
version, |
||||||
|
secretKey, |
||||||
|
assocData, |
||||||
|
rounds |
||||||
|
]; |
||||||
|
|
||||||
|
static void debugAll(VarDictionary dict) { |
||||||
|
_logger |
||||||
|
.fine('VarDictionary{\n${fields.map((f) => f.debug(dict)).join('\n')}'); |
||||||
|
} |
||||||
|
|
||||||
|
T read(VarDictionary dict) => dict.get(type, field); |
||||||
|
String debug(VarDictionary dict) { |
||||||
|
final value = dict.get(type, field); |
||||||
|
final strValue = type == ValueType.typeBytes |
||||||
|
? ByteUtils.toHexList(value as Uint8List) |
||||||
|
: value; |
||||||
|
return '$field=$strValue'; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class KeyEncrypterKdf { |
||||||
|
KeyEncrypterKdf(this.argon2); |
||||||
|
|
||||||
|
static const kdfUuids = <String, KdfType>{ |
||||||
|
'72Nt34wpREuR96mkA+MKDA==': KdfType.Argon2, |
||||||
|
'ydnzmmKKRGC/dA0IwYpP6g==': KdfType.Aes, |
||||||
|
}; |
||||||
|
|
||||||
|
final Argon2 argon2; |
||||||
|
|
||||||
|
Uint8List encrypt(Uint8List key, VarDictionary kdfParameters) { |
||||||
|
final uuid = kdfParameters.get(ValueType.typeBytes, '\$UUID'); |
||||||
|
if (uuid == null) { |
||||||
|
throw KdbxCorruptedFileException('No Kdf UUID'); |
||||||
|
} |
||||||
|
final kdfUuid = base64.encode(uuid); |
||||||
|
switch (kdfUuids[kdfUuid]) { |
||||||
|
case KdfType.Argon2: |
||||||
|
_logger.fine('Must be using argon2'); |
||||||
|
return encryptArgon2(key, kdfParameters); |
||||||
|
break; |
||||||
|
case KdfType.Aes: |
||||||
|
_logger.fine('Must be using aes'); |
||||||
|
break; |
||||||
|
} |
||||||
|
throw UnsupportedError('unsupported encrypt stuff.'); |
||||||
|
} |
||||||
|
|
||||||
|
Uint8List encryptArgon2(Uint8List key, VarDictionary kdfParameters) { |
||||||
|
_logger.fine('argon2():'); |
||||||
|
_logger.fine('key: ${ByteUtils.toHexList(key)}'); |
||||||
|
KdfField.debugAll(kdfParameters); |
||||||
|
return argon2.argon2( |
||||||
|
key, |
||||||
|
KdfField.salt.read(kdfParameters), |
||||||
|
65536, //KdfField.memory.read(kdfParameters), |
||||||
|
KdfField.iterations.read(kdfParameters), |
||||||
|
32, |
||||||
|
KdfField.parallelism.read(kdfParameters), |
||||||
|
0, |
||||||
|
KdfField.version.read(kdfParameters), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
abstract class Argon2 { |
||||||
|
Uint8List argon2( |
||||||
|
Uint8List key, |
||||||
|
Uint8List salt, |
||||||
|
int memory, |
||||||
|
int iterations, |
||||||
|
int length, |
||||||
|
int parallelism, |
||||||
|
int type, |
||||||
|
int version, |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,127 @@ |
|||||||
|
import 'package:kdbx/src/internal/byte_utils.dart'; |
||||||
|
import 'package:logging/logging.dart'; |
||||||
|
import 'package:meta/meta.dart'; |
||||||
|
|
||||||
|
final _logger = Logger('kdbx_var_dictionary'); |
||||||
|
|
||||||
|
typedef Decoder<T> = T Function(ReaderHelper reader, int length); |
||||||
|
typedef Encoder<T> = void Function(WriterHelper writer, T value); |
||||||
|
|
||||||
|
extension on WriterHelper { |
||||||
|
LengthWriter _lengthWriter() => (int length) => writeInt32(length); |
||||||
|
} |
||||||
|
|
||||||
|
@immutable |
||||||
|
class ValueType<T> { |
||||||
|
const ValueType(this.code, this.decoder, [this.encoder]); |
||||||
|
final int code; |
||||||
|
final Decoder<T> decoder; |
||||||
|
final Encoder<T> encoder; |
||||||
|
|
||||||
|
static final typeUInt32 = ValueType( |
||||||
|
0x04, |
||||||
|
(reader, _) => reader.readUint32(), |
||||||
|
(writer, value) => writer.writeUint32(value, writer._lengthWriter()), |
||||||
|
); |
||||||
|
static final typeUInt64 = ValueType( |
||||||
|
0x05, |
||||||
|
(reader, _) => reader.readUint64(), |
||||||
|
(writer, value) => writer.writeUint64(value, writer._lengthWriter()), |
||||||
|
); |
||||||
|
static final typeBool = ValueType( |
||||||
|
0x08, |
||||||
|
(reader, _) => reader.readUint8() != 0, |
||||||
|
(writer, value) => writer.writeUint8(value ? 1 : 0, writer._lengthWriter()), |
||||||
|
); |
||||||
|
static final typeInt32 = ValueType( |
||||||
|
0x0C, |
||||||
|
(reader, _) => reader.readInt32(), |
||||||
|
(writer, value) => writer.writeInt32(value, writer._lengthWriter()), |
||||||
|
); |
||||||
|
static final typeInt64 = ValueType( |
||||||
|
0x0D, |
||||||
|
(reader, _) => reader.readInt64(), |
||||||
|
(writer, value) => writer.writeInt64(value, writer._lengthWriter()), |
||||||
|
); |
||||||
|
static final typeString = ValueType( |
||||||
|
0x18, |
||||||
|
(reader, length) => reader.readString(length), |
||||||
|
(writer, value) => writer.writeString(value, writer._lengthWriter()), |
||||||
|
); |
||||||
|
static final typeBytes = ValueType( |
||||||
|
0x42, |
||||||
|
(reader, length) => reader.readBytes(length), |
||||||
|
(writer, value) => writer.writeBytes(value, writer._lengthWriter()), |
||||||
|
); |
||||||
|
|
||||||
|
static ValueType typeByCode(int code) => |
||||||
|
values.firstWhere((t) => t.code == code); |
||||||
|
|
||||||
|
static final values = [ |
||||||
|
typeUInt32, |
||||||
|
typeUInt64, |
||||||
|
typeBool, |
||||||
|
typeInt32, |
||||||
|
typeInt64, |
||||||
|
typeString, |
||||||
|
typeBytes, |
||||||
|
]; |
||||||
|
} |
||||||
|
|
||||||
|
class VarDictionaryItem<T> { |
||||||
|
VarDictionaryItem(this._key, this._valueType, this._value); |
||||||
|
|
||||||
|
final String _key; |
||||||
|
final ValueType<T> _valueType; |
||||||
|
final T _value; |
||||||
|
|
||||||
|
String toDebugString() { |
||||||
|
return 'VarDictionaryItem{key=$_key, valueType=$_valueType, value=${_value.runtimeType}}'; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class VarDictionary { |
||||||
|
VarDictionary(List<VarDictionaryItem<dynamic>> items) |
||||||
|
: assert(items != null), |
||||||
|
_items = items, |
||||||
|
_dict = Map.fromEntries(items.map((item) => MapEntry(item._key, item))); |
||||||
|
|
||||||
|
factory VarDictionary.read(ReaderHelper reader) { |
||||||
|
final items = <VarDictionaryItem>[]; |
||||||
|
final versionMinor = reader.readUint8(); |
||||||
|
final versionMajor = reader.readUint8(); |
||||||
|
_logger.finest('Reading VarDictionary $versionMajor.$versionMinor'); |
||||||
|
assert(versionMajor == 1); |
||||||
|
|
||||||
|
while (true) { |
||||||
|
final item = _readItem(reader); |
||||||
|
if (item == null) { |
||||||
|
break; |
||||||
|
} |
||||||
|
items.add(item); |
||||||
|
} |
||||||
|
return VarDictionary(items); |
||||||
|
} |
||||||
|
|
||||||
|
final List<VarDictionaryItem<dynamic>> _items; |
||||||
|
final Map<String, VarDictionaryItem<dynamic>> _dict; |
||||||
|
|
||||||
|
T get<T>(ValueType<T> type, String key) => _dict[key]?._value as T; |
||||||
|
|
||||||
|
static VarDictionaryItem<dynamic> _readItem(ReaderHelper reader) { |
||||||
|
final type = reader.readUint8(); |
||||||
|
if (type == 0) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
final keyLength = reader.readUint32(); |
||||||
|
final key = reader.readString(keyLength); |
||||||
|
final valueLength = reader.readInt32(); |
||||||
|
final valueType = ValueType.typeByCode(type); |
||||||
|
return VarDictionaryItem<dynamic>( |
||||||
|
key, valueType, valueType.decoder(reader, valueLength)); |
||||||
|
} |
||||||
|
|
||||||
|
String toDebugString() { |
||||||
|
return 'VarDictionary{${_items.map((item) => item.toDebugString())}'; |
||||||
|
} |
||||||
|
} |
Binary file not shown.
@ -0,0 +1,117 @@ |
|||||||
|
import 'dart:convert'; |
||||||
|
import 'dart:ffi'; |
||||||
|
import 'dart:io'; |
||||||
|
import 'dart:typed_data'; |
||||||
|
|
||||||
|
import 'package:ffi/ffi.dart'; |
||||||
|
import 'package:ffi_helper/ffi_helper.dart'; |
||||||
|
import 'package:kdbx/kdbx.dart'; |
||||||
|
import 'package:kdbx/src/crypto/key_encrypter_kdf.dart'; |
||||||
|
import 'package:logging/logging.dart'; |
||||||
|
import 'package:logging_appenders/logging_appenders.dart'; |
||||||
|
import 'package:test/test.dart'; |
||||||
|
|
||||||
|
final _logger = Logger('kdbx4_test'); |
||||||
|
|
||||||
|
//typedef HashStuff = Pointer<Utf8> Function(Pointer<Utf8> str); |
||||||
|
typedef Argon2HashNative = Pointer<Utf8> Function( |
||||||
|
Pointer<Uint8> key, |
||||||
|
IntPtr keyLen, |
||||||
|
Pointer<Uint8> salt, |
||||||
|
Uint64 saltlen, |
||||||
|
Uint32 m_cost, // memory cost |
||||||
|
Uint32 t_cost, // time cost (number iterations) |
||||||
|
Uint32 parallelism, |
||||||
|
IntPtr hashlen, |
||||||
|
Uint8 type, |
||||||
|
Uint32 version, |
||||||
|
); |
||||||
|
typedef Argon2Hash = Pointer<Utf8> Function( |
||||||
|
Pointer<Uint8> key, |
||||||
|
int keyLen, |
||||||
|
Pointer<Uint8> salt, |
||||||
|
int saltlen, |
||||||
|
int m_cost, // memory cost |
||||||
|
int t_cost, // time cost (number iterations) |
||||||
|
int parallelism, |
||||||
|
int hashlen, |
||||||
|
int type, |
||||||
|
int version, |
||||||
|
); |
||||||
|
|
||||||
|
class Argon2Test implements Argon2 { |
||||||
|
Argon2Test() { |
||||||
|
// final argon2lib = DynamicLibrary.open('libargon2.1.dylib'); |
||||||
|
final argon2lib = DynamicLibrary.open('libargon2_ffi.dylib'); |
||||||
|
_argon2hash = argon2lib |
||||||
|
.lookup<NativeFunction<Argon2HashNative>>('hp_argon2_hash') |
||||||
|
.asFunction(); |
||||||
|
} |
||||||
|
Argon2Hash _argon2hash; |
||||||
|
|
||||||
|
@override |
||||||
|
Uint8List argon2( |
||||||
|
Uint8List key, |
||||||
|
Uint8List salt, |
||||||
|
int memory, |
||||||
|
int iterations, |
||||||
|
int length, |
||||||
|
int parallelism, |
||||||
|
int type, |
||||||
|
int version, |
||||||
|
) { |
||||||
|
// print('hash: ${hashStuff('abc')}'); |
||||||
|
final keyArray = Uint8Array.fromTypedList(key); |
||||||
|
// final saltArray = Uint8Array.fromTypedList(salt); |
||||||
|
final saltArray = allocate<Uint8>(count: salt.length); |
||||||
|
final saltList = saltArray.asTypedList(length); |
||||||
|
saltList.setAll(0, salt); |
||||||
|
const int memoryCost = 1 << 16; |
||||||
|
|
||||||
|
// _logger.fine('saltArray: ${ByteUtils.toHexList(saltArray.view)}'); |
||||||
|
|
||||||
|
final result = _argon2hash( |
||||||
|
keyArray.rawPtr, |
||||||
|
keyArray.length, |
||||||
|
saltArray, |
||||||
|
salt.length, |
||||||
|
memoryCost, |
||||||
|
iterations, |
||||||
|
parallelism, |
||||||
|
length, |
||||||
|
type, |
||||||
|
version, |
||||||
|
); |
||||||
|
|
||||||
|
keyArray.free(); |
||||||
|
// saltArray.free(); |
||||||
|
free(saltArray); |
||||||
|
final resultString = Utf8.fromUtf8(result); |
||||||
|
return base64.decode(resultString); |
||||||
|
} |
||||||
|
|
||||||
|
// String hashStuff(String password) => |
||||||
|
// Utf8.fromUtf8(_hashStuff(Utf8.toUtf8(password))); |
||||||
|
} |
||||||
|
|
||||||
|
void main() { |
||||||
|
Logger.root.level = Level.ALL; |
||||||
|
PrintAppender().attachToLogger(Logger.root); |
||||||
|
final kdbxFormat = KdbxFormat(Argon2Test()); |
||||||
|
group('Reading', () { |
||||||
|
final argon2 = Argon2Test(); |
||||||
|
test('bubb', () async { |
||||||
|
final key = utf8.encode('asdf') as Uint8List; |
||||||
|
final salt = Uint8List(8); |
||||||
|
// final result = Argon2Test().argon2(key, salt, 1 << 16, 5, 16, 1, 0x13, 1); |
||||||
|
// _logger.fine('hashing: $result'); |
||||||
|
final data = await File('test/keepassxcpasswords.kdbx').readAsBytes(); |
||||||
|
final file = |
||||||
|
kdbxFormat.read(data, Credentials(ProtectedValue.fromString('asdf'))); |
||||||
|
final firstEntry = file.body.rootGroup.entries.first; |
||||||
|
final pwd = firstEntry.getString(KdbxKey('Password')).getText(); |
||||||
|
_logger.info('password: $pwd'); |
||||||
|
expect(pwd, 'MyPassword'); |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
Binary file not shown.
Loading…
Reference in new issue