diff options
author | MÃ¥rten Nordheim <marten.nordheim@qt.io> | 2018-01-19 12:27:58 +0100 |
---|---|---|
committer | Simon Hausmann <simon.hausmann@qt.io> | 2018-04-23 12:55:45 +0000 |
commit | 72bb1d95fd99ece1c79223ad889fc7dbddcc36c6 (patch) | |
tree | a8619d84f314e1c33362f519a3c47fca6a209248 /src/network/ssl/qpassworddigestor.cpp | |
parent | c45802e33a564bfca4745e31193bc1c2fb3520fa (diff) |
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>
Diffstat (limited to 'src/network/ssl/qpassworddigestor.cpp')
-rw-r--r-- | src/network/ssl/qpassworddigestor.cpp | 187 |
1 files changed, 187 insertions, 0 deletions
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 <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 |