Introduce QPasswordDigestor functions

Added a few functions to derive keys from passwords. Currently it
supports PBKDF1 and PBKDF2 as defined in
RFC 8018 ( https://tools.ietf.org/html/rfc8018 ).

[ChangeLog][QtNetwork][QPasswordDigestor] Added QPasswordDigestor

Task-number: QTBUG-30550
Change-Id: I2166b518bd8b54e3486514166e76fd9ba2f219c8
Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
This commit is contained in:
Mårten Nordheim 2018-01-19 12:27:58 +01:00 committed by Simon Hausmann
parent c45802e33a
commit 72bb1d95fd
6 changed files with 426 additions and 0 deletions

View File

@ -0,0 +1,187 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qpassworddigestor.h"
#include <QtCore/QDebug>
#include <QtCore/QMessageAuthenticationCode>
#include <QtCore/QtEndian>
#include <limits>
QT_BEGIN_NAMESPACE
namespace QPasswordDigestor {
/*!
\namespace QPasswordDigestor
\inmodule QtNetwork
\brief The QPasswordDigestor namespace contains functions which you can use
to generate hashes or keys.
*/
/*!
\since 5.12
Returns a hash computed using the PBKDF1-algorithm as defined in
\l {https://tools.ietf.org/html/rfc8018#section-5.1} {RFC 8018}.
The function takes the \a data and \a salt, and then hashes it repeatedly
for \a iterations iterations using the specified hash \a algorithm. If the
resulting hash is longer than \a dkLen then it is truncated before it is
returned.
This function only supports SHA-1 and MD5! The max output size is 160 bits
(20 bytes) when using SHA-1, or 128 bits (16 bytes) when using MD5.
Specifying a value for \a dkLen which is greater than this results in a
warning and an empty QByteArray is returned. To programmatically check this
limit you can use \l {QCryptographicHash::hashLength}. Furthermore: the
\a salt must always be 8 bytes long!
\note This function is provided for use with legacy applications and all
new applications are recommended to use \l {pbkdf2} {PBKDF2}.
\sa deriveKeyPbkdf2, QCryptographicHash, QCryptographicHash::hashLength
*/
Q_NETWORK_EXPORT QByteArray deriveKeyPbkdf1(QCryptographicHash::Algorithm algorithm,
const QByteArray &data, const QByteArray &salt,
int iterations, quint64 dkLen)
{
// https://tools.ietf.org/html/rfc8018#section-5.1
if (algorithm != QCryptographicHash::Sha1
#ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
&& algorithm != QCryptographicHash::Md5
#endif
) {
qWarning("The only supported algorithms for pbkdf1 are SHA-1 and MD5!");
return QByteArray();
}
if (salt.size() != 8) {
qWarning("The salt must be 8 bytes long!");
return QByteArray();
}
if (iterations < 1 || dkLen < 1)
return QByteArray();
if (dkLen > quint64(QCryptographicHash::hashLength(algorithm))) {
qWarning() << "Derived key too long:\n"
<< algorithm << "was chosen which produces output of length"
<< QCryptographicHash::hashLength(algorithm) << "but" << dkLen
<< "was requested.";
return QByteArray();
}
QCryptographicHash hash(algorithm);
hash.addData(data);
hash.addData(salt);
QByteArray key = hash.result();
for (int i = 1; i < iterations; i++) {
hash.reset();
hash.addData(key);
key = hash.result();
}
return key.left(dkLen);
}
/*!
\since 5.12
Derive a key using the PBKDF2-algorithm as defined in
\l {https://tools.ietf.org/html/rfc8018#section-5.2} {RFC 8018}.
This function takes the \a data and \a salt, and then applies HMAC-X, where
the X is \a algorithm, repeatedly. It internally concatenates intermediate
results to the final output until at least \a dkLen amount of bytes have
been computed and it will execute HMAC-X \a iterations times each time a
concatenation is required. The total number of times it will execute HMAC-X
depends on \a iterations, \a dkLen and \a algorithm and can be calculated
as
\c{iterations * ceil(dkLen / QCryptographicHash::hashLength(algorithm))}.
\sa deriveKeyPbkdf1, QMessageAuthenticationCode, QCryptographicHash
*/
Q_NETWORK_EXPORT QByteArray deriveKeyPbkdf2(QCryptographicHash::Algorithm algorithm,
const QByteArray &data, const QByteArray &salt,
int iterations, quint64 dkLen)
{
// https://tools.ietf.org/html/rfc8018#section-5.2
// The RFC recommends checking that 'dkLen' is not greater than '(2^32 - 1) * hLen'
int hashLen = QCryptographicHash::hashLength(algorithm);
const quint64 maxLen = quint64(std::numeric_limits<quint32>::max() - 1) * hashLen;
if (dkLen > maxLen) {
qWarning().nospace() << "Derived key too long:\n"
<< algorithm << " was chosen which produces output of length "
<< maxLen << " but " << dkLen << " was requested.";
return QByteArray();
}
if (iterations < 1 || dkLen < 1)
return QByteArray();
QByteArray key;
quint32 currentIteration = 1;
QMessageAuthenticationCode hmac(algorithm, data);
QByteArray index(4, Qt::Uninitialized);
while (quint64(key.length()) < dkLen) {
hmac.addData(salt);
qToBigEndian(currentIteration, index.data());
hmac.addData(index);
QByteArray u = hmac.result();
hmac.reset();
QByteArray tkey = u;
for (int iter = 1; iter < iterations; iter++) {
hmac.addData(u);
u = hmac.result();
hmac.reset();
std::transform(tkey.cbegin(), tkey.cend(), u.cbegin(), tkey.begin(),
std::bit_xor<char>());
}
key += tkey;
currentIteration++;
}
return key.left(dkLen);
}
} // namespace QPasswordDigestor
QT_END_NAMESPACE

View File

@ -0,0 +1,60 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QPASSWORDDIGESTOR_H
#define QPASSWORDDIGESTOR_H
#include <QtNetwork/qtnetworkglobal.h>
#include <QtCore/QByteArray>
#include <QtCore/QCryptographicHash>
QT_BEGIN_NAMESPACE
namespace QPasswordDigestor {
Q_NETWORK_EXPORT QByteArray deriveKeyPbkdf1(QCryptographicHash::Algorithm algorithm,
const QByteArray &password, const QByteArray &salt,
int iterations, quint64 dkLen);
Q_NETWORK_EXPORT QByteArray deriveKeyPbkdf2(QCryptographicHash::Algorithm algorithm,
const QByteArray &password, const QByteArray &salt,
int iterations, quint64 dkLen);
} // namespace QPasswordDigestor
QT_END_NAMESPACE
#endif // QPASSWORDDIGESTOR_H

View File

@ -102,3 +102,6 @@ qtConfig(ssl) {
}
}
}
HEADERS += ssl/qpassworddigestor.h
SOURCES += ssl/qpassworddigestor.cpp

View File

@ -0,0 +1,4 @@
CONFIG += testcase
TARGET = tst_qpassworddigestor
QT = core network testlib
SOURCES = tst_qpassworddigestor.cpp

View File

@ -0,0 +1,171 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtTest/QtTest>
#include <QtNetwork/qpassworddigestor.h>
#include <QtCore/QByteArray>
class tst_QPasswordDigestor : public QObject
{
Q_OBJECT
private Q_SLOTS:
void pbkdf1Vectors_data();
void pbkdf1Vectors();
void pbkdf2Vectors_data();
void pbkdf2Vectors();
};
void tst_QPasswordDigestor::pbkdf1Vectors_data()
{
QTest::addColumn<QCryptographicHash::Algorithm>("algorithm");
QTest::addColumn<QByteArray>("password");
QTest::addColumn<QByteArray>("salt");
QTest::addColumn<int>("iterations");
QTest::addColumn<int>("dkLen");
QTest::addColumn<QByteArray>("result");
// data from
// https://web.archive.org/web/20160912052752/https://www.di-mgt.com.au/cryptoKDFs.html#examplespbkdf
// (Note: this is not official, but at least it's something to compare with.)
QTest::newRow("di-mgt") << QCryptographicHash::Sha1 << QByteArray::fromHex("70617373776F7264")
<< QByteArray::fromHex("78578E5A5D63CB06") << 1000 << 16
<< QByteArray::fromHex("DC19847E05C64D2FAF10EBFB4A3D2A20");
}
void tst_QPasswordDigestor::pbkdf1Vectors()
{
QFETCH(QCryptographicHash::Algorithm, algorithm);
QFETCH(QByteArray, password);
QFETCH(QByteArray, salt);
QFETCH(int, iterations);
QFETCH(int, dkLen);
QFETCH(QByteArray, result);
QCOMPARE(QPasswordDigestor::deriveKeyPbkdf1(algorithm, password, salt, iterations, dkLen), result);
}
void tst_QPasswordDigestor::pbkdf2Vectors_data()
{
QTest::addColumn<QCryptographicHash::Algorithm>("algorithm");
QTest::addColumn<QByteArray>("password");
QTest::addColumn<QByteArray>("salt");
QTest::addColumn<int>("iterations");
QTest::addColumn<int>("dkLen");
QTest::addColumn<QByteArray>("result");
// data from https://tools.ietf.org/html/rfc6070
auto hash = QCryptographicHash::Sha1;
QTest::newRow("rfc6070-1") << hash << QByteArrayLiteral("password") << QByteArrayLiteral("salt")
<< 1 << 20
<< QByteArray::fromHex("0c60c80f961f0e71f3a9b524af6012062fe037a6");
QTest::newRow("rfc6070-2") << hash << QByteArrayLiteral("password") << QByteArrayLiteral("salt")
<< 2 << 20
<< QByteArray::fromHex("ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957");
QTest::newRow("rfc6070-3") << hash << QByteArrayLiteral("password") << QByteArrayLiteral("salt")
<< 4096 << 20
<< QByteArray::fromHex("4b007901b765489abead49d926f721d065a429c1");
#if 0
// Excluding: takes about 3 minutes to run
QTest::newRow("rfc6070-4") << hash << QByteArrayLiteral("password") << QByteArrayLiteral("salt")
<< 16777216 << 20
<< QByteArray::fromHex("eefe3d61cd4da4e4e9945b3d6ba2158c2634e984");
#endif
QTest::newRow("rfc6070-5") << hash << QByteArrayLiteral("passwordPASSWORDpassword")
<< QByteArrayLiteral("saltSALTsaltSALTsaltSALTsaltSALTsalt") << 4096
<< 25
<< QByteArray::fromHex(
"3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038");
QTest::newRow("rfc6070-6") << hash << QByteArrayLiteral("pass\0word")
<< QByteArrayLiteral("sa\0lt") << 4096 << 16
<< QByteArray::fromHex("56fa6aa75548099dcc37d7f03425e0c3");
// the next few bits of data are from https://tools.ietf.org/html/rfc3962#appendix-B
QByteArray password = QByteArrayLiteral("password");
QByteArray salt = QByteArrayLiteral("ATHENA.MIT.EDUraeburn");
QTest::newRow("rfc3962-1") << hash << password << salt << 1 << 16
<< QByteArray::fromHex("cdedb5281bb2f801565a1122b2563515");
QTest::newRow("rfc3962-2")
<< hash << password << salt << 1 << 32
<< QByteArray::fromHex("cdedb5281bb2f801565a1122b25635150ad1f7a04bb9f3a333ecc0e2e1f70837");
QTest::newRow("rfc3962-3") << hash << password << salt << 2 << 16
<< QByteArray::fromHex("01dbee7f4a9e243e988b62c73cda935d");
QTest::newRow("rfc3962-4")
<< hash << QByteArrayLiteral("password") << salt << 2 << 32
<< QByteArray::fromHex("01dbee7f4a9e243e988b62c73cda935da05378b93244ec8f48a99e61ad799d86");
QTest::newRow("rfc3962-5") << hash << password << salt << 1200 << 16
<< QByteArray::fromHex("5c08eb61fdf71e4e4ec3cf6ba1f5512b");
QTest::newRow("rfc3962-6")
<< hash << password << salt << 1200 << 32
<< QByteArray::fromHex("5c08eb61fdf71e4e4ec3cf6ba1f5512ba7e52ddbc5e5142f708a31e2e62b1e13");
salt = QByteArray::fromHex("1234567878563412"); // 0x1234567878563412
QTest::newRow("rfc3962-7") << hash << password << salt << 5 << 16
<< QByteArray::fromHex("d1daa78615f287e6a1c8b120d7062a49");
QTest::newRow("rfc3962-8")
<< hash << password << salt << 5 << 32
<< QByteArray::fromHex("d1daa78615f287e6a1c8b120d7062a493f98d203e6be49a6adf4fa574b6e64ee");
password = QByteArray(64, 'X');
salt = "pass phrase equals block size";
QTest::newRow("rfc3962-9") << hash << password << salt << 1200 << 16
<< QByteArray::fromHex("139c30c0966bc32ba55fdbf212530ac9");
QTest::newRow("rfc3962-10")
<< hash << password << salt << 1200 << 32
<< QByteArray::fromHex("139c30c0966bc32ba55fdbf212530ac9c5ec59f1a452f5cc9ad940fea0598ed1");
password.append('X');
salt = "pass phrase exceeds block size";
QTest::newRow("rfc3962-11") << hash << password << salt << 1200 << 16
<< QByteArray::fromHex("9ccad6d468770cd51b10e6a68721be61");
QTest::newRow("rfc3962-12")
<< hash << password << salt << 1200 << 32
<< QByteArray::fromHex("9ccad6d468770cd51b10e6a68721be611a8b4d282601db3b36be9246915ec82a");
password = QByteArray::fromHex("f09d849e"); // 0xf09d849e
salt = "EXAMPLE.COMpianist";
QTest::newRow("rfc3962-13") << hash << password << salt << 50 << 16
<< QByteArray::fromHex("6b9cf26d45455a43a5b8bb276a403b39");
QTest::newRow("rfc3962-14")
<< hash << password << salt << 50 << 32
<< QByteArray::fromHex("6b9cf26d45455a43a5b8bb276a403b39e7fe37a0c41e02c281ff3069e1e94f52");
}
void tst_QPasswordDigestor::pbkdf2Vectors()
{
QFETCH(QCryptographicHash::Algorithm, algorithm);
QFETCH(QByteArray, password);
QFETCH(QByteArray, salt);
QFETCH(int, iterations);
QFETCH(int, dkLen);
QFETCH(QByteArray, result);
QCOMPARE(QPasswordDigestor::deriveKeyPbkdf2(algorithm, password, salt, iterations, dkLen), result);
}
QTEST_MAIN(tst_QPasswordDigestor)
#include "tst_qpassworddigestor.moc"

View File

@ -2,6 +2,7 @@ TEMPLATE=subdirs
QT_FOR_CONFIG += network
SUBDIRS=\
qpassworddigestor \
qsslcertificate \
qsslcipher \
qsslellipticcurve \