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