Herbert Poul
5 years ago
14 changed files with 641 additions and 37 deletions
@ -1,5 +1,5 @@
|
||||
import 'package:kdbx/kdbx.dart'; |
||||
|
||||
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