Herbert Poul
5 years ago
10 changed files with 283 additions and 159 deletions
@ -0,0 +1,19 @@ |
|||||||
|
import 'dart:typed_data'; |
||||||
|
|
||||||
|
class ByteUtils { |
||||||
|
static bool eq(Uint8List a, Uint8List b) { |
||||||
|
if (a.length != b.length) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
for (int i = a.length - 1; i >= 0; i--) { |
||||||
|
if (a[i] != b[i]) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
static String toHex(int val) => '0x${val.toRadixString(16)}'; |
||||||
|
|
||||||
|
static String toHexList(Uint8List list) => list.map((val) => toHex(val)).join(' '); |
||||||
|
} |
@ -0,0 +1,84 @@ |
|||||||
|
|
||||||
|
|
||||||
|
import 'dart:typed_data'; |
||||||
|
|
||||||
|
import 'package:pointycastle/export.dart'; |
||||||
|
|
||||||
|
class CryptoUtils { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/// https://gist.github.com/proteye/e54eef1713e1fe9123d1eb04c0a5cf9b |
||||||
|
class AesHelper { |
||||||
|
static const CBC_MODE = 'CBC'; |
||||||
|
static const CFB_MODE = 'CFB'; |
||||||
|
|
||||||
|
// AES key size |
||||||
|
static const KEY_SIZE = 32; // 32 byte key for AES-256 |
||||||
|
static const ITERATION_COUNT = 1000; |
||||||
|
|
||||||
|
static Uint8List deriveKey( |
||||||
|
Uint8List password, { |
||||||
|
Uint8List salt, |
||||||
|
int iterationCount = ITERATION_COUNT, |
||||||
|
int derivedKeyLength = KEY_SIZE, |
||||||
|
}) { |
||||||
|
final Pbkdf2Parameters params = Pbkdf2Parameters(salt, iterationCount, derivedKeyLength); |
||||||
|
final KeyDerivator keyDerivator = PBKDF2KeyDerivator(HMac(SHA256Digest(), 16)); |
||||||
|
keyDerivator.init(params); |
||||||
|
|
||||||
|
return keyDerivator.process(password); |
||||||
|
} |
||||||
|
|
||||||
|
static String decrypt(Uint8List derivedKey, Uint8List cipherIvBytes, {String mode = CBC_MODE}) { |
||||||
|
// Uint8List derivedKey = deriveKey(password); |
||||||
|
final KeyParameter keyParam = KeyParameter(derivedKey); |
||||||
|
final BlockCipher aes = AESFastEngine(); |
||||||
|
|
||||||
|
// Uint8List cipherIvBytes = base64.decode(ciphertext); |
||||||
|
final Uint8List iv = Uint8List(aes.blockSize)..setRange(0, aes.blockSize, cipherIvBytes); |
||||||
|
|
||||||
|
BlockCipher cipher; |
||||||
|
final ParametersWithIV params = ParametersWithIV(keyParam, iv); |
||||||
|
switch (mode) { |
||||||
|
case CBC_MODE: |
||||||
|
cipher = CBCBlockCipher(aes); |
||||||
|
break; |
||||||
|
case CFB_MODE: |
||||||
|
cipher = CFBBlockCipher(aes, aes.blockSize); |
||||||
|
break; |
||||||
|
default: |
||||||
|
throw ArgumentError('incorrect value of the "mode" parameter'); |
||||||
|
break; |
||||||
|
} |
||||||
|
cipher.init(false, params); |
||||||
|
|
||||||
|
final int cipherLen = cipherIvBytes.length - aes.blockSize; |
||||||
|
final Uint8List cipherBytes = Uint8List(cipherLen)..setRange(0, cipherLen, cipherIvBytes, aes.blockSize); |
||||||
|
final Uint8List paddedText = processBlocks(cipher, cipherBytes); |
||||||
|
final Uint8List textBytes = unpad(paddedText); |
||||||
|
|
||||||
|
return String.fromCharCodes(textBytes); |
||||||
|
} |
||||||
|
|
||||||
|
static Uint8List unpad(Uint8List src) { |
||||||
|
final pad = PKCS7Padding(); |
||||||
|
pad.init(null); |
||||||
|
|
||||||
|
final int padLength = pad.padCount(src); |
||||||
|
final int len = src.length - padLength; |
||||||
|
|
||||||
|
return Uint8List(len)..setRange(0, len, src); |
||||||
|
} |
||||||
|
|
||||||
|
static Uint8List processBlocks(BlockCipher cipher, Uint8List inp) { |
||||||
|
final out = Uint8List(inp.lengthInBytes); |
||||||
|
|
||||||
|
for (var offset = 0; offset < inp.lengthInBytes;) { |
||||||
|
final len = cipher.processBlock(inp, offset, out, offset); |
||||||
|
offset += len; |
||||||
|
} |
||||||
|
|
||||||
|
return out; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,83 @@ |
|||||||
|
import 'dart:convert'; |
||||||
|
import 'dart:io'; |
||||||
|
import 'dart:typed_data'; |
||||||
|
|
||||||
|
import 'package:crypto/crypto.dart' as crypto; |
||||||
|
import 'package:kdbx/src/internal/byte_utils.dart'; |
||||||
|
import 'package:kdbx/src/internal/crypto_utils.dart'; |
||||||
|
import 'package:kdbx/src/kdbx_header.dart'; |
||||||
|
import 'package:logging/logging.dart'; |
||||||
|
import 'package:pointycastle/export.dart'; |
||||||
|
|
||||||
|
final _logger = Logger('kdbx.format'); |
||||||
|
|
||||||
|
|
||||||
|
class KdbxFormat { |
||||||
|
static Future<void> read(Uint8List input, Credentials credentials) async { |
||||||
|
final reader = ReaderHelper(input); |
||||||
|
final header = await KdbxHeader.read(reader); |
||||||
|
_loadV3(header, reader, credentials); |
||||||
|
} |
||||||
|
|
||||||
|
static void _loadV3(KdbxHeader header, ReaderHelper reader, Credentials credentials) { |
||||||
|
// _getMasterKeyV3(header, credentials); |
||||||
|
final masterKey = _generateMasterKeyV3(header, credentials); |
||||||
|
final encryptedPayload = reader.readRemaining(); |
||||||
|
final content = _decryptContent(header, masterKey, encryptedPayload); |
||||||
|
final blocks = HashedBlockReader.readBlocks(ReaderHelper(content)); |
||||||
|
|
||||||
|
_logger.finer('compression: ${header.compression}'); |
||||||
|
if (header.compression == Compression.gzip) { |
||||||
|
final xml = GZipCodec().decode(blocks); |
||||||
|
final string = utf8.decode(xml); |
||||||
|
print('xml: $string'); |
||||||
|
} |
||||||
|
|
||||||
|
// final result = utf8.decode(decrypted); |
||||||
|
// final aesEngine = AESFastEngine(); |
||||||
|
// aesEngine.init(true, KeyParameter(seed)); |
||||||
|
// final key = AesHelper.deriveKey(keyComposite.bytes as Uint8List, salt: seed, iterationCount: rounds, derivedKeyLength: 32); |
||||||
|
// final masterKey = Uint8List.fromList(key + masterSeed.asUint8List()); |
||||||
|
// print('key length: ${key.length} + ${masterSeed.lengthInBytes} = ${masterKey.lengthInBytes} (${masterKey.lengthInBytes} bytes)'); |
||||||
|
|
||||||
|
// final result = AesHelper.decrypt(masterKey, reader.readRemaining()); |
||||||
|
// print('before : ${_toHexList(encryptedPayload)}'); |
||||||
|
} |
||||||
|
|
||||||
|
static Uint8List _decryptContent(KdbxHeader header, Uint8List masterKey, Uint8List encryptedPayload) { |
||||||
|
final encryptionIv = header.fields[HeaderFields.EncryptionIV].bytes; |
||||||
|
final decryptCipher = CBCBlockCipher(AESFastEngine()); |
||||||
|
decryptCipher.init(false, ParametersWithIV(KeyParameter(masterKey), encryptionIv.asUint8List())); |
||||||
|
final decrypted = AesHelper.processBlocks(decryptCipher, encryptedPayload); |
||||||
|
|
||||||
|
final streamStart = header.fields[HeaderFields.StreamStartBytes].bytes; |
||||||
|
|
||||||
|
_logger.finest('streamStart: ${ByteUtils.toHexList(streamStart.asUint8List())}'); |
||||||
|
_logger.finest('actual : ${ByteUtils.toHexList(decrypted.sublist(0, streamStart.lengthInBytes))}'); |
||||||
|
|
||||||
|
if (!ByteUtils.eq(streamStart.asUint8List(), decrypted.sublist(0, streamStart.lengthInBytes))) { |
||||||
|
throw KdbxInvalidKeyException(); |
||||||
|
} |
||||||
|
final content = decrypted.sublist(streamStart.lengthInBytes); |
||||||
|
return content; |
||||||
|
} |
||||||
|
|
||||||
|
static Uint8List _generateMasterKeyV3(KdbxHeader header, Credentials credentials) { |
||||||
|
final rounds = header.fields[HeaderFields.TransformRounds].bytes.asUint64List().first; |
||||||
|
final seed = header.fields[HeaderFields.TransformSeed].bytes.asUint8List(); |
||||||
|
final masterSeed = header.fields[HeaderFields.MasterSeed].bytes; |
||||||
|
_logger.finer('Rounds: $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.asUint8List() + transformedKey)).bytes as Uint8List; |
||||||
|
return masterKey; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,80 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8" standalone="yes"?> |
||||||
|
<KeePassFile> |
||||||
|
<Meta> |
||||||
|
<Generator>KdbxWeb</Generator> |
||||||
|
<HeaderHash>a9XeOPjkxVOzggrVtvoEpBIc07uqIShumzKMU+/lj04=</HeaderHash> |
||||||
|
<DatabaseName>FooBar</DatabaseName> |
||||||
|
<DatabaseNameChanged>2019-08-20T13:16:06Z</DatabaseNameChanged> |
||||||
|
<DatabaseDescription /> |
||||||
|
<DatabaseDescriptionChanged>2019-08-20T13:15:47Z</DatabaseDescriptionChanged> |
||||||
|
<DefaultUserName /> |
||||||
|
<DefaultUserNameChanged>2019-08-20T13:15:47Z</DefaultUserNameChanged> |
||||||
|
<MaintenanceHistoryDays>365</MaintenanceHistoryDays> |
||||||
|
<Color /> |
||||||
|
<MasterKeyChanged>2019-08-20T13:16:03Z</MasterKeyChanged> |
||||||
|
<MasterKeyChangeRec>-1</MasterKeyChangeRec> |
||||||
|
<MasterKeyChangeForce>-1</MasterKeyChangeForce> |
||||||
|
<RecycleBinEnabled>True</RecycleBinEnabled> |
||||||
|
<RecycleBinUUID>dVSBC/BAx70qcsy6XkrGJA==</RecycleBinUUID> |
||||||
|
<RecycleBinChanged>2019-08-20T13:15:47Z</RecycleBinChanged> |
||||||
|
<EntryTemplatesGroup>AAAAAAAAAAAAAAAAAAAAAA==</EntryTemplatesGroup> |
||||||
|
<EntryTemplatesGroupChanged>2019-08-20T13:15:47Z</EntryTemplatesGroupChanged> |
||||||
|
<HistoryMaxItems>10</HistoryMaxItems> |
||||||
|
<HistoryMaxSize>6291456</HistoryMaxSize> |
||||||
|
<LastSelectedGroup /> |
||||||
|
<LastTopVisibleGroup /> |
||||||
|
<MemoryProtection> |
||||||
|
<ProtectTitle>False</ProtectTitle> |
||||||
|
<ProtectUserName>False</ProtectUserName> |
||||||
|
<ProtectPassword>True</ProtectPassword> |
||||||
|
<ProtectURL>False</ProtectURL> |
||||||
|
<ProtectNotes>False</ProtectNotes> |
||||||
|
</MemoryProtection> |
||||||
|
<CustomIcons /> |
||||||
|
<Binaries /> |
||||||
|
<CustomData /> |
||||||
|
</Meta> |
||||||
|
<Root> |
||||||
|
<Group> |
||||||
|
<UUID>LAQMkihXTkxhA2D2tE40Fg==</UUID> |
||||||
|
<Name>FooBar</Name> |
||||||
|
<Notes /> |
||||||
|
<IconID>49</IconID> |
||||||
|
<Times> |
||||||
|
<CreationTime>2019-08-20T13:15:47Z</CreationTime> |
||||||
|
<LastModificationTime>2019-08-20T13:16:06Z</LastModificationTime> |
||||||
|
<LastAccessTime>2019-08-20T13:16:06Z</LastAccessTime> |
||||||
|
<ExpiryTime>2019-08-20T13:15:47Z</ExpiryTime> |
||||||
|
<Expires>False</Expires> |
||||||
|
<UsageCount>0</UsageCount> |
||||||
|
<LocationChanged>2019-08-20T13:15:47Z</LocationChanged> |
||||||
|
</Times> |
||||||
|
<IsExpanded>True</IsExpanded> |
||||||
|
<DefaultAutoTypeSequence /> |
||||||
|
<EnableAutoType>null</EnableAutoType> |
||||||
|
<EnableSearching>null</EnableSearching> |
||||||
|
<LastTopVisibleEntry>AAAAAAAAAAAAAAAAAAAAAA==</LastTopVisibleEntry> |
||||||
|
<Group> |
||||||
|
<UUID>dVSBC/BAx70qcsy6XkrGJA==</UUID> |
||||||
|
<Name>Recycle Bin</Name> |
||||||
|
<Notes /> |
||||||
|
<IconID>43</IconID> |
||||||
|
<Times> |
||||||
|
<CreationTime>2019-08-20T13:15:47Z</CreationTime> |
||||||
|
<LastModificationTime>2019-08-20T13:15:47Z</LastModificationTime> |
||||||
|
<LastAccessTime>2019-08-20T13:15:47Z</LastAccessTime> |
||||||
|
<ExpiryTime>2019-08-20T13:15:47Z</ExpiryTime> |
||||||
|
<Expires>False</Expires> |
||||||
|
<UsageCount>0</UsageCount> |
||||||
|
<LocationChanged>2019-08-20T13:15:47Z</LocationChanged> |
||||||
|
</Times> |
||||||
|
<IsExpanded>True</IsExpanded> |
||||||
|
<DefaultAutoTypeSequence /> |
||||||
|
<EnableAutoType>False</EnableAutoType> |
||||||
|
<EnableSearching>False</EnableSearching> |
||||||
|
<LastTopVisibleEntry>AAAAAAAAAAAAAAAAAAAAAA==</LastTopVisibleEntry> |
||||||
|
</Group> |
||||||
|
</Group> |
||||||
|
<DeletedObjects /> |
||||||
|
</Root> |
||||||
|
</KeePassFile> |
Binary file not shown.
Loading…
Reference in new issue