1// Copyright (C) 2018 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:critical reason:cryptography
4
5#include "qpassworddigestor.h"
6
7#include <QtCore/QDebug>
8#include <QtCore/QMessageAuthenticationCode>
9#include <QtCore/QtEndian>
10#include <QtCore/QList>
11
12#include "qtcore-config_p.h"
13
14#include <limits>
15
16#if QT_CONFIG(opensslv30) && QT_CONFIG(openssl_linked)
17#define USING_OPENSSL30
18#include <openssl/core_names.h>
19#include <openssl/kdf.h>
20#include <openssl/params.h>
21#include <openssl/provider.h>
22#endif
23
24QT_BEGIN_NAMESPACE
25namespace QPasswordDigestor {
26
27/*!
28 \namespace QPasswordDigestor
29 \inmodule QtNetwork
30
31 \brief The QPasswordDigestor namespace contains functions which you can use
32 to generate hashes or keys.
33*/
34
35/*!
36 \since 5.12
37
38 Returns a hash computed using the PBKDF1-algorithm as defined in
39 \l {RFC 8018, section 5.1}.
40
41 The function takes the \a data and \a salt, and then hashes it repeatedly
42 for \a iterations iterations using the specified hash \a algorithm. If the
43 resulting hash is longer than \a dkLen then it is truncated before it is
44 returned.
45
46 This function only supports SHA-1 and MD5! The max output size is 160 bits
47 (20 bytes) when using SHA-1, or 128 bits (16 bytes) when using MD5.
48 Specifying a value for \a dkLen which is greater than this results in a
49 warning and an empty QByteArray is returned. To programmatically check this
50 limit you can use \l {QCryptographicHash::hashLength}. Furthermore: the
51 \a salt must always be 8 bytes long!
52
53 \note This function is provided for use with legacy applications and all
54 new applications are recommended to use \l {deriveKeyPbkdf2} {PBKDF2}.
55
56 \sa deriveKeyPbkdf2, QCryptographicHash, QCryptographicHash::hashLength
57*/
58Q_NETWORK_EXPORT QByteArray deriveKeyPbkdf1(QCryptographicHash::Algorithm algorithm,
59 const QByteArray &data, const QByteArray &salt,
60 int iterations, quint64 dkLen)
61{
62 // https://tools.ietf.org/html/rfc8018#section-5.1
63
64 if (algorithm != QCryptographicHash::Sha1
65 && algorithm != QCryptographicHash::Md5
66 ) {
67 qWarning(msg: "The only supported algorithms for pbkdf1 are SHA-1 and MD5!");
68 return QByteArray();
69 }
70
71 if (salt.size() != 8) {
72 qWarning(msg: "The salt must be 8 bytes long!");
73 return QByteArray();
74 }
75 if (iterations < 1 || dkLen < 1)
76 return QByteArray();
77
78 if (dkLen > quint64(QCryptographicHash::hashLength(method: algorithm))) {
79 qWarning() << "Derived key too long:\n"
80 << algorithm << "was chosen which produces output of length"
81 << QCryptographicHash::hashLength(method: algorithm) << "but" << dkLen
82 << "was requested.";
83 return QByteArray();
84 }
85
86 QCryptographicHash hash(algorithm);
87 hash.addData(data);
88 hash.addData(data: salt);
89 QByteArray key = hash.result();
90
91 for (int i = 1; i < iterations; i++) {
92 hash.reset();
93 hash.addData(data: key);
94 key = hash.result();
95 }
96 return key.left(n: dkLen);
97}
98
99#ifdef USING_OPENSSL30
100// Copied from QCryptographicHashPrivate
101static constexpr const char * methodToName(QCryptographicHash::Algorithm method) noexcept
102{
103 switch (method) {
104#define CASE(Enum, Name) \
105 case QCryptographicHash:: Enum : \
106 return Name \
107 /*end*/
108 CASE(Sha1, "SHA1");
109 CASE(Md4, "MD4");
110 CASE(Md5, "MD5");
111 CASE(Sha224, "SHA224");
112 CASE(Sha256, "SHA256");
113 CASE(Sha384, "SHA384");
114 CASE(Sha512, "SHA512");
115 CASE(RealSha3_224, "SHA3-224");
116 CASE(RealSha3_256, "SHA3-256");
117 CASE(RealSha3_384, "SHA3-384");
118 CASE(RealSha3_512, "SHA3-512");
119 CASE(Keccak_224, "SHA3-224");
120 CASE(Keccak_256, "SHA3-256");
121 CASE(Keccak_384, "SHA3-384");
122 CASE(Keccak_512, "SHA3-512");
123 CASE(Blake2b_512, "BLAKE2B512");
124 CASE(Blake2s_256, "BLAKE2S256");
125#undef CASE
126 default: return nullptr;
127 }
128}
129
130static QByteArray opensslDeriveKeyPbkdf2(QCryptographicHash::Algorithm algorithm,
131 const QByteArray &data, const QByteArray &salt,
132 uint64_t iterations, quint64 dkLen)
133{
134 EVP_KDF *kdf = EVP_KDF_fetch(nullptr, "PBKDF2", nullptr);
135
136 if (!kdf)
137 return QByteArray();
138
139 auto cleanUpKdf = qScopeGuard([kdf] {
140 EVP_KDF_free(kdf);
141 });
142
143 EVP_KDF_CTX *ctx = EVP_KDF_CTX_new(kdf);
144
145 if (!ctx)
146 return QByteArray();
147
148 auto cleanUpCtx = qScopeGuard([ctx] {
149 EVP_KDF_CTX_free(ctx);
150 });
151
152 // Do not enable SP800-132 compliance check, otherwise we will require:
153 // - the iteration count is at least 1000
154 // - the salt length is at least 128 bits
155 // - the derived key length is at least 112 bits
156 // This would be a different behavior from the original implementation.
157 int checkDisabled = 1;
158 QList<OSSL_PARAM> params;
159 params.append(OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST, const_cast<char*>(methodToName(algorithm)), 0));
160 params.append(OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SALT, const_cast<char*>(salt.data()), salt.size()));
161 params.append(OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_PASSWORD, const_cast<char*>(data.data()), data.size()));
162 params.append(OSSL_PARAM_construct_uint64(OSSL_KDF_PARAM_ITER, &iterations));
163 params.append(OSSL_PARAM_construct_int(OSSL_KDF_PARAM_PKCS5, &checkDisabled));
164 params.append(OSSL_PARAM_construct_end());
165
166 if (EVP_KDF_CTX_set_params(ctx, params.data()) <= 0)
167 return QByteArray();
168
169 QByteArray derived(dkLen, '\0');
170
171 if (!EVP_KDF_derive(ctx, reinterpret_cast<unsigned char*>(derived.data()), derived.size(), nullptr))
172 return QByteArray();
173
174 return derived;
175}
176#endif
177
178/*!
179 \since 5.12
180
181 Derive a key using the PBKDF2-algorithm as defined in
182 \l {RFC 8018, section 5.2}.
183
184 This function takes the \a data and \a salt, and then applies HMAC-X, where
185 the X is \a algorithm, repeatedly. It internally concatenates intermediate
186 results to the final output until at least \a dkLen amount of bytes have
187 been computed and it will execute HMAC-X \a iterations times each time a
188 concatenation is required. The total number of times it will execute HMAC-X
189 depends on \a iterations, \a dkLen and \a algorithm and can be calculated
190 as
191 \c{iterations * ceil(dkLen / QCryptographicHash::hashLength(algorithm))}.
192
193 \sa deriveKeyPbkdf1, QMessageAuthenticationCode, QCryptographicHash
194*/
195Q_NETWORK_EXPORT QByteArray deriveKeyPbkdf2(QCryptographicHash::Algorithm algorithm,
196 const QByteArray &data, const QByteArray &salt,
197 int iterations, quint64 dkLen)
198{
199 // The RFC recommends checking that 'dkLen' is not greater than '(2^32 - 1) * hLen'
200 int hashLen = QCryptographicHash::hashLength(method: algorithm);
201 const quint64 maxLen = quint64(std::numeric_limits<quint32>::max() - 1) * hashLen;
202 if (dkLen > maxLen) {
203 qWarning().nospace() << "Derived key too long:\n"
204 << algorithm << " was chosen which produces output of length "
205 << maxLen << " but " << dkLen << " was requested.";
206 return QByteArray();
207 }
208
209 if (iterations < 1 || dkLen < 1)
210 return QByteArray();
211
212#ifdef USING_OPENSSL30
213 if (methodToName(algorithm))
214 return opensslDeriveKeyPbkdf2(algorithm, data, salt, iterations, dkLen);
215#endif
216
217 // https://tools.ietf.org/html/rfc8018#section-5.2
218 QByteArray key;
219 quint32 currentIteration = 1;
220 QMessageAuthenticationCode hmac(algorithm, data);
221 QByteArray index(4, Qt::Uninitialized);
222 while (quint64(key.size()) < dkLen) {
223 hmac.addData(data: salt);
224
225 qToBigEndian(src: currentIteration, dest: index.data());
226 hmac.addData(data: index);
227
228 QByteArray u = hmac.result();
229 hmac.reset();
230 QByteArray tkey = u;
231 for (int iter = 1; iter < iterations; iter++) {
232 hmac.addData(data: u);
233 u = hmac.result();
234 hmac.reset();
235 std::transform(first1: tkey.cbegin(), last1: tkey.cend(), first2: u.cbegin(), result: tkey.begin(),
236 binary_op: std::bit_xor<char>());
237 }
238 key += tkey;
239 currentIteration++;
240 }
241 return key.left(n: dkLen);
242}
243} // namespace QPasswordDigestor
244QT_END_NAMESPACE
245

source code of qtbase/src/network/ssl/qpassworddigestor.cpp