diff --git a/include/QtCrypto/qca_basic.h b/include/QtCrypto/qca_basic.h
index 3806c297..4e1b0da5 100644
--- a/include/QtCrypto/qca_basic.h
+++ b/include/QtCrypto/qca_basic.h
@@ -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
diff --git a/include/QtCrypto/qcaprovider.h b/include/QtCrypto/qcaprovider.h
index d4b54f98..92f20547 100644
--- a/include/QtCrypto/qcaprovider.h
+++ b/include/QtCrypto/qcaprovider.h
@@ -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
 
diff --git a/plugins/qca-botan/qca-botan.cpp b/plugins/qca-botan/qca-botan.cpp
index 8822ab54..d6230748 100644
--- a/plugins/qca-botan/qca-botan.cpp
+++ b/plugins/qca-botan/qca-botan.cpp
@@ -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" )
diff --git a/plugins/qca-ossl/qca-ossl.cpp b/plugins/qca-ossl/qca-ossl.cpp
index 3ca9c7c4..6ddad895 100644
--- a/plugins/qca-ossl/qca-ossl.cpp
+++ b/plugins/qca-ossl/qca-ossl.cpp
@@ -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)" )
diff --git a/src/qca_basic.cpp b/src/qca_basic.cpp
index c2e10e1c..395ae8b0 100644
--- a/src/qca_basic.cpp
+++ b/src/qca_basic.cpp
@@ -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);
+}
+
 }
diff --git a/unittest/kdfunittest/kdfunittest.cpp b/unittest/kdfunittest/kdfunittest.cpp
index ed94fbaa..4683f6af 100644
--- a/unittest/kdfunittest/kdfunittest.cpp
+++ b/unittest/kdfunittest/kdfunittest.cpp
@@ -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"