Introduce HKDF

Summary:
It's needed for implementation of Secret Service:
https://specifications.freedesktop.org/secret-service/ch07s03.html

Reviewers: iromanov, sitter, #frameworks, dfaure

Reviewed By: dfaure

Subscribers: dfaure

Differential Revision: https://phabricator.kde.org/D15510
This commit is contained in:
Alexander Volkov 2018-10-15 14:27:03 +03:00
parent 98eead0058
commit f57d661416
6 changed files with 256 additions and 0 deletions

View File

@ -1078,6 +1078,63 @@ public:
: KeyDerivationFunction(withAlgorithm(QStringLiteral("pbkdf2"), algorithm), provider) {}
};
/**
\class HKDF qca_basic.h QtCrypto
\since 2.3
HMAC-based extract-and-expand key derivation function
This class implements HMAC-based Extract-and-Expand Key Derivation Function,
as specified in RFC5869.
\ingroup UserAPI
*/
class QCA_EXPORT HKDF : public Algorithm
{
public:
/**
Standard constructor
\param algorithm the name of the hashing algorithm to use
\param provider the name of the provider to use, if available
*/
explicit HKDF(const QString &algorithm = QStringLiteral("sha256"), const QString &provider = QString());
/**
Standard copy constructor
\param from the KeyDerivationFunction to copy from
*/
HKDF(const HKDF &from);
~HKDF();
/**
Assignment operator
Copies the state (including key) from one HKDF
to another
\param from the HKDF to assign from
*/
HKDF & operator=(const HKDF &from);
/**
Generate the key from a specified secret, salt value, and an additional info
\note key length is ignored for some functions
\param secret the secret (password or passphrase)
\param salt the salt to use
\param info the info to use
\param keyLength the length of key to return
\return the derived key
*/
SymmetricKey makeKey(const SecureArray &secret, const InitializationVector &salt,
const InitializationVector &info, unsigned int keyLength);
};
}
#endif

View File

@ -379,6 +379,40 @@ public:
unsigned int *iterationCount) = 0;
};
/**
\class HKDFContext qcaprovider.h QtCrypto
HKDF provider
\note This class is part of the provider plugin interface and should not
be used directly by applications. You probably want HKDF instead.
\ingroup ProviderAPI
*/
class QCA_EXPORT HKDFContext : public BasicContext
{
Q_OBJECT
public:
/**
Standard constructor
\param p the provider associated with this context
\param type the name of the HKDF provided by this context (including algorithm)
*/
HKDFContext(Provider *p, const QString &type) : BasicContext(p, type) {}
/**
Create a key and return it
\param secret the secret part (typically password)
\param salt the salt / initialization vector
\param info the info / initialization vector
\param keyLength the length of the key to be produced
*/
virtual SymmetricKey makeKey(const SecureArray &secret, const InitializationVector &salt,
const InitializationVector &info, unsigned int keyLength) = 0;
};
/**
\class DLGroupContext qcaprovider.h QtCrypto

View File

@ -34,6 +34,7 @@
#include <botan/filters.h>
#include <botan/hash.h>
#include <botan/pbkdf.h>
#include <botan/hkdf.h>
#include <botan/stream_cipher.h>
#endif
@ -226,6 +227,48 @@ protected:
Botan::S2K* m_s2k;
};
//-----------------------------------------------------------
class BotanHKDFContext: public QCA::HKDFContext
{
public:
BotanHKDFContext(const QString &hashName, QCA::Provider *p, const QString &type) : QCA::HKDFContext(p, type)
{
Botan::HMAC *hashObj;
#if BOTAN_VERSION_CODE < BOTAN_VERSION_CODE_FOR(2,0,0)
hashObj = new Botan::HMAC(Botan::global_state().algorithm_factory().make_hash_function(hashName.toStdString()));
#else
hashObj = new Botan::HMAC(Botan::HashFunction::create_or_throw(hashName.toStdString()).release());
#endif
m_hkdf = new Botan::HKDF(hashObj);
}
~BotanHKDFContext()
{
delete m_hkdf;
}
Context *clone() const
{
return new BotanHKDFContext( *this );
}
QCA::SymmetricKey makeKey(const QCA::SecureArray &secret, const QCA::InitializationVector &salt,
const QCA::InitializationVector &info, unsigned int keyLength)
{
std::string secretString(secret.data(), secret.size());
Botan::secure_vector<uint8_t> key(keyLength);
m_hkdf->kdf(key.data(), keyLength,
reinterpret_cast<const Botan::byte*>(secret.data()), secret.size(),
reinterpret_cast<const Botan::byte*>(salt.data()), salt.size(),
reinterpret_cast<const Botan::byte*>(info.data()), info.size());
QCA::SecureArray retval(QByteArray::fromRawData(reinterpret_cast<const char*>(key.data()), key.size()));
return QCA::SymmetricKey(retval);
}
protected:
Botan::HKDF* m_hkdf;
};
//-----------------------------------------------------------
class BotanCipherContext : public QCA::CipherContext
@ -416,6 +459,7 @@ public:
list += "pbkdf1(sha1)";
list += "pbkdf1(md2)";
list += "pbkdf2(sha1)";
list += "hkdf(sha256)";
list += "aes128-ecb";
list += "aes128-cbc";
list += "aes128-cfb";
@ -481,6 +525,8 @@ public:
return new BotanPBKDFContext( QString("PBKDF1(MD2)"), this, type );
else if ( type == "pbkdf2(sha1)" )
return new BotanPBKDFContext( QString("PBKDF2(SHA-1)"), this, type );
else if ( type == "hkdf(sha256)" )
return new BotanHKDFContext( QString("SHA-256"), this, type );
else if ( type == "aes128-ecb" )
return new BotanCipherContext( QString("AES-128"), QString("ECB"), QString("NoPadding"), this, type );
else if ( type == "aes128-cbc" )

View File

@ -29,6 +29,7 @@
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/kdf.h>
#include <stdio.h>
#include <stdlib.h>
@ -1276,6 +1277,35 @@ public:
protected:
};
class opensslHkdfContext : public HKDFContext
{
public:
opensslHkdfContext(Provider *p, const QString &type) : HKDFContext(p, type)
{
}
Provider::Context *clone() const
{
return new opensslHkdfContext( *this );
}
SymmetricKey makeKey(const SecureArray &secret, const InitializationVector &salt,
const InitializationVector &info, unsigned int keyLength)
{
SecureArray out(keyLength);
EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
EVP_PKEY_derive_init(pctx);
EVP_PKEY_CTX_set_hkdf_md(pctx, EVP_sha256());
EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt.data(), int(salt.size()));
EVP_PKEY_CTX_set1_hkdf_key(pctx, secret.data(), int(secret.size()));
EVP_PKEY_CTX_add1_hkdf_info(pctx, info.data(), int(info.size()));
size_t outlen = out.size();
EVP_PKEY_derive(pctx, reinterpret_cast<unsigned char*>(out.data()), &outlen);
EVP_PKEY_CTX_free(pctx);
return out;
}
};
class opensslHMACContext : public MACContext
{
public:
@ -7381,6 +7411,7 @@ public:
#endif
list += "pbkdf1(sha1)";
list += "pbkdf2(sha1)";
list += "hkdf(sha256)";
list += "pkey";
list += "dlgroup";
list += "rsa";
@ -7451,6 +7482,8 @@ public:
#endif
else if ( type == "pbkdf2(sha1)" )
return new opensslPbkdf2Context( this, type );
else if ( type == "hkdf(sha256)" )
return new opensslHkdfContext( this, type );
else if ( type == "hmac(md5)" )
return new opensslHMACContext( EVP_md5(), this, type );
else if ( type == "hmac(sha1)" )

View File

@ -591,4 +591,35 @@ QString KeyDerivationFunction::withAlgorithm(const QString &kdfType, const QStri
return (kdfType + '(' + algType + ')');
}
//----------------------------------------------------------------------------
// HKDF
//----------------------------------------------------------------------------
HKDF::HKDF(const QString &algorithm, const QString &provider)
: Algorithm(QStringLiteral("hkdf(") + algorithm + ')', provider)
{
}
HKDF::HKDF(const HKDF &from)
: Algorithm(from)
{
}
HKDF::~HKDF()
{
}
HKDF & HKDF::operator=(const HKDF &from)
{
Algorithm::operator=(from);
return *this;
}
SymmetricKey HKDF::makeKey(const SecureArray &secret, const InitializationVector &salt, const InitializationVector &info, unsigned int keyLength)
{
return static_cast<HKDFContext *>(context())->makeKey(secret,
salt,
info,
keyLength);
}
}

View File

@ -46,6 +46,8 @@ private slots:
void pbkdf2Tests();
void pbkdf2TimeTest();
void pbkdf2extraTests();
void hkdfTests_data();
void hkdfTests();
private:
QCA::Initializer* m_init;
};
@ -475,6 +477,59 @@ void KDFUnitTest::pbkdf2extraTests()
}
}
void KDFUnitTest::hkdfTests_data()
{
QTest::addColumn<QString>("secret"); // usually a password or passphrase
QTest::addColumn<QString>("salt"); // a salt or initialisation vector
QTest::addColumn<QString>("info"); // an additional info
QTest::addColumn<QString>("output"); // the key you get back
// RFC 5869, Appendix A
QTest::newRow("1") << QString("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b")
<< QString("000102030405060708090a0b0c")
<< QString("f0f1f2f3f4f5f6f7f8f9")
<< QString("3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865");
QTest::newRow("2") << QString("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f")
<< QString("606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf")
<< QString("b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff")
<< QString("b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71cc30c58179ec3e87c14c01d5c1f3434f1d87");
QTest::newRow("3") << QString("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b")
<< QString()
<< QString()
<< QString("8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8");
}
void KDFUnitTest::hkdfTests()
{
QStringList providersToTest;
providersToTest.append("qca-ossl");
//providersToTest.append("qca-gcrypt");
providersToTest.append("qca-botan");
QFETCH(QString, secret);
QFETCH(QString, salt);
QFETCH(QString, info);
QFETCH(QString, output);
foreach(QString provider, providersToTest) {
if(!QCA::isSupported("hkdf(sha256)", provider))
QWARN(QString("HKDF with SHA256 not supported for "+provider).toLocal8Bit());
else {
QCA::SecureArray password = QCA::hexToArray( secret );
QCA::InitializationVector saltv( QCA::hexToArray( salt ) );
QCA::InitializationVector infov( QCA::hexToArray( info ) );
QCA::SymmetricKey key = QCA::HKDF("sha256", provider).makeKey( password,
saltv,
infov,
output.size() / 2 );
QCOMPARE( QCA::arrayToHex( key.toByteArray() ), output );
}
}
}
QTEST_MAIN(KDFUnitTest)
#include "kdfunittest.moc"