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

@ -22,6 +22,7 @@
const Uuid KeePass2::CIPHER_AES = Uuid(QByteArray::fromHex("31c1f2e6bf714350be5805216afc5aff"));
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"));
@ -30,6 +31,7 @@ const QByteArray KeePass2::INNER_STREAM_SALSA20_IV("\xE8\x30\x09\x4B\x97\x20\x5D
const QList<KeePass2::UuidNamePair> KeePass2::CIPHERS {
KeePass2::UuidNamePair(KeePass2::CIPHER_AES, "AES: 256-bit"),
KeePass2::UuidNamePair(KeePass2::CIPHER_TWOFISH, "Twofish: 256-bit"),
KeePass2::UuidNamePair(KeePass2::CIPHER_CHACHA20, "ChaCha20: 256-bit")
};
const QList<KeePass2::UuidNamePair> KeePass2::KDFS {
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)
: m_uuid(uuid)
, m_name(name)

View file

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

View file

@ -20,16 +20,27 @@
#include "crypto/CryptoHash.h"
#include "format/KeePass2.h"
KeePass2RandomStream::KeePass2RandomStream()
: m_cipher(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt)
KeePass2RandomStream::KeePass2RandomStream(KeePass2::ProtectedStreamAlgo algo)
: m_cipher(mapAlgo(algo), SymmetricCipher::Stream, SymmetricCipher::Encrypt)
, m_offset(0)
{
}
bool KeePass2RandomStream::init(const QByteArray& key)
{
return m_cipher.init(CryptoHash::hash(key, CryptoHash::Sha256),
KeePass2::INNER_STREAM_SALSA20_IV);
switch (m_cipher.algorithm()) {
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)
@ -109,3 +120,14 @@ bool KeePass2RandomStream::loadBlock()
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 "crypto/SymmetricCipher.h"
#include "KeePass2.h"
class KeePass2RandomStream
{
public:
KeePass2RandomStream();
KeePass2RandomStream(KeePass2::ProtectedStreamAlgo algo);
bool init(const QByteArray& key);
QByteArray randomBytes(int size, bool* ok);
QByteArray process(const QByteArray& data, bool* ok);
@ -38,6 +40,8 @@ private:
SymmetricCipher m_cipher;
QByteArray m_buffer;
int m_offset;
static SymmetricCipher::Algorithm mapAlgo(KeePass2::ProtectedStreamAlgo algo);
};
#endif // KEEPASSX_KEEPASS2RANDOMSTREAM_H

View file

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

View file

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

View file

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

View file

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