diff --git a/src/network/ssl/qpassworddigestor.cpp b/src/network/ssl/qpassworddigestor.cpp new file mode 100644 index 0000000000..127d94e849 --- /dev/null +++ b/src/network/ssl/qpassworddigestor.cpp @@ -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 +#include +#include + +#include + +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::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()); + } + key += tkey; + currentIteration++; + } + return key.left(dkLen); +} +} // namespace QPasswordDigestor +QT_END_NAMESPACE diff --git a/src/network/ssl/qpassworddigestor.h b/src/network/ssl/qpassworddigestor.h new file mode 100644 index 0000000000..0f88643298 --- /dev/null +++ b/src/network/ssl/qpassworddigestor.h @@ -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 +#include +#include + +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 diff --git a/src/network/ssl/ssl.pri b/src/network/ssl/ssl.pri index b8cbe83089..3529c8828b 100644 --- a/src/network/ssl/ssl.pri +++ b/src/network/ssl/ssl.pri @@ -102,3 +102,6 @@ qtConfig(ssl) { } } } + +HEADERS += ssl/qpassworddigestor.h +SOURCES += ssl/qpassworddigestor.cpp diff --git a/tests/auto/network/ssl/qpassworddigestor/qpassworddigestor.pro b/tests/auto/network/ssl/qpassworddigestor/qpassworddigestor.pro new file mode 100644 index 0000000000..3e2685f579 --- /dev/null +++ b/tests/auto/network/ssl/qpassworddigestor/qpassworddigestor.pro @@ -0,0 +1,4 @@ +CONFIG += testcase +TARGET = tst_qpassworddigestor +QT = core network testlib +SOURCES = tst_qpassworddigestor.cpp diff --git a/tests/auto/network/ssl/qpassworddigestor/tst_qpassworddigestor.cpp b/tests/auto/network/ssl/qpassworddigestor/tst_qpassworddigestor.cpp new file mode 100644 index 0000000000..bbd6c72ca8 --- /dev/null +++ b/tests/auto/network/ssl/qpassworddigestor/tst_qpassworddigestor.cpp @@ -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 +#include +#include + +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("algorithm"); + QTest::addColumn("password"); + QTest::addColumn("salt"); + QTest::addColumn("iterations"); + QTest::addColumn("dkLen"); + QTest::addColumn("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("algorithm"); + QTest::addColumn("password"); + QTest::addColumn("salt"); + QTest::addColumn("iterations"); + QTest::addColumn("dkLen"); + QTest::addColumn("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" diff --git a/tests/auto/network/ssl/ssl.pro b/tests/auto/network/ssl/ssl.pro index 175f361071..5f2173044e 100644 --- a/tests/auto/network/ssl/ssl.pro +++ b/tests/auto/network/ssl/ssl.pro @@ -2,6 +2,7 @@ TEMPLATE=subdirs QT_FOR_CONFIG += network SUBDIRS=\ + qpassworddigestor \ qsslcertificate \ qsslcipher \ qsslellipticcurve \