diff --git a/include/QtCrypto/qca_basic.h b/include/QtCrypto/qca_basic.h --- a/include/QtCrypto/qca_basic.h +++ b/include/QtCrypto/qca_basic.h @@ -1078,6 +1078,63 @@ : 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 diff --git a/include/QtCrypto/qcaprovider.h b/include/QtCrypto/qcaprovider.h --- a/include/QtCrypto/qcaprovider.h +++ b/include/QtCrypto/qcaprovider.h @@ -379,6 +379,40 @@ 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 diff --git a/plugins/qca-botan/qca-botan.cpp b/plugins/qca-botan/qca-botan.cpp --- a/plugins/qca-botan/qca-botan.cpp +++ b/plugins/qca-botan/qca-botan.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #endif @@ -226,6 +227,48 @@ 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 key(keyLength); + m_hkdf->kdf(key.data(), keyLength, + reinterpret_cast(secret.data()), secret.size(), + reinterpret_cast(salt.data()), salt.size(), + reinterpret_cast(info.data()), info.size()); + QCA::SecureArray retval(QByteArray::fromRawData(reinterpret_cast(key.data()), key.size())); + return QCA::SymmetricKey(retval); + } + +protected: + Botan::HKDF* m_hkdf; +}; + //----------------------------------------------------------- class BotanCipherContext : public QCA::CipherContext @@ -416,6 +459,7 @@ 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 @@ 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" ) diff --git a/plugins/qca-ossl/qca-ossl.cpp b/plugins/qca-ossl/qca-ossl.cpp --- a/plugins/qca-ossl/qca-ossl.cpp +++ b/plugins/qca-ossl/qca-ossl.cpp @@ -29,6 +29,7 @@ #include #include +#include #include #include @@ -1276,6 +1277,35 @@ 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(out.data()), &outlen); + EVP_PKEY_CTX_free(pctx); + return out; + } +}; + class opensslHMACContext : public MACContext { public: @@ -7381,6 +7411,7 @@ #endif list += "pbkdf1(sha1)"; list += "pbkdf2(sha1)"; + list += "hkdf(sha256)"; list += "pkey"; list += "dlgroup"; list += "rsa"; @@ -7451,6 +7482,8 @@ #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)" ) diff --git a/src/qca_basic.cpp b/src/qca_basic.cpp --- a/src/qca_basic.cpp +++ b/src/qca_basic.cpp @@ -591,4 +591,35 @@ 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(context())->makeKey(secret, + salt, + info, + keyLength); +} + } diff --git a/unittest/kdfunittest/kdfunittest.cpp b/unittest/kdfunittest/kdfunittest.cpp --- a/unittest/kdfunittest/kdfunittest.cpp +++ b/unittest/kdfunittest/kdfunittest.cpp @@ -46,6 +46,8 @@ void pbkdf2Tests(); void pbkdf2TimeTest(); void pbkdf2extraTests(); + void hkdfTests_data(); + void hkdfTests(); private: QCA::Initializer* m_init; }; @@ -475,6 +477,59 @@ } } +void KDFUnitTest::hkdfTests_data() +{ + QTest::addColumn("secret"); // usually a password or passphrase + QTest::addColumn("salt"); // a salt or initialisation vector + QTest::addColumn("info"); // an additional info + QTest::addColumn("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"