Add support for various algorithms for kdbx4

* Add SHA512 support to CryptoHash
* Add ChaCha20 support
* Add HMAC support
* Add new HmacBlockStream, used in KDBX 4
* Add support for ChaCha20 protected stream
This commit is contained in:
angelsl 2017-11-13 02:23:01 +08:00 committed by Jonathan White
parent 4532108678
commit 6a0d05e1ef
No known key found for this signature in database
GPG Key ID: 440FC65F2E0C6E01
23 changed files with 616 additions and 25 deletions

View File

@ -262,7 +262,7 @@ set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS_NONE QT_NO_DEBUG)
find_package(LibGPGError REQUIRED) find_package(LibGPGError REQUIRED)
find_package(Gcrypt 1.6.0 REQUIRED) find_package(Gcrypt 1.7.0 REQUIRED)
find_package(ZLIB REQUIRED) find_package(ZLIB REQUIRED)

View File

@ -148,6 +148,7 @@ set(keepassx_SOURCES
keys/PasswordKey.cpp keys/PasswordKey.cpp
keys/YkChallengeResponseKey.cpp keys/YkChallengeResponseKey.cpp
streams/HashedBlockStream.cpp streams/HashedBlockStream.cpp
streams/HmacBlockStream.cpp
streams/LayeredStream.cpp streams/LayeredStream.cpp
streams/qtiocompressor.cpp streams/qtiocompressor.cpp
streams/StoreDataStream.cpp streams/StoreDataStream.cpp

View File

@ -95,18 +95,28 @@ bool Crypto::checkAlgorithms()
qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr)); qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr));
return false; return false;
} }
if (gcry_cipher_algo_info(GCRY_CIPHER_CHACHA20, GCRYCTL_TEST_ALGO, nullptr, nullptr) != 0) {
m_errorStr = "GCRY_CIPHER_CHACHA20 not found.";
qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr));
return false;
}
if (gcry_md_test_algo(GCRY_MD_SHA256) != 0) { if (gcry_md_test_algo(GCRY_MD_SHA256) != 0) {
m_errorStr = "GCRY_MD_SHA256 not found."; m_errorStr = "GCRY_MD_SHA256 not found.";
qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr)); qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr));
return false; return false;
} }
if (gcry_md_test_algo(GCRY_MD_SHA512) != 0) {
m_errorStr = "GCRY_MD_SHA512 not found.";
qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr));
return false;
}
return true; return true;
} }
bool Crypto::selfTest() bool Crypto::selfTest()
{ {
return testSha256() && testAes256Cbc() && testAes256Ecb() && testTwofish() && testSalsa20(); return testSha256() && testSha512() && testAes256Cbc() && testAes256Ecb() && testTwofish() && testSalsa20() && testChaCha20();
} }
void Crypto::raiseError(const QString& str) void Crypto::raiseError(const QString& str)
@ -128,6 +138,19 @@ bool Crypto::testSha256()
return true; return true;
} }
bool Crypto::testSha512()
{
QByteArray sha512Test = CryptoHash::hash("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
CryptoHash::Sha512);
if (sha512Test != QByteArray::fromHex("204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445")) {
raiseError("SHA-512 mismatch.");
return false;
}
return true;
}
bool Crypto::testAes256Cbc() bool Crypto::testAes256Cbc()
{ {
QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"); QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
@ -285,3 +308,30 @@ bool Crypto::testSalsa20()
return true; return true;
} }
bool Crypto::testChaCha20() {
QByteArray chacha20Key = QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000000");
QByteArray chacha20iv = QByteArray::fromHex("0000000000000000");
QByteArray chacha20Plain = QByteArray::fromHex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
QByteArray chacha20Cipher = QByteArray::fromHex("76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586");
bool ok;
SymmetricCipher chacha20Stream(SymmetricCipher::ChaCha20, SymmetricCipher::Stream,
SymmetricCipher::Encrypt);
if (!chacha20Stream.init(chacha20Key, chacha20iv)) {
raiseError(chacha20Stream.errorString());
return false;
}
QByteArray chacha20Processed = chacha20Stream.process(chacha20Plain, &ok);
if (!ok) {
raiseError(chacha20Stream.errorString());
return false;
}
if (chacha20Processed != chacha20Cipher) {
raiseError("ChaCha20 stream cipher mismatch.");
return false;
}
return true;
}

View File

@ -35,10 +35,12 @@ private:
static bool selfTest(); static bool selfTest();
static void raiseError(const QString& str); static void raiseError(const QString& str);
static bool testSha256(); static bool testSha256();
static bool testSha512();
static bool testAes256Cbc(); static bool testAes256Cbc();
static bool testAes256Ecb(); static bool testAes256Ecb();
static bool testTwofish(); static bool testTwofish();
static bool testSalsa20(); static bool testSalsa20();
static bool testChaCha20();
static bool m_initalized; static bool m_initalized;
static QString m_errorStr; static QString m_errorStr;

View File

@ -29,27 +29,42 @@ public:
}; };
CryptoHash::CryptoHash(CryptoHash::Algorithm algo) CryptoHash::CryptoHash(CryptoHash::Algorithm algo)
: CryptoHash::CryptoHash(algo, false) {}
CryptoHash::CryptoHash(CryptoHash::Algorithm algo, bool hmac)
: d_ptr(new CryptoHashPrivate()) : d_ptr(new CryptoHashPrivate())
{ {
Q_D(CryptoHash); Q_D(CryptoHash);
Q_ASSERT(Crypto::initalized()); Q_ASSERT(Crypto::initalized());
int algoGcrypt; int algoGcrypt = -1;
unsigned int flagsGcrypt = 0;
switch (algo) { switch (algo) {
case CryptoHash::Sha256: case CryptoHash::Sha256:
algoGcrypt = GCRY_MD_SHA256; algoGcrypt = GCRY_MD_SHA256;
break; break;
case CryptoHash::Sha512:
algoGcrypt = GCRY_MD_SHA512;
break;
default: default:
Q_ASSERT(false); Q_ASSERT(false);
break; break;
} }
gcry_error_t error = gcry_md_open(&d->ctx, algoGcrypt, 0); if (hmac) {
flagsGcrypt |= GCRY_MD_FLAG_HMAC;
}
gcry_error_t error = gcry_md_open(&d->ctx, algoGcrypt, flagsGcrypt);
if (error) {
qWarning("Gcrypt error (ctor): %s", gcry_strerror(error));
qWarning("Gcrypt error (ctor): %s", gcry_strsource(error));
}
Q_ASSERT(error == 0); // TODO: error handling Q_ASSERT(error == 0); // TODO: error handling
Q_UNUSED(error);
d->hashLen = gcry_md_get_algo_dlen(algoGcrypt); d->hashLen = gcry_md_get_algo_dlen(algoGcrypt);
} }
@ -74,6 +89,18 @@ void CryptoHash::addData(const QByteArray& data)
gcry_md_write(d->ctx, data.constData(), data.size()); gcry_md_write(d->ctx, data.constData(), data.size());
} }
void CryptoHash::setKey(const QByteArray& data)
{
Q_D(CryptoHash);
gcry_error_t error = gcry_md_setkey(d->ctx, data.constData(), data.size());
if (error) {
qWarning("Gcrypt error (setKey): %s", gcry_strerror(error));
qWarning("Gcrypt error (setKey): %s", gcry_strsource(error));
}
Q_ASSERT(error == 0);
}
void CryptoHash::reset() void CryptoHash::reset()
{ {
Q_D(CryptoHash); Q_D(CryptoHash);
@ -96,3 +123,12 @@ QByteArray CryptoHash::hash(const QByteArray& data, CryptoHash::Algorithm algo)
cryptoHash.addData(data); cryptoHash.addData(data);
return cryptoHash.result(); return cryptoHash.result();
} }
QByteArray CryptoHash::hmac(const QByteArray& data, const QByteArray& key, CryptoHash::Algorithm algo)
{
// replace with gcry_md_hash_buffer()?
CryptoHash cryptoHash(algo, true);
cryptoHash.setKey(key);
cryptoHash.addData(data);
return cryptoHash.result();
}

View File

@ -27,16 +27,20 @@ class CryptoHash
public: public:
enum Algorithm enum Algorithm
{ {
Sha256 Sha256,
Sha512
}; };
explicit CryptoHash(CryptoHash::Algorithm algo); explicit CryptoHash(CryptoHash::Algorithm algo);
explicit CryptoHash(CryptoHash::Algorithm algo, bool hmac);
~CryptoHash(); ~CryptoHash();
void addData(const QByteArray& data); void addData(const QByteArray& data);
void reset(); void reset();
QByteArray result() const; QByteArray result() const;
void setKey(const QByteArray& data);
static QByteArray hash(const QByteArray& data, CryptoHash::Algorithm algo); static QByteArray hash(const QByteArray& data, CryptoHash::Algorithm algo);
static QByteArray hmac(const QByteArray& data, const QByteArray& key, Algorithm algo);
private: private:
CryptoHashPrivate* const d_ptr; CryptoHashPrivate* const d_ptr;

View File

@ -24,6 +24,7 @@ SymmetricCipher::SymmetricCipher(SymmetricCipher::Algorithm algo, SymmetricCiphe
SymmetricCipher::Direction direction) SymmetricCipher::Direction direction)
: m_backend(createBackend(algo, mode, direction)) : m_backend(createBackend(algo, mode, direction))
, m_initialized(false) , m_initialized(false)
, m_algo(algo)
{ {
} }
@ -61,6 +62,7 @@ SymmetricCipherBackend* SymmetricCipher::createBackend(SymmetricCipher::Algorith
case SymmetricCipher::Aes256: case SymmetricCipher::Aes256:
case SymmetricCipher::Twofish: case SymmetricCipher::Twofish:
case SymmetricCipher::Salsa20: case SymmetricCipher::Salsa20:
case SymmetricCipher::ChaCha20:
return new SymmetricCipherGcrypt(algo, mode, direction); return new SymmetricCipherGcrypt(algo, mode, direction);
default: default:
@ -93,10 +95,14 @@ SymmetricCipher::Algorithm SymmetricCipher::cipherToAlgorithm(Uuid cipher)
{ {
if (cipher == KeePass2::CIPHER_AES) { if (cipher == KeePass2::CIPHER_AES) {
return SymmetricCipher::Aes256; return SymmetricCipher::Aes256;
} } else if (cipher == KeePass2::CIPHER_CHACHA20) {
else { return SymmetricCipher::ChaCha20;
} else if (cipher == KeePass2::CIPHER_TWOFISH) {
return SymmetricCipher::Twofish; return SymmetricCipher::Twofish;
} }
qWarning("SymmetricCipher::cipherToAlgorithm: invalid Uuid %s", cipher.toByteArray().toHex().data());
return InvalidAlgorithm;
} }
Uuid SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm algo) Uuid SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm algo)
@ -104,7 +110,42 @@ Uuid SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm algo)
switch (algo) { switch (algo) {
case SymmetricCipher::Aes256: case SymmetricCipher::Aes256:
return KeePass2::CIPHER_AES; return KeePass2::CIPHER_AES;
default: case SymmetricCipher::ChaCha20:
return KeePass2::CIPHER_CHACHA20;
case SymmetricCipher::Twofish:
return KeePass2::CIPHER_TWOFISH; return KeePass2::CIPHER_TWOFISH;
default:
qWarning("SymmetricCipher::algorithmToCipher: invalid algorithm %d", algo);
return Uuid();
} }
} }
int SymmetricCipher::algorithmIvSize(SymmetricCipher::Algorithm algo) {
switch (algo) {
case SymmetricCipher::ChaCha20:
return 12;
case SymmetricCipher::Aes256:
case SymmetricCipher::Twofish:
return 16;
default:
qWarning("SymmetricCipher::algorithmIvSize: invalid algorithm %d", algo);
return -1;
}
}
SymmetricCipher::Mode SymmetricCipher::algorithmMode(SymmetricCipher::Algorithm algo) {
switch (algo) {
case SymmetricCipher::ChaCha20:
return SymmetricCipher::Stream;
case SymmetricCipher::Aes256:
case SymmetricCipher::Twofish:
return SymmetricCipher::Cbc;
default:
qWarning("SymmetricCipher::algorithmMode: invalid algorithm %d", algo);
return SymmetricCipher::InvalidMode;
}
}
SymmetricCipher::Algorithm SymmetricCipher::algorithm() const {
return m_algo;
}

View File

@ -33,7 +33,9 @@ public:
{ {
Aes256, Aes256,
Twofish, Twofish,
Salsa20 Salsa20,
ChaCha20,
InvalidAlgorithm = -1
}; };
enum Mode enum Mode
@ -41,7 +43,8 @@ public:
Cbc, Cbc,
Ctr, Ctr,
Ecb, Ecb,
Stream Stream,
InvalidMode = -1
}; };
enum Direction enum Direction
@ -74,9 +77,12 @@ public:
int keySize() const; int keySize() const;
int blockSize() const; int blockSize() const;
QString errorString() const; QString errorString() const;
Algorithm algorithm() const;
static SymmetricCipher::Algorithm cipherToAlgorithm(Uuid cipher); static Algorithm cipherToAlgorithm(Uuid cipher);
static Uuid algorithmToCipher(SymmetricCipher::Algorithm algo); static Uuid algorithmToCipher(Algorithm algo);
static int algorithmIvSize(Algorithm algo);
static Mode algorithmMode(Algorithm algo);
private: private:
static SymmetricCipherBackend* createBackend(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode, static SymmetricCipherBackend* createBackend(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
@ -84,6 +90,7 @@ private:
const QScopedPointer<SymmetricCipherBackend> m_backend; const QScopedPointer<SymmetricCipherBackend> m_backend;
bool m_initialized; bool m_initialized;
Algorithm m_algo;
Q_DISABLE_COPY(SymmetricCipher) Q_DISABLE_COPY(SymmetricCipher)
}; };

View File

@ -46,6 +46,9 @@ int SymmetricCipherGcrypt::gcryptAlgo(SymmetricCipher::Algorithm algo)
case SymmetricCipher::Salsa20: case SymmetricCipher::Salsa20:
return GCRY_CIPHER_SALSA20; return GCRY_CIPHER_SALSA20;
case SymmetricCipher::ChaCha20:
return GCRY_CIPHER_CHACHA20;
default: default:
Q_ASSERT(false); Q_ASSERT(false);
return -1; return -1;

View File

@ -22,6 +22,7 @@
const Uuid KeePass2::CIPHER_AES = Uuid(QByteArray::fromHex("31c1f2e6bf714350be5805216afc5aff")); const Uuid KeePass2::CIPHER_AES = Uuid(QByteArray::fromHex("31c1f2e6bf714350be5805216afc5aff"));
const Uuid KeePass2::CIPHER_TWOFISH = Uuid(QByteArray::fromHex("ad68f29f576f4bb9a36ad47af965346c")); const Uuid KeePass2::CIPHER_TWOFISH = Uuid(QByteArray::fromHex("ad68f29f576f4bb9a36ad47af965346c"));
const Uuid KeePass2::CIPHER_CHACHA20 = Uuid(QByteArray::fromHex("D6038A2B8B6F4CB5A524339A31DBB59A"));
const Uuid KeePass2::KDF_AES = Uuid(QByteArray::fromHex("C9D9F39A628A4460BF740D08C18A4FEA")); const Uuid KeePass2::KDF_AES = Uuid(QByteArray::fromHex("C9D9F39A628A4460BF740D08C18A4FEA"));
@ -30,6 +31,7 @@ const QByteArray KeePass2::INNER_STREAM_SALSA20_IV("\xE8\x30\x09\x4B\x97\x20\x5D
const QList<KeePass2::UuidNamePair> KeePass2::CIPHERS { const QList<KeePass2::UuidNamePair> KeePass2::CIPHERS {
KeePass2::UuidNamePair(KeePass2::CIPHER_AES, "AES: 256-bit"), KeePass2::UuidNamePair(KeePass2::CIPHER_AES, "AES: 256-bit"),
KeePass2::UuidNamePair(KeePass2::CIPHER_TWOFISH, "Twofish: 256-bit"), KeePass2::UuidNamePair(KeePass2::CIPHER_TWOFISH, "Twofish: 256-bit"),
KeePass2::UuidNamePair(KeePass2::CIPHER_CHACHA20, "ChaCha20: 256-bit")
}; };
const QList<KeePass2::UuidNamePair> KeePass2::KDFS { const QList<KeePass2::UuidNamePair> KeePass2::KDFS {
KeePass2::UuidNamePair(KeePass2::KDF_AES, "AES-KDF"), KeePass2::UuidNamePair(KeePass2::KDF_AES, "AES-KDF"),
@ -53,6 +55,20 @@ Uuid KeePass2::kdfToUuid(const Kdf& kdf)
} }
} }
KeePass2::ProtectedStreamAlgo KeePass2::idToProtectedStreamAlgo(quint32 id)
{
switch (id) {
case static_cast<quint32>(KeePass2::ArcFourVariant):
return KeePass2::ArcFourVariant;
case static_cast<quint32>(KeePass2::Salsa20):
return KeePass2::Salsa20;
case static_cast<quint32>(KeePass2::ChaCha20):
return KeePass2::ChaCha20;
default:
return KeePass2::InvalidProtectedStreamAlgo;
}
}
KeePass2::UuidNamePair::UuidNamePair(const Uuid& uuid, const QString& name) KeePass2::UuidNamePair::UuidNamePair(const Uuid& uuid, const QString& name)
: m_uuid(uuid) : m_uuid(uuid)
, m_name(name) , m_name(name)

View File

@ -37,6 +37,7 @@ namespace KeePass2
extern const Uuid CIPHER_AES; extern const Uuid CIPHER_AES;
extern const Uuid CIPHER_TWOFISH; extern const Uuid CIPHER_TWOFISH;
extern const Uuid CIPHER_CHACHA20;
extern const Uuid KDF_AES; extern const Uuid KDF_AES;
@ -75,11 +76,14 @@ namespace KeePass2
enum ProtectedStreamAlgo enum ProtectedStreamAlgo
{ {
ArcFourVariant = 1, ArcFourVariant = 1,
Salsa20 = 2 Salsa20 = 2,
ChaCha20 = 3,
InvalidProtectedStreamAlgo = -1
}; };
Kdf* uuidToKdf(const Uuid& uuid); Kdf* uuidToKdf(const Uuid& uuid);
Uuid kdfToUuid(const Kdf& kdf); Uuid kdfToUuid(const Kdf& kdf);
ProtectedStreamAlgo idToProtectedStreamAlgo(quint32 id);
} }
#endif // KEEPASSX_KEEPASS2_H #endif // KEEPASSX_KEEPASS2_H

View File

@ -20,16 +20,27 @@
#include "crypto/CryptoHash.h" #include "crypto/CryptoHash.h"
#include "format/KeePass2.h" #include "format/KeePass2.h"
KeePass2RandomStream::KeePass2RandomStream() KeePass2RandomStream::KeePass2RandomStream(KeePass2::ProtectedStreamAlgo algo)
: m_cipher(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt) : m_cipher(mapAlgo(algo), SymmetricCipher::Stream, SymmetricCipher::Encrypt)
, m_offset(0) , m_offset(0)
{ {
} }
bool KeePass2RandomStream::init(const QByteArray& key) bool KeePass2RandomStream::init(const QByteArray& key)
{ {
return m_cipher.init(CryptoHash::hash(key, CryptoHash::Sha256), switch (m_cipher.algorithm()) {
KeePass2::INNER_STREAM_SALSA20_IV); case SymmetricCipher::Salsa20:
return m_cipher.init(CryptoHash::hash(key, CryptoHash::Sha256),
KeePass2::INNER_STREAM_SALSA20_IV);
case SymmetricCipher::ChaCha20: {
QByteArray keyIv = CryptoHash::hash(key, CryptoHash::Sha512);
return m_cipher.init(keyIv.left(32), keyIv.mid(32, 12));
}
default:
qWarning("Invalid stream algorithm (%d)", m_cipher.algorithm());
break;
}
return false;
} }
QByteArray KeePass2RandomStream::randomBytes(int size, bool* ok) QByteArray KeePass2RandomStream::randomBytes(int size, bool* ok)
@ -109,3 +120,14 @@ bool KeePass2RandomStream::loadBlock()
return true; return true;
} }
SymmetricCipher::Algorithm KeePass2RandomStream::mapAlgo(KeePass2::ProtectedStreamAlgo algo) {
switch (algo) {
case KeePass2::ChaCha20:
return SymmetricCipher::ChaCha20;
case KeePass2::Salsa20:
return SymmetricCipher::Salsa20;
default:
return SymmetricCipher::InvalidAlgorithm;
}
}

View File

@ -21,11 +21,13 @@
#include <QByteArray> #include <QByteArray>
#include "crypto/SymmetricCipher.h" #include "crypto/SymmetricCipher.h"
#include "KeePass2.h"
class KeePass2RandomStream class KeePass2RandomStream
{ {
public: public:
KeePass2RandomStream(); KeePass2RandomStream(KeePass2::ProtectedStreamAlgo algo);
bool init(const QByteArray& key); bool init(const QByteArray& key);
QByteArray randomBytes(int size, bool* ok); QByteArray randomBytes(int size, bool* ok);
QByteArray process(const QByteArray& data, bool* ok); QByteArray process(const QByteArray& data, bool* ok);
@ -38,6 +40,8 @@ private:
SymmetricCipher m_cipher; SymmetricCipher m_cipher;
QByteArray m_buffer; QByteArray m_buffer;
int m_offset; int m_offset;
static SymmetricCipher::Algorithm mapAlgo(KeePass2::ProtectedStreamAlgo algo);
}; };
#endif // KEEPASSX_KEEPASS2RANDOMSTREAM_H #endif // KEEPASSX_KEEPASS2RANDOMSTREAM_H

View File

@ -41,6 +41,7 @@ KeePass2Reader::KeePass2Reader()
, m_headerEnd(false) , m_headerEnd(false)
, m_saveXml(false) , m_saveXml(false)
, m_db(nullptr) , m_db(nullptr)
, m_irsAlgo(KeePass2::InvalidProtectedStreamAlgo)
{ {
} }
@ -164,7 +165,7 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke
xmlDevice = ioCompressor.data(); xmlDevice = ioCompressor.data();
} }
KeePass2RandomStream randomStream; KeePass2RandomStream randomStream(m_irsAlgo);
if (!randomStream.init(m_protectedStreamKey)) { if (!randomStream.init(m_protectedStreamKey)) {
raiseError(randomStream.errorString()); raiseError(randomStream.errorString());
return nullptr; return nullptr;
@ -447,9 +448,14 @@ void KeePass2Reader::setInnerRandomStreamID(const QByteArray& data)
} }
else { else {
quint32 id = Endian::bytesToUInt32(data, KeePass2::BYTEORDER); quint32 id = Endian::bytesToUInt32(data, KeePass2::BYTEORDER);
m_irsAlgo = KeePass2::idToProtectedStreamAlgo(id);
if (id != KeePass2::Salsa20) { if (m_irsAlgo == KeePass2::ArcFourVariant || m_irsAlgo == KeePass2::InvalidProtectedStreamAlgo) {
raiseError("Unsupported random stream algorithm"); raiseError("Unsupported random stream algorithm");
} }
} }
} }
KeePass2::ProtectedStreamAlgo KeePass2Reader::protectedStreamAlgo() const
{
return m_irsAlgo;
}

View File

@ -21,6 +21,7 @@
#include <QCoreApplication> #include <QCoreApplication>
#include "keys/CompositeKey.h" #include "keys/CompositeKey.h"
#include "format/KeePass2.h"
class Database; class Database;
class QIODevice; class QIODevice;
@ -38,6 +39,7 @@ public:
void setSaveXml(bool save); void setSaveXml(bool save);
QByteArray xmlData(); QByteArray xmlData();
QByteArray streamKey(); QByteArray streamKey();
KeePass2::ProtectedStreamAlgo protectedStreamAlgo() const;
private: private:
void raiseError(const QString& errorMessage); void raiseError(const QString& errorMessage);
@ -67,6 +69,7 @@ private:
QByteArray m_encryptionIV; QByteArray m_encryptionIV;
QByteArray m_streamStartBytes; QByteArray m_streamStartBytes;
QByteArray m_protectedStreamKey; QByteArray m_protectedStreamKey;
KeePass2::ProtectedStreamAlgo m_irsAlgo;
}; };
#endif // KEEPASSX_KEEPASS2READER_H #endif // KEEPASSX_KEEPASS2READER_H

View File

@ -71,7 +71,7 @@ KeePass2Repair::RepairOutcome KeePass2Repair::repairDatabase(QIODevice* device,
return qMakePair(RepairFailed, nullptr); return qMakePair(RepairFailed, nullptr);
} }
KeePass2RandomStream randomStream; KeePass2RandomStream randomStream(reader.protectedStreamAlgo());
randomStream.init(reader.streamKey()); randomStream.init(reader.streamKey());
KeePass2XmlReader xmlReader; KeePass2XmlReader xmlReader;
QBuffer buffer(&xmlData); QBuffer buffer(&xmlData);

View File

@ -131,7 +131,7 @@ void KeePass2Writer::writeDatabase(QIODevice* device, Database* db)
m_device = ioCompressor.data(); m_device = ioCompressor.data();
} }
KeePass2RandomStream randomStream; KeePass2RandomStream randomStream(KeePass2::Salsa20);
if (!randomStream.init(protectedStreamKey)) { if (!randomStream.init(protectedStreamKey)) {
raiseError(randomStream.errorString()); raiseError(randomStream.errorString());
return; return;

View File

@ -0,0 +1,267 @@
/*
* Copyright (C) 2017 KeePassXC Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "HmacBlockStream.h"
#include "core/Endian.h"
#include "crypto/CryptoHash.h"
const QSysInfo::Endian HmacBlockStream::ByteOrder = QSysInfo::LittleEndian;
HmacBlockStream::HmacBlockStream(QIODevice* baseDevice, QByteArray key)
: LayeredStream(baseDevice)
, m_blockSize(1024*1024)
, m_key(key)
{
init();
}
HmacBlockStream::HmacBlockStream(QIODevice* baseDevice, QByteArray key, qint32 blockSize)
: LayeredStream(baseDevice)
, m_blockSize(blockSize)
, m_key(key)
{
init();
}
HmacBlockStream::~HmacBlockStream()
{
close();
}
void HmacBlockStream::init()
{
m_buffer.clear();
m_bufferPos = 0;
m_blockIndex = 0;
m_eof = false;
m_error = false;
}
bool HmacBlockStream::reset()
{
// Write final block(s) only if device is writable and we haven't
// already written a final block.
if (isWritable() && (!m_buffer.isEmpty() || m_blockIndex != 0)) {
if (!m_buffer.isEmpty()) {
if (!writeHashedBlock()) {
return false;
}
}
// write empty final block
if (!writeHashedBlock()) {
return false;
}
}
init();
return true;
}
void HmacBlockStream::close()
{
// Write final block(s) only if device is writable and we haven't
// already written a final block.
if (isWritable() && (!m_buffer.isEmpty() || m_blockIndex != 0)) {
if (!m_buffer.isEmpty()) {
writeHashedBlock();
}
// write empty final block
writeHashedBlock();
}
LayeredStream::close();
}
qint64 HmacBlockStream::readData(char* data, qint64 maxSize)
{
if (m_error) {
return -1;
}
else if (m_eof) {
return 0;
}
qint64 bytesRemaining = maxSize;
qint64 offset = 0;
while (bytesRemaining > 0) {
if (m_bufferPos == m_buffer.size()) {
if (!readHashedBlock()) {
if (m_error) {
return -1;
}
else {
return maxSize - bytesRemaining;
}
}
}
int bytesToCopy = qMin(bytesRemaining, static_cast<qint64>(m_buffer.size() - m_bufferPos));
memcpy(data + offset, m_buffer.constData() + m_bufferPos, bytesToCopy);
offset += bytesToCopy;
m_bufferPos += bytesToCopy;
bytesRemaining -= bytesToCopy;
}
return maxSize;
}
bool HmacBlockStream::readHashedBlock()
{
if (m_eof) {
return false;
}
QByteArray hmac = m_baseDevice->read(32);
if (hmac.size() != 32) {
m_error = true;
setErrorString("Invalid HMAC size.");
return false;
}
QByteArray blockSizeBytes = m_baseDevice->read(4);
if (blockSizeBytes.size() != 4) {
m_error = true;
setErrorString("Invalid block size size.");
return false;
}
qint32 blockSize = Endian::bytesToInt32(blockSizeBytes, ByteOrder);
if (blockSize < 0) {
m_error = true;
setErrorString("Invalid block size.");
return false;
}
m_buffer = m_baseDevice->read(blockSize);
if (m_buffer.size() != blockSize) {
m_error = true;
setErrorString("Block too short.");
return false;
}
CryptoHash hasher(CryptoHash::Sha256, true);
hasher.setKey(getCurrentHmacKey());
hasher.addData(Endian::uint64ToBytes(m_blockIndex, ByteOrder));
hasher.addData(blockSizeBytes);
hasher.addData(m_buffer);
if (hmac != hasher.result()) {
m_error = true;
setErrorString("Mismatch between hash and data.");
return false;
}
m_bufferPos = 0;
m_blockIndex++;
if (blockSize == 0) {
m_eof = true;
return false;
}
return true;
}
qint64 HmacBlockStream::writeData(const char* data, qint64 maxSize)
{
Q_ASSERT(maxSize >= 0);
if (m_error) {
return 0;
}
qint64 bytesRemaining = maxSize;
qint64 offset = 0;
while (bytesRemaining > 0) {
int bytesToCopy = qMin(bytesRemaining, static_cast<qint64>(m_blockSize - m_buffer.size()));
m_buffer.append(data + offset, bytesToCopy);
offset += bytesToCopy;
bytesRemaining -= bytesToCopy;
if (m_buffer.size() == m_blockSize) {
if (!writeHashedBlock()) {
if (m_error) {
return -1;
}
else {
return maxSize - bytesRemaining;
}
}
}
}
return maxSize;
}
bool HmacBlockStream::writeHashedBlock()
{
CryptoHash hasher(CryptoHash::Sha256, true);
hasher.setKey(getCurrentHmacKey());
hasher.addData(Endian::uint64ToBytes(m_blockIndex, ByteOrder));
hasher.addData(Endian::int32ToBytes(m_buffer.size(), ByteOrder));
hasher.addData(m_buffer);
QByteArray hash = hasher.result();
if (m_baseDevice->write(hash) != hash.size()) {
m_error = true;
setErrorString(m_baseDevice->errorString());
return false;
}
if (!Endian::writeInt32(m_buffer.size(), m_baseDevice, ByteOrder)) {
m_error = true;
setErrorString(m_baseDevice->errorString());
return false;
}
if (!m_buffer.isEmpty()) {
if (m_baseDevice->write(m_buffer) != m_buffer.size()) {
m_error = true;
setErrorString(m_baseDevice->errorString());
return false;
}
m_buffer.clear();
}
m_blockIndex++;
return true;
}
QByteArray HmacBlockStream::getCurrentHmacKey() const {
return getHmacKey(m_blockIndex, m_key);
}
QByteArray HmacBlockStream::getHmacKey(quint64 blockIndex, QByteArray key) {
Q_ASSERT(key.size() == 64);
QByteArray indexBytes = Endian::uint64ToBytes(blockIndex, ByteOrder);
CryptoHash hasher(CryptoHash::Sha512);
hasher.addData(indexBytes);
hasher.addData(key);
return hasher.result();
}
bool HmacBlockStream::atEnd() const {
return m_eof;
}

View File

@ -0,0 +1,61 @@
/*
* Copyright (C) 2017 KeePassXC Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSX_HMACBLOCKSTREAM_H
#define KEEPASSX_HMACBLOCKSTREAM_H
#include <QSysInfo>
#include "streams/LayeredStream.h"
class HmacBlockStream : public LayeredStream
{
Q_OBJECT
public:
explicit HmacBlockStream(QIODevice* baseDevice, QByteArray key);
HmacBlockStream(QIODevice* baseDevice, QByteArray key, qint32 blockSize);
~HmacBlockStream();
bool reset() override;
void close() override;
static QByteArray getHmacKey(quint64 blockIndex, QByteArray key);
bool atEnd() const override;
protected:
qint64 readData(char* data, qint64 maxSize) override;
qint64 writeData(const char* data, qint64 maxSize) override;
private:
void init();
bool readHashedBlock();
bool writeHashedBlock();
QByteArray getCurrentHmacKey() const;
static const QSysInfo::Endian ByteOrder;
qint32 m_blockSize;
QByteArray m_buffer;
QByteArray m_key;
int m_bufferPos;
quint64 m_blockIndex;
bool m_eof;
bool m_error;
};
#endif // KEEPASSX_HMACBLOCKSTREAM_H

View File

@ -44,4 +44,17 @@ void TestCryptoHash::test()
cryptoHash3.addData(QString("ssX").toLatin1()); cryptoHash3.addData(QString("ssX").toLatin1());
QCOMPARE(cryptoHash3.result(), QCOMPARE(cryptoHash3.result(),
QByteArray::fromHex("0b56e5f65263e747af4a833bd7dd7ad26a64d7a4de7c68e52364893dca0766b4")); QByteArray::fromHex("0b56e5f65263e747af4a833bd7dd7ad26a64d7a4de7c68e52364893dca0766b4"));
CryptoHash cryptoHash2(CryptoHash::Sha512);
QCOMPARE(cryptoHash2.result(),
QByteArray::fromHex("cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"));
QByteArray result3 = CryptoHash::hash(source2, CryptoHash::Sha512);
QCOMPARE(result3, QByteArray::fromHex("0d41b612584ed39ff72944c29494573e40f4bb95283455fae2e0be1e3565aa9f48057d59e6ffd777970e282871c25a549a2763e5b724794f312c97021c42f91d"));
CryptoHash cryptoHash4(CryptoHash::Sha512);
cryptoHash4.addData(QString("KeePa").toLatin1());
cryptoHash4.addData(QString("ssX").toLatin1());
QCOMPARE(cryptoHash4.result(),
QByteArray::fromHex("0d41b612584ed39ff72944c29494573e40f4bb95283455fae2e0be1e3565aa9f48057d59e6ffd777970e282871c25a549a2763e5b724794f312c97021c42f91d"));
} }

View File

@ -58,7 +58,7 @@ void TestKeePass2RandomStream::test()
} }
KeePass2RandomStream randomStream; KeePass2RandomStream randomStream(KeePass2::Salsa20);
bool ok; bool ok;
QVERIFY(randomStream.init(key)); QVERIFY(randomStream.init(key));
QByteArray randomStreamData; QByteArray randomStreamData;

View File

@ -342,6 +342,56 @@ void TestSymmetricCipher::testSalsa20()
QCOMPARE(cipherTextB.mid(448, 64), expectedCipherText4); QCOMPARE(cipherTextB.mid(448, 64), expectedCipherText4);
} }
void TestSymmetricCipher::testChaCha20()
{
// https://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-04#section-7
bool ok;
{
QByteArray key = QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000000");
QByteArray iv = QByteArray::fromHex("0000000000000000");
SymmetricCipher cipher(SymmetricCipher::ChaCha20, SymmetricCipher::Stream, SymmetricCipher::Encrypt);
QVERIFY(cipher.init(key, iv));
QCOMPARE(cipher.process(QByteArray(64, 0), &ok),
QByteArray::fromHex(
"76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586"));
QVERIFY(ok);
}
{
QByteArray key = QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000001");
QByteArray iv = QByteArray::fromHex("0000000000000000");
SymmetricCipher cipher(SymmetricCipher::ChaCha20, SymmetricCipher::Stream, SymmetricCipher::Encrypt);
QVERIFY(cipher.init(key, iv));
QCOMPARE(cipher.process(QByteArray(64, 0), &ok),
QByteArray::fromHex(
"4540f05a9f1fb296d7736e7b208e3c96eb4fe1834688d2604f450952ed432d41bbe2a0b6ea7566d2a5d1e7e20d42af2c53d792b1c43fea817e9ad275ae546963"));
QVERIFY(ok);
}
{
QByteArray key = QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000000");
QByteArray iv = QByteArray::fromHex("0000000000000001");
SymmetricCipher cipher(SymmetricCipher::ChaCha20, SymmetricCipher::Stream, SymmetricCipher::Encrypt);
QVERIFY(cipher.init(key, iv));
QCOMPARE(cipher.process(QByteArray(60, 0), &ok),
QByteArray::fromHex(
"de9cba7bf3d69ef5e786dc63973f653a0b49e015adbff7134fcb7df137821031e85a050278a7084527214f73efc7fa5b5277062eb7a0433e445f41e3"));
QVERIFY(ok);
}
{
QByteArray key = QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000000");
QByteArray iv = QByteArray::fromHex("0100000000000000");
SymmetricCipher cipher(SymmetricCipher::ChaCha20, SymmetricCipher::Stream, SymmetricCipher::Encrypt);
QVERIFY(cipher.init(key, iv));
QCOMPARE(cipher.process(QByteArray(64, 0), &ok),
QByteArray::fromHex(
"ef3fdfd6c61578fbf5cf35bd3dd33b8009631634d21e42ac33960bd138e50d32111e4caf237ee53ca8ad6426194a88545ddc497a0b466e7d6bbdb0041b2f586b"));
QVERIFY(ok);
}
}
void TestSymmetricCipher::testPadding() void TestSymmetricCipher::testPadding()
{ {
QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"); QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");

View File

@ -34,6 +34,7 @@ private slots:
void testTwofish256CbcEncryption(); void testTwofish256CbcEncryption();
void testTwofish256CbcDecryption(); void testTwofish256CbcDecryption();
void testSalsa20(); void testSalsa20();
void testChaCha20();
void testPadding(); void testPadding();
void testStreamReset(); void testStreamReset();
}; };