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