diff options
Diffstat (limited to 'src/corelib/tools/qcryptographichash.cpp')
-rw-r--r-- | src/corelib/tools/qcryptographichash.cpp | 1595 |
1 files changed, 1282 insertions, 313 deletions
diff --git a/src/corelib/tools/qcryptographichash.cpp b/src/corelib/tools/qcryptographichash.cpp index d5a6c4e5a0..2e82a394ee 100644 --- a/src/corelib/tools/qcryptographichash.cpp +++ b/src/corelib/tools/qcryptographichash.cpp @@ -1,45 +1,19 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Copyright (C) 2013 Richard J. Moore <rich@kde.org>. -** 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$ -** -****************************************************************************/ +// Copyright (C) 2023 The Qt Company Ltd. +// Copyright (C) 2013 Ruslan Nigmatullin <euroelessar@yandex.ru> +// Copyright (C) 2013 Richard J. Moore <rich@kde.org>. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include <qcryptographichash.h> +#include <qmessageauthenticationcode.h> + #include <qiodevice.h> +#include <qmutex.h> +#include <qvarlengtharray.h> +#include <private/qlocking_p.h> + +#include <array> +#include <climits> +#include <numeric> #include "../../3rdparty/sha1/sha1.cpp" @@ -47,12 +21,17 @@ # error "Are you sure you need the other hashing algorithms besides SHA-1?" #endif +// Header from rfc6234 +#include "../../3rdparty/rfc6234/sha.h" + #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1 +#if !QT_CONFIG(openssl_hash) // qdoc and qmake only need SHA-1 #include "../../3rdparty/md5/md5.h" #include "../../3rdparty/md5/md5.cpp" #include "../../3rdparty/md4/md4.h" #include "../../3rdparty/md4/md4.cpp" +#endif // !QT_CONFIG(openssl_hash) typedef unsigned char BitSequence; typedef unsigned long long DataLength; @@ -79,55 +58,21 @@ typedef HashReturn (SHA3Final)(hashState *state, BitSequence *hashval); #include "../../3rdparty/sha3/KeccakF-1600-opt64.c" -static SHA3Init * const sha3Init = Init; -static SHA3Update * const sha3Update = Update; -static SHA3Final * const sha3Final = Final; +Q_CONSTINIT static SHA3Init * const sha3Init = Init; +Q_CONSTINIT static SHA3Update * const sha3Update = Update; +Q_CONSTINIT static SHA3Final * const sha3Final = Final; #else // 32 bit optimised fallback #include "../../3rdparty/sha3/KeccakF-1600-opt32.c" -static SHA3Init * const sha3Init = Init; -static SHA3Update * const sha3Update = Update; -static SHA3Final * const sha3Final = Final; +Q_CONSTINIT static SHA3Init * const sha3Init = Init; +Q_CONSTINIT static SHA3Update * const sha3Update = Update; +Q_CONSTINIT static SHA3Final * const sha3Final = Final; #endif -/* - These #defines replace the typedefs needed by the RFC6234 code. Normally - the typedefs would come from from stdint.h, but since this header is not - available on all platforms (MSVC 2008, for example), we #define them to the - Qt equivalents. -*/ - -#ifdef uint64_t -#undef uint64_t -#endif - -#define uint64_t QT_PREPEND_NAMESPACE(quint64) - -#ifdef uint32_t -#undef uint32_t -#endif - -#define uint32_t QT_PREPEND_NAMESPACE(quint32) - -#ifdef uint8_t -#undef uint8_t -#endif - -#define uint8_t QT_PREPEND_NAMESPACE(quint8) - -#ifdef int_least16_t -#undef int_least16_t -#endif - -#define int_least16_t QT_PREPEND_NAMESPACE(qint16) - -// Header from rfc6234 with 1 modification: -// sha1.h - commented out '#include <stdint.h>' on line 74 -#include "../../3rdparty/rfc6234/sha.h" - +#if !QT_CONFIG(openssl_hash) /* These 2 functions replace macros of the same name in sha224-256.c and sha384-512.c. Originally, these macros relied on a global static 'addTemp' @@ -149,21 +94,19 @@ static int SHA384_512AddLength(SHA512Context *context, unsigned int length); // sha384-512.c - appended 'M' to the SHA224_256AddLength macro on line 304 #include "../../3rdparty/rfc6234/sha384-512.c" -#undef uint64_t -#undef uint32_t -#undef uint68_t -#undef int_least16_t - static inline int SHA224_256AddLength(SHA256Context *context, unsigned int length) { - QT_PREPEND_NAMESPACE(quint32) addTemp; + uint32_t addTemp; return SHA224_256AddLengthM(context, length); } static inline int SHA384_512AddLength(SHA512Context *context, unsigned int length) { - QT_PREPEND_NAMESPACE(quint64) addTemp; + uint64_t addTemp; return SHA384_512AddLengthM(context, length); } +#endif // !QT_CONFIG(opensslv30) + +#include "qtcore-config_p.h" #if QT_CONFIG(system_libb2) #include <blake2.h> @@ -173,39 +116,279 @@ static inline int SHA384_512AddLength(SHA512Context *context, unsigned int lengt #endif #endif // QT_CRYPTOGRAPHICHASH_ONLY_SHA1 +#if !defined(QT_BOOTSTRAPPED) && QT_CONFIG(openssl_hash) +#define USING_OPENSSL30 +#include <openssl/evp.h> +#include <openssl/provider.h> +#endif + QT_BEGIN_NAMESPACE +template <size_t N> +class QSmallByteArray +{ + std::array<quint8, N> m_data; + static_assert(N <= std::numeric_limits<std::uint8_t>::max()); + quint8 m_size = 0; +public: + QSmallByteArray() = default; + // all compiler-generated SMFs are ok! + template <std::size_t M, std::enable_if_t<M < N, bool> = true> // M == N is for copy ctor! + constexpr QSmallByteArray(const QSmallByteArray<M> &other) noexcept + { + assign(other); + } + template <std::size_t M, std::enable_if_t<M < N, bool> = true> // M == N is for copy-assignment op! + constexpr QSmallByteArray &operator=(const QSmallByteArray<M> &other) noexcept + { + assign(other); + return *this; + } + + template <typename Container> // ### underconstrained + constexpr void assign(const Container &c) + { + const size_t otherSize = size_t(std::size(c)); + Q_ASSERT(otherSize < N); + memcpy(data(), std::data(c), otherSize); + m_size = quint8(otherSize); + } + + constexpr quint8 *data() noexcept { return m_data.data(); } + constexpr const quint8 *data() const noexcept { return m_data.data(); } + constexpr qsizetype size() const noexcept { return qsizetype{m_size}; } + constexpr quint8 &operator[](qsizetype n) + { + Q_ASSERT(n < size()); + return data()[n]; + } + constexpr const quint8 &operator[](qsizetype n) const + { + Q_ASSERT(n < size()); + return data()[n]; + } + constexpr bool isEmpty() const noexcept { return size() == 0; } + constexpr void clear() noexcept { m_size = 0; } + constexpr void resizeForOverwrite(qsizetype s) + { + Q_ASSERT(s >= 0); + Q_ASSERT(size_t(s) <= N); + m_size = std::uint8_t(s); + } + constexpr void resize(qsizetype s, quint8 v) + { + const auto oldSize = size(); + resizeForOverwrite(s); + if (s > oldSize) + memset(data() + oldSize, v, size() - oldSize); + } + constexpr QByteArrayView toByteArrayView() const noexcept + { return *this; } + + constexpr auto begin() noexcept { return data(); } + constexpr auto begin() const noexcept { return data(); } + constexpr auto cbegin() const noexcept { return begin(); } + constexpr auto end() noexcept { return data() + size(); } + constexpr auto end() const noexcept { return data() + size(); } + constexpr auto cend() const noexcept { return end(); } +}; + +static constexpr int hashLengthInternal(QCryptographicHash::Algorithm method) noexcept +{ + switch (method) { +#define CASE(Enum, Size) \ + case QCryptographicHash:: Enum : \ + return Size \ + /*end*/ + CASE(Sha1, 20); +#ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1 + CASE(Md4, 16); + CASE(Md5, 16); + CASE(Sha224, SHA224HashSize); + CASE(Sha256, SHA256HashSize); + CASE(Sha384, SHA384HashSize); + CASE(Sha512, SHA512HashSize); + CASE(Blake2s_128, 128 / 8); + case QCryptographicHash::Blake2b_160: + case QCryptographicHash::Blake2s_160: + return 160 / 8; + case QCryptographicHash::RealSha3_224: + case QCryptographicHash::Keccak_224: + case QCryptographicHash::Blake2s_224: + return 224 / 8; + case QCryptographicHash::RealSha3_256: + case QCryptographicHash::Keccak_256: + case QCryptographicHash::Blake2b_256: + case QCryptographicHash::Blake2s_256: + return 256 / 8; + case QCryptographicHash::RealSha3_384: + case QCryptographicHash::Keccak_384: + case QCryptographicHash::Blake2b_384: + return 384 / 8; + case QCryptographicHash::RealSha3_512: + case QCryptographicHash::Keccak_512: + case QCryptographicHash::Blake2b_512: + return 512 / 8; +#endif +#undef CASE + case QCryptographicHash::NumAlgorithms: ; + // fall through + // Q_UNREACHABLE() would be BiC here, as hashLength(~~invalid~~) worked in 6.4 + } + return 0; +} + +static constexpr int maxHashLength() +{ + int result = 0; + using A = QCryptographicHash::Algorithm; + for (int i = 0; i < A::NumAlgorithms; ++i) + result = std::max(result, hashLengthInternal(A(i))); + return result; +} + +using HashResult = QSmallByteArray<maxHashLength()>; + +#ifdef USING_OPENSSL30 +static constexpr const char * methodToName(QCryptographicHash::Algorithm method) noexcept +{ + switch (method) { +#define CASE(Enum, Name) \ + case QCryptographicHash:: Enum : \ + return Name \ + /*end*/ + CASE(Sha1, "SHA1"); + CASE(Md4, "MD4"); + CASE(Md5, "MD5"); + CASE(Sha224, "SHA224"); + CASE(Sha256, "SHA256"); + CASE(Sha384, "SHA384"); + CASE(Sha512, "SHA512"); + CASE(RealSha3_224, "SHA3-224"); + CASE(RealSha3_256, "SHA3-256"); + CASE(RealSha3_384, "SHA3-384"); + CASE(RealSha3_512, "SHA3-512"); + CASE(Blake2b_512, "BLAKE2B512"); + CASE(Blake2s_256, "BLAKE2S256"); +#undef CASE + default: return nullptr; + } +} + +/* + Checks whether given method is not provided by OpenSSL and whether we will + have a fallback to non-OpenSSL implementation. +*/ +static constexpr bool useNonOpenSSLFallback(QCryptographicHash::Algorithm method) noexcept +{ + if (method == QCryptographicHash::Keccak_224 || method == QCryptographicHash::Keccak_256 || + method == QCryptographicHash::Keccak_384 || method == QCryptographicHash::Keccak_512 || + method == QCryptographicHash::Blake2b_160 || method == QCryptographicHash::Blake2b_256 || + method == QCryptographicHash::Blake2b_384 || method == QCryptographicHash::Blake2s_128 || + method == QCryptographicHash::Blake2s_160 || method == QCryptographicHash::Blake2s_224) + return true; + + return false; +} +#endif // USING_OPENSSL30 + class QCryptographicHashPrivate { public: - QCryptographicHash::Algorithm method; - union { + explicit QCryptographicHashPrivate(QCryptographicHash::Algorithm method) noexcept + : state(method), method(method) + { + } + ~QCryptographicHashPrivate() + { + state.destroy(method); + } + + void reset() noexcept; + void addData(QByteArrayView bytes) noexcept; + bool addData(QIODevice *dev); + void finalize() noexcept; + // when not called from the static hash() function, this function needs to be + // called with finalizeMutex held (finalize() will do that): + void finalizeUnchecked() noexcept; + // END functions that need to be called with finalizeMutex held + QByteArrayView resultView() const noexcept { return result.toByteArrayView(); } + static bool supportsAlgorithm(QCryptographicHash::Algorithm method); + +#ifdef USING_OPENSSL30 + struct EVP_MD_CTX_deleter { + void operator()(EVP_MD_CTX *ctx) const noexcept { + EVP_MD_CTX_free(ctx); + } + }; + struct EVP_MD_deleter { + void operator()(EVP_MD *md) const noexcept { + EVP_MD_free(md); + } + }; + struct OSSL_PROVIDER_deleter { + void operator()(OSSL_PROVIDER *provider) const noexcept { + OSSL_PROVIDER_unload(provider); + } + }; + + using EVP_MD_CTX_ptr = std::unique_ptr<EVP_MD_CTX, EVP_MD_CTX_deleter>; + using EVP_MD_ptr = std::unique_ptr<EVP_MD, EVP_MD_deleter>; + using OSSL_PROVIDER_ptr = std::unique_ptr<OSSL_PROVIDER, OSSL_PROVIDER_deleter>; + struct EVP { + EVP_MD_ptr algorithm; + EVP_MD_CTX_ptr context; + OSSL_PROVIDER_ptr defaultProvider; + OSSL_PROVIDER_ptr legacyProvider; + bool initializationFailed; + + explicit EVP(QCryptographicHash::Algorithm method); + void reset() noexcept; + void finalizeUnchecked(HashResult &result) noexcept; + }; +#endif + + union State { + explicit State(QCryptographicHash::Algorithm method); + void destroy(QCryptographicHash::Algorithm method); +#ifdef USING_OPENSSL30 + ~State() {} +#endif + + void reset(QCryptographicHash::Algorithm method) noexcept; + void addData(QCryptographicHash::Algorithm method, QByteArrayView data) noexcept; + void finalizeUnchecked(QCryptographicHash::Algorithm method, HashResult &result) noexcept; + Sha1State sha1Context; #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1 +#ifdef USING_OPENSSL30 + EVP evp; +#else MD5Context md5Context; md4_context md4Context; SHA224Context sha224Context; SHA256Context sha256Context; SHA384Context sha384Context; SHA512Context sha512Context; +#endif SHA3Context sha3Context; + + enum class Sha3Variant { Sha3, Keccak }; + void sha3Finish(HashResult &result, int bitCount, Sha3Variant sha3Variant); blake2b_state blake2bContext; blake2s_state blake2sContext; #endif - }; -#ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1 - enum class Sha3Variant - { - Sha3, - Keccak - }; - void sha3Finish(int bitCount, Sha3Variant sha3Variant); -#endif - QByteArray result; + } state; + // protects result in finalize() + QBasicMutex finalizeMutex; + HashResult result; + + const QCryptographicHash::Algorithm method; }; #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1 -void QCryptographicHashPrivate::sha3Finish(int bitCount, Sha3Variant sha3Variant) +void QCryptographicHashPrivate::State::sha3Finish(HashResult &result, int bitCount, + Sha3Variant sha3Variant) { /* FIPS 202 ยง6.1 defines SHA-3 in terms of calculating the Keccak function @@ -229,7 +412,7 @@ void QCryptographicHashPrivate::sha3Finish(int bitCount, Sha3Variant sha3Variant */ static const unsigned char sha3FinalSuffix = 0x80; - result.resize(bitCount / 8); + result.resizeForOverwrite(bitCount / 8); SHA3Context copy = sha3Context; @@ -241,7 +424,7 @@ void QCryptographicHashPrivate::sha3Finish(int bitCount, Sha3Variant sha3Variant break; } - sha3Final(©, reinterpret_cast<BitSequence *>(result.data())); + sha3Final(©, result.data()); } #endif @@ -298,19 +481,30 @@ void QCryptographicHashPrivate::sha3Finish(int bitCount, Sha3Variant sha3Variant \omitvalue RealSha3_256 \omitvalue RealSha3_384 \omitvalue RealSha3_512 + \omitvalue NumAlgorithms */ /*! Constructs an object that can be used to create a cryptographic hash from data using \a method. */ QCryptographicHash::QCryptographicHash(Algorithm method) - : d(new QCryptographicHashPrivate) + : d(new QCryptographicHashPrivate{method}) { - d->method = method; - reset(); } /*! + \fn QCryptographicHash::QCryptographicHash(QCryptographicHash &&other) + + Move-constructs a new QCryptographicHash from \a other. + + \note The moved-from object \a other is placed in a + partially-formed state, in which the only valid operations are + destruction and assignment of a new value. + + \since 6.5 +*/ + +/*! Destroys the object. */ QCryptographicHash::~QCryptographicHash() @@ -319,14 +513,129 @@ QCryptographicHash::~QCryptographicHash() } /*! + \fn QCryptographicHash &QCryptographicHash::operator=(QCryptographicHash &&other) + + Move-assigns \a other to this QCryptographicHash instance. + + \note The moved-from object \a other is placed in a + partially-formed state, in which the only valid operations are + destruction and assignment of a new value. + + \since 6.5 +*/ + +/*! + \fn void QCryptographicHash::swap(QCryptographicHash &other) + + Swaps cryptographic hash \a other with this cryptographic hash. This + operation is very fast and never fails. + + \since 6.5 +*/ + +/*! Resets the object. */ -void QCryptographicHash::reset() +void QCryptographicHash::reset() noexcept { - switch (d->method) { - case Sha1: - new (&d->sha1Context) Sha1State; - sha1InitState(&d->sha1Context); + d->reset(); +} + +/*! + Returns the algorithm used to generate the cryptographic hash. + + \since 6.5 +*/ +QCryptographicHash::Algorithm QCryptographicHash::algorithm() const noexcept +{ + return d->method; +} + +#ifdef USING_OPENSSL30 + +QCryptographicHashPrivate::State::State(QCryptographicHash::Algorithm method) +{ + if (method == QCryptographicHash::Keccak_224 || + method == QCryptographicHash::Keccak_256 || + method == QCryptographicHash::Keccak_384 || + method == QCryptographicHash::Keccak_512) { + new (&sha3Context) SHA3Context; + reset(method); + } else if (method == QCryptographicHash::Blake2b_160 || + method == QCryptographicHash::Blake2b_256 || + method == QCryptographicHash::Blake2b_384) { + new (&blake2bContext) blake2b_state; + reset(method); + } else if (method == QCryptographicHash::Blake2s_128 || + method == QCryptographicHash::Blake2s_160 || + method == QCryptographicHash::Blake2s_224) { + new (&blake2sContext) blake2s_state; + reset(method); + } else { + new (&evp) EVP(method); + } +} + +void QCryptographicHashPrivate::State::destroy(QCryptographicHash::Algorithm method) +{ + if (method != QCryptographicHash::Keccak_224 && + method != QCryptographicHash::Keccak_256 && + method != QCryptographicHash::Keccak_384 && + method != QCryptographicHash::Keccak_512 && + method != QCryptographicHash::Blake2b_160 && + method != QCryptographicHash::Blake2b_256 && + method != QCryptographicHash::Blake2b_384 && + method != QCryptographicHash::Blake2s_128 && + method != QCryptographicHash::Blake2s_160 && + method != QCryptographicHash::Blake2s_224) { + evp.~EVP(); + } +} + +QCryptographicHashPrivate::EVP::EVP(QCryptographicHash::Algorithm method) + : initializationFailed{true} +{ + if (method == QCryptographicHash::Md4) { + /* + * We need to load the legacy provider in order to have the MD4 + * algorithm available. + */ + legacyProvider = OSSL_PROVIDER_ptr(OSSL_PROVIDER_load(nullptr, "legacy")); + + if (!legacyProvider) + return; + } + + defaultProvider = OSSL_PROVIDER_ptr(OSSL_PROVIDER_load(nullptr, "default")); + if (!defaultProvider) + return; + + context = EVP_MD_CTX_ptr(EVP_MD_CTX_new()); + + if (!context) { + return; + } + + /* + * Using the "-fips" option will disable the global "fips=yes" for + * this one lookup and the algorithm can be fetched from any provider + * that implements the algorithm (including the FIPS provider). + */ + algorithm = EVP_MD_ptr(EVP_MD_fetch(nullptr, methodToName(method), "-fips")); + if (!algorithm) { + return; + } + + initializationFailed = !EVP_DigestInit_ex(context.get(), algorithm.get(), nullptr); +} + +#else // USING_OPENSSL30 + +QCryptographicHashPrivate::State::State(QCryptographicHash::Algorithm method) +{ + switch (method) { + case QCryptographicHash::Sha1: + new (&sha1Context) Sha1State; break; #ifdef QT_CRYPTOGRAPHICHASH_ONLY_SHA1 default: @@ -334,82 +643,241 @@ void QCryptographicHash::reset() Q_UNREACHABLE(); break; #else - case Md4: - new (&d->md4Context) md4_context; - md4_init(&d->md4Context); - break; - case Md5: - new (&d->md5Context) MD5Context; - MD5Init(&d->md5Context); - break; - case Sha224: - new (&d->sha224Context) SHA224Context; - SHA224Reset(&d->sha224Context); - break; - case Sha256: - new (&d->sha256Context) SHA256Context; - SHA256Reset(&d->sha256Context); - break; - case Sha384: - new (&d->sha384Context) SHA384Context; - SHA384Reset(&d->sha384Context); - break; - case Sha512: - new (&d->sha512Context) SHA512Context; - SHA512Reset(&d->sha512Context); - break; - case RealSha3_224: - case Keccak_224: - case RealSha3_256: - case Keccak_256: - case RealSha3_384: - case Keccak_384: - case RealSha3_512: - case Keccak_512: - new (&d->sha3Context) SHA3Context; - sha3Init(&d->sha3Context, hashLength(d->method) * 8); - break; - case Blake2b_160: - case Blake2b_256: - case Blake2b_384: - case Blake2b_512: - new (&d->blake2bContext) blake2b_state; - blake2b_init(&d->blake2bContext, hashLength(d->method)); - break; - case Blake2s_128: - case Blake2s_160: - case Blake2s_224: - case Blake2s_256: - new (&d->blake2sContext) blake2s_state; - blake2s_init(&d->blake2sContext, hashLength(d->method)); + case QCryptographicHash::Md4: + new (&md4Context) md4_context; + break; + case QCryptographicHash::Md5: + new (&md5Context) MD5Context; + break; + case QCryptographicHash::Sha224: + new (&sha224Context) SHA224Context; + break; + case QCryptographicHash::Sha256: + new (&sha256Context) SHA256Context; + break; + case QCryptographicHash::Sha384: + new (&sha384Context) SHA384Context; + break; + case QCryptographicHash::Sha512: + new (&sha512Context) SHA512Context; + break; + case QCryptographicHash::RealSha3_224: + case QCryptographicHash::Keccak_224: + case QCryptographicHash::RealSha3_256: + case QCryptographicHash::Keccak_256: + case QCryptographicHash::RealSha3_384: + case QCryptographicHash::Keccak_384: + case QCryptographicHash::RealSha3_512: + case QCryptographicHash::Keccak_512: + new (&sha3Context) SHA3Context; + break; + case QCryptographicHash::Blake2b_160: + case QCryptographicHash::Blake2b_256: + case QCryptographicHash::Blake2b_384: + case QCryptographicHash::Blake2b_512: + new (&blake2bContext) blake2b_state; + break; + case QCryptographicHash::Blake2s_128: + case QCryptographicHash::Blake2s_160: + case QCryptographicHash::Blake2s_224: + case QCryptographicHash::Blake2s_256: + new (&blake2sContext) blake2s_state; break; #endif + case QCryptographicHash::NumAlgorithms: + Q_UNREACHABLE(); + } + reset(method); +} + +void QCryptographicHashPrivate::State::destroy(QCryptographicHash::Algorithm) +{ + static_assert(std::is_trivially_destructible_v<State>); // so nothing to do here +} +#endif // !USING_OPENSSL30 + +void QCryptographicHashPrivate::reset() noexcept +{ + result.clear(); + state.reset(method); +} + +#ifdef USING_OPENSSL30 + +void QCryptographicHashPrivate::State::reset(QCryptographicHash::Algorithm method) noexcept +{ + if (method == QCryptographicHash::Keccak_224 || + method == QCryptographicHash::Keccak_256 || + method == QCryptographicHash::Keccak_384 || + method == QCryptographicHash::Keccak_512) { + sha3Init(&sha3Context, hashLengthInternal(method) * 8); + } else if (method == QCryptographicHash::Blake2b_160 || + method == QCryptographicHash::Blake2b_256 || + method == QCryptographicHash::Blake2b_384) { + blake2b_init(&blake2bContext, hashLengthInternal(method)); + } else if (method == QCryptographicHash::Blake2s_128 || + method == QCryptographicHash::Blake2s_160 || + method == QCryptographicHash::Blake2s_224) { + blake2s_init(&blake2sContext, hashLengthInternal(method)); + } else { + evp.reset(); + } +} + +void QCryptographicHashPrivate::EVP::reset() noexcept +{ + if (!initializationFailed) { + Q_ASSERT(context); + Q_ASSERT(algorithm); + // everything already set up - just reset the context + EVP_MD_CTX_reset(context.get()); + initializationFailed = !EVP_DigestInit_ex(context.get(), algorithm.get(), nullptr); } - d->result.clear(); + // if initializationFailed first time around, it will not succeed this time, either } +#else // USING_OPENSSL30 + +void QCryptographicHashPrivate::State::reset(QCryptographicHash::Algorithm method) noexcept +{ + switch (method) { + case QCryptographicHash::Sha1: + sha1InitState(&sha1Context); + break; +#ifdef QT_CRYPTOGRAPHICHASH_ONLY_SHA1 + default: + Q_ASSERT_X(false, "QCryptographicHash", "Method not compiled in"); + Q_UNREACHABLE(); + break; +#else + case QCryptographicHash::Md4: + md4_init(&md4Context); + break; + case QCryptographicHash::Md5: + MD5Init(&md5Context); + break; + case QCryptographicHash::Sha224: + SHA224Reset(&sha224Context); + break; + case QCryptographicHash::Sha256: + SHA256Reset(&sha256Context); + break; + case QCryptographicHash::Sha384: + SHA384Reset(&sha384Context); + break; + case QCryptographicHash::Sha512: + SHA512Reset(&sha512Context); + break; + case QCryptographicHash::RealSha3_224: + case QCryptographicHash::Keccak_224: + case QCryptographicHash::RealSha3_256: + case QCryptographicHash::Keccak_256: + case QCryptographicHash::RealSha3_384: + case QCryptographicHash::Keccak_384: + case QCryptographicHash::RealSha3_512: + case QCryptographicHash::Keccak_512: + sha3Init(&sha3Context, hashLengthInternal(method) * 8); + break; + case QCryptographicHash::Blake2b_160: + case QCryptographicHash::Blake2b_256: + case QCryptographicHash::Blake2b_384: + case QCryptographicHash::Blake2b_512: + blake2b_init(&blake2bContext, hashLengthInternal(method)); + break; + case QCryptographicHash::Blake2s_128: + case QCryptographicHash::Blake2s_160: + case QCryptographicHash::Blake2s_224: + case QCryptographicHash::Blake2s_256: + blake2s_init(&blake2sContext, hashLengthInternal(method)); + break; +#endif + case QCryptographicHash::NumAlgorithms: + Q_UNREACHABLE(); + } +} + +#endif // USING_OPENSSL30 + +#if QT_DEPRECATED_SINCE(6, 4) /*! Adds the first \a length chars of \a data to the cryptographic hash. + + \obsolete + Use the QByteArrayView overload instead. */ void QCryptographicHash::addData(const char *data, qsizetype length) { Q_ASSERT(length >= 0); + addData(QByteArrayView{data, length}); +} +#endif + +/*! + Adds the characters in \a bytes to the cryptographic hash. + + \note In Qt versions prior to 6.3, this function took QByteArray, + not QByteArrayView. +*/ +void QCryptographicHash::addData(QByteArrayView bytes) noexcept +{ + d->addData(bytes); +} + +void QCryptographicHashPrivate::addData(QByteArrayView bytes) noexcept +{ + state.addData(method, bytes); + result.clear(); +} + +#ifdef USING_OPENSSL30 + +void QCryptographicHashPrivate::State::addData(QCryptographicHash::Algorithm method, + QByteArrayView bytes) noexcept +{ + const char *data = bytes.data(); + auto length = bytes.size(); + // all functions take size_t length, so we don't need to loop around them: + { + if (method == QCryptographicHash::Keccak_224 || + method == QCryptographicHash::Keccak_256 || + method == QCryptographicHash::Keccak_384 || + method == QCryptographicHash::Keccak_512) { + sha3Update(&sha3Context, reinterpret_cast<const BitSequence *>(data), uint64_t(length) * 8); + } else if (method == QCryptographicHash::Blake2b_160 || + method == QCryptographicHash::Blake2b_256 || + method == QCryptographicHash::Blake2b_384) { + blake2b_update(&blake2bContext, reinterpret_cast<const uint8_t *>(data), length); + } else if (method == QCryptographicHash::Blake2s_128 || + method == QCryptographicHash::Blake2s_160 || + method == QCryptographicHash::Blake2s_224) { + blake2s_update(&blake2sContext, reinterpret_cast<const uint8_t *>(data), length); + } else if (!evp.initializationFailed) { + EVP_DigestUpdate(evp.context.get(), (const unsigned char *)data, length); + } + } +} + +#else // USING_OPENSSL30 + +void QCryptographicHashPrivate::State::addData(QCryptographicHash::Algorithm method, + QByteArrayView bytes) noexcept +{ + const char *data = bytes.data(); + auto length = bytes.size(); #if QT_POINTER_SIZE == 8 // feed the data UINT_MAX bytes at a time, as some of the methods below // take a uint (of course, feeding more than 4G of data into the hashing // functions will be pretty slow anyway) - qsizetype remaining = length; - while (remaining) { + for (auto remaining = length; remaining; remaining -= length, data += length) { length = qMin(qsizetype(std::numeric_limits<uint>::max()), remaining); - remaining -= length; #else { #endif - switch (d->method) { - case Sha1: - sha1Update(&d->sha1Context, (const unsigned char *)data, length); + switch (method) { + case QCryptographicHash::Sha1: + sha1Update(&sha1Context, (const unsigned char *)data, length); break; #ifdef QT_CRYPTOGRAPHICHASH_ONLY_SHA1 default: @@ -417,72 +885,65 @@ void QCryptographicHash::addData(const char *data, qsizetype length) Q_UNREACHABLE(); break; #else - case Md4: - md4_update(&d->md4Context, (const unsigned char *)data, length); - break; - case Md5: - MD5Update(&d->md5Context, (const unsigned char *)data, length); - break; - case Sha224: - SHA224Input(&d->sha224Context, reinterpret_cast<const unsigned char *>(data), length); + case QCryptographicHash::Md4: + md4_update(&md4Context, (const unsigned char *)data, length); break; - case Sha256: - SHA256Input(&d->sha256Context, reinterpret_cast<const unsigned char *>(data), length); + case QCryptographicHash::Md5: + MD5Update(&md5Context, (const unsigned char *)data, length); break; - case Sha384: - SHA384Input(&d->sha384Context, reinterpret_cast<const unsigned char *>(data), length); + case QCryptographicHash::Sha224: + SHA224Input(&sha224Context, reinterpret_cast<const unsigned char *>(data), length); break; - case Sha512: - SHA512Input(&d->sha512Context, reinterpret_cast<const unsigned char *>(data), length); + case QCryptographicHash::Sha256: + SHA256Input(&sha256Context, reinterpret_cast<const unsigned char *>(data), length); break; - case RealSha3_224: - case Keccak_224: - sha3Update(&d->sha3Context, reinterpret_cast<const BitSequence *>(data), quint64(length) * 8); + case QCryptographicHash::Sha384: + SHA384Input(&sha384Context, reinterpret_cast<const unsigned char *>(data), length); break; - case RealSha3_256: - case Keccak_256: - sha3Update(&d->sha3Context, reinterpret_cast<const BitSequence *>(data), quint64(length) * 8); + case QCryptographicHash::Sha512: + SHA512Input(&sha512Context, reinterpret_cast<const unsigned char *>(data), length); break; - case RealSha3_384: - case Keccak_384: - sha3Update(&d->sha3Context, reinterpret_cast<const BitSequence *>(data), quint64(length) * 8); + case QCryptographicHash::RealSha3_224: + case QCryptographicHash::Keccak_224: + case QCryptographicHash::RealSha3_256: + case QCryptographicHash::Keccak_256: + case QCryptographicHash::RealSha3_384: + case QCryptographicHash::Keccak_384: + case QCryptographicHash::RealSha3_512: + case QCryptographicHash::Keccak_512: + sha3Update(&sha3Context, reinterpret_cast<const BitSequence *>(data), uint64_t(length) * 8); break; - case RealSha3_512: - case Keccak_512: - sha3Update(&d->sha3Context, reinterpret_cast<const BitSequence *>(data), quint64(length) * 8); + case QCryptographicHash::Blake2b_160: + case QCryptographicHash::Blake2b_256: + case QCryptographicHash::Blake2b_384: + case QCryptographicHash::Blake2b_512: + blake2b_update(&blake2bContext, reinterpret_cast<const uint8_t *>(data), length); break; - case Blake2b_160: - case Blake2b_256: - case Blake2b_384: - case Blake2b_512: - blake2b_update(&d->blake2bContext, reinterpret_cast<const uint8_t *>(data), length); - break; - case Blake2s_128: - case Blake2s_160: - case Blake2s_224: - case Blake2s_256: - blake2s_update(&d->blake2sContext, reinterpret_cast<const uint8_t *>(data), length); + case QCryptographicHash::Blake2s_128: + case QCryptographicHash::Blake2s_160: + case QCryptographicHash::Blake2s_224: + case QCryptographicHash::Blake2s_256: + blake2s_update(&blake2sContext, reinterpret_cast<const uint8_t *>(data), length); break; #endif + case QCryptographicHash::NumAlgorithms: + Q_UNREACHABLE(); } } - d->result.clear(); -} - -/*! - \overload addData() -*/ -void QCryptographicHash::addData(const QByteArray &data) -{ - addData(data.constData(), data.length()); } +#endif // !USING_OPENSSL30 /*! Reads the data from the open QIODevice \a device until it ends and hashes it. Returns \c true if reading was successful. \since 5.0 */ -bool QCryptographicHash::addData(QIODevice* device) +bool QCryptographicHash::addData(QIODevice *device) +{ + return d->addData(device); +} + +bool QCryptographicHashPrivate::addData(QIODevice *device) { if (!device->isReadable()) return false; @@ -491,10 +952,10 @@ bool QCryptographicHash::addData(QIODevice* device) return false; char buffer[1024]; - int length; + qint64 length; - while ((length = device->read(buffer,sizeof(buffer))) > 0) - addData(buffer,length); + while ((length = device->read(buffer, sizeof(buffer))) > 0) + addData({buffer, qsizetype(length)}); // length always <= 1024 return device->atEnd(); } @@ -503,19 +964,105 @@ bool QCryptographicHash::addData(QIODevice* device) /*! Returns the final hash value. - \sa QByteArray::toHex() + \sa resultView(), QByteArray::toHex() */ QByteArray QCryptographicHash::result() const { - if (!d->result.isEmpty()) - return d->result; + return resultView().toByteArray(); +} + +/*! + \since 6.3 + + Returns the final hash value. + + Note that the returned view remains valid only as long as the QCryptographicHash object is + not modified by other means. + + \sa result() +*/ +QByteArrayView QCryptographicHash::resultView() const noexcept +{ + // resultView() is a const function, so concurrent calls are allowed; protect: + d->finalize(); + // resultView() remains(!) valid even after we dropped the mutex in finalize() + return d->resultView(); +} + +/*! + \internal + + Calls finalizeUnchecked(), if needed, under finalizeMutex protection. +*/ +void QCryptographicHashPrivate::finalize() noexcept +{ + const auto lock = qt_scoped_lock(finalizeMutex); + // check that no other thread already finalizeUnchecked()'ed before us: + if (!result.isEmpty()) + return; + finalizeUnchecked(); +} + +/*! + \internal + + Must be called with finalizeMutex held (except from static hash() function, + where no sharing can take place). +*/ +void QCryptographicHashPrivate::finalizeUnchecked() noexcept +{ + state.finalizeUnchecked(method, result); +} + +#ifdef USING_OPENSSL30 +void QCryptographicHashPrivate::State::finalizeUnchecked(QCryptographicHash::Algorithm method, + HashResult &result) noexcept +{ + if (method == QCryptographicHash::Keccak_224 || + method == QCryptographicHash::Keccak_256 || + method == QCryptographicHash::Keccak_384 || + method == QCryptographicHash::Keccak_512) { + sha3Finish(result, 8 * hashLengthInternal(method), Sha3Variant::Keccak); + } else if (method == QCryptographicHash::Blake2b_160 || + method == QCryptographicHash::Blake2b_256 || + method == QCryptographicHash::Blake2b_384) { + const auto length = hashLengthInternal(method); + blake2b_state copy = blake2bContext; + result.resizeForOverwrite(length); + blake2b_final(©, result.data(), length); + } else if (method == QCryptographicHash::Blake2s_128 || + method == QCryptographicHash::Blake2s_160 || + method == QCryptographicHash::Blake2s_224) { + const auto length = hashLengthInternal(method); + blake2s_state copy = blake2sContext; + result.resizeForOverwrite(length); + blake2s_final(©, result.data(), length); + } else { + evp.finalizeUnchecked(result); + } +} + +void QCryptographicHashPrivate::EVP::finalizeUnchecked(HashResult &result) noexcept +{ + if (!initializationFailed) { + EVP_MD_CTX_ptr copy = EVP_MD_CTX_ptr(EVP_MD_CTX_new()); + EVP_MD_CTX_copy_ex(copy.get(), context.get()); + result.resizeForOverwrite(EVP_MD_get_size(algorithm.get())); + EVP_DigestFinal_ex(copy.get(), result.data(), nullptr); + } +} + +#else // USING_OPENSSL30 - switch (d->method) { - case Sha1: { - Sha1State copy = d->sha1Context; - d->result.resize(20); +void QCryptographicHashPrivate::State::finalizeUnchecked(QCryptographicHash::Algorithm method, + HashResult &result) noexcept +{ + switch (method) { + case QCryptographicHash::Sha1: { + Sha1State copy = sha1Context; + result.resizeForOverwrite(20); sha1FinalizeState(©); - sha1ToHash(©, (unsigned char *)d->result.data()); + sha1ToHash(©, result.data()); break; } #ifdef QT_CRYPTOGRAPHICHASH_ONLY_SHA1 @@ -524,107 +1071,95 @@ QByteArray QCryptographicHash::result() const Q_UNREACHABLE(); break; #else - case Md4: { - md4_context copy = d->md4Context; - d->result.resize(MD4_RESULTLEN); - md4_final(©, (unsigned char *)d->result.data()); + case QCryptographicHash::Md4: { + md4_context copy = md4Context; + result.resizeForOverwrite(MD4_RESULTLEN); + md4_final(©, result.data()); break; } - case Md5: { - MD5Context copy = d->md5Context; - d->result.resize(16); - MD5Final(©, (unsigned char *)d->result.data()); + case QCryptographicHash::Md5: { + MD5Context copy = md5Context; + result.resizeForOverwrite(16); + MD5Final(©, result.data()); break; } - case Sha224: { - SHA224Context copy = d->sha224Context; - d->result.resize(SHA224HashSize); - SHA224Result(©, reinterpret_cast<unsigned char *>(d->result.data())); + case QCryptographicHash::Sha224: { + SHA224Context copy = sha224Context; + result.resizeForOverwrite(SHA224HashSize); + SHA224Result(©, result.data()); break; } - case Sha256:{ - SHA256Context copy = d->sha256Context; - d->result.resize(SHA256HashSize); - SHA256Result(©, reinterpret_cast<unsigned char *>(d->result.data())); + case QCryptographicHash::Sha256: { + SHA256Context copy = sha256Context; + result.resizeForOverwrite(SHA256HashSize); + SHA256Result(©, result.data()); break; } - case Sha384:{ - SHA384Context copy = d->sha384Context; - d->result.resize(SHA384HashSize); - SHA384Result(©, reinterpret_cast<unsigned char *>(d->result.data())); + case QCryptographicHash::Sha384: { + SHA384Context copy = sha384Context; + result.resizeForOverwrite(SHA384HashSize); + SHA384Result(©, result.data()); break; } - case Sha512:{ - SHA512Context copy = d->sha512Context; - d->result.resize(SHA512HashSize); - SHA512Result(©, reinterpret_cast<unsigned char *>(d->result.data())); + case QCryptographicHash::Sha512: { + SHA512Context copy = sha512Context; + result.resizeForOverwrite(SHA512HashSize); + SHA512Result(©, result.data()); break; } - case RealSha3_224: { - d->sha3Finish(224, QCryptographicHashPrivate::Sha3Variant::Sha3); - break; - } - case RealSha3_256: { - d->sha3Finish(256, QCryptographicHashPrivate::Sha3Variant::Sha3); - break; - } - case RealSha3_384: { - d->sha3Finish(384, QCryptographicHashPrivate::Sha3Variant::Sha3); - break; - } - case RealSha3_512: { - d->sha3Finish(512, QCryptographicHashPrivate::Sha3Variant::Sha3); - break; - } - case Keccak_224: { - d->sha3Finish(224, QCryptographicHashPrivate::Sha3Variant::Keccak); - break; - } - case Keccak_256: { - d->sha3Finish(256, QCryptographicHashPrivate::Sha3Variant::Keccak); - break; - } - case Keccak_384: { - d->sha3Finish(384, QCryptographicHashPrivate::Sha3Variant::Keccak); + case QCryptographicHash::RealSha3_224: + case QCryptographicHash::RealSha3_256: + case QCryptographicHash::RealSha3_384: + case QCryptographicHash::RealSha3_512: { + sha3Finish(result, 8 * hashLengthInternal(method), Sha3Variant::Sha3); break; } - case Keccak_512: { - d->sha3Finish(512, QCryptographicHashPrivate::Sha3Variant::Keccak); + case QCryptographicHash::Keccak_224: + case QCryptographicHash::Keccak_256: + case QCryptographicHash::Keccak_384: + case QCryptographicHash::Keccak_512: { + sha3Finish(result, 8 * hashLengthInternal(method), Sha3Variant::Keccak); break; } - case Blake2b_160: - case Blake2b_256: - case Blake2b_384: - case Blake2b_512: { - const auto length = hashLength(d->method); - blake2b_state copy = d->blake2bContext; - d->result.resize(length); - blake2b_final(©, reinterpret_cast<uint8_t *>(d->result.data()), length); + case QCryptographicHash::Blake2b_160: + case QCryptographicHash::Blake2b_256: + case QCryptographicHash::Blake2b_384: + case QCryptographicHash::Blake2b_512: { + const auto length = hashLengthInternal(method); + blake2b_state copy = blake2bContext; + result.resizeForOverwrite(length); + blake2b_final(©, result.data(), length); break; } - case Blake2s_128: - case Blake2s_160: - case Blake2s_224: - case Blake2s_256: { - const auto length = hashLength(d->method); - blake2s_state copy = d->blake2sContext; - d->result.resize(length); - blake2s_final(©, reinterpret_cast<uint8_t *>(d->result.data()), length); + case QCryptographicHash::Blake2s_128: + case QCryptographicHash::Blake2s_160: + case QCryptographicHash::Blake2s_224: + case QCryptographicHash::Blake2s_256: { + const auto length = hashLengthInternal(method); + blake2s_state copy = blake2sContext; + result.resizeForOverwrite(length); + blake2s_final(©, result.data(), length); break; } #endif + case QCryptographicHash::NumAlgorithms: + Q_UNREACHABLE(); } - return d->result; } +#endif // !USING_OPENSSL30 /*! Returns the hash of \a data using \a method. + + \note In Qt versions prior to 6.3, this function took QByteArray, + not QByteArrayView. */ -QByteArray QCryptographicHash::hash(const QByteArray &data, Algorithm method) +QByteArray QCryptographicHash::hash(QByteArrayView data, Algorithm method) { - QCryptographicHash hash(method); + QCryptographicHashPrivate hash(method); hash.addData(data); - return hash.result(); + hash.finalizeUnchecked(); // no mutex needed: no-one but us has access to 'hash' + return hash.resultView().toByteArray(); } /*! @@ -634,49 +1169,483 @@ QByteArray QCryptographicHash::hash(const QByteArray &data, Algorithm method) */ int QCryptographicHash::hashLength(QCryptographicHash::Algorithm method) { + return hashLengthInternal(method); +} + +/*! + Returns whether the selected algorithm \a method is supported and if + result() will return a value when the \a method is used. + + \note OpenSSL will be responsible for providing this information when + used as a provider, otherwise \c true will be returned as the non-OpenSSL + implementation doesn't have any restrictions. + We return \c false if we fail to query OpenSSL. + + \since 6.5 +*/ + + +bool QCryptographicHash::supportsAlgorithm(QCryptographicHash::Algorithm method) +{ + return QCryptographicHashPrivate::supportsAlgorithm(method); +} + +bool QCryptographicHashPrivate::supportsAlgorithm(QCryptographicHash::Algorithm method) +{ +#ifdef USING_OPENSSL30 + // OpenSSL doesn't support Blake2b{60,236,384} and Blake2s{128,160,224} + // and these would automatically return FALSE in that case, while they are + // actually supported by our non-OpenSSL implementation. + if (useNonOpenSSLFallback(method)) + return true; + + auto legacyProvider = OSSL_PROVIDER_ptr(OSSL_PROVIDER_load(nullptr, "legacy")); + auto defaultProvider = OSSL_PROVIDER_ptr(OSSL_PROVIDER_load(nullptr, "default")); + + const char *restriction = "-fips"; + EVP_MD_ptr algorithm = EVP_MD_ptr(EVP_MD_fetch(nullptr, methodToName(method), restriction)); + + return algorithm != nullptr; +#else switch (method) { case QCryptographicHash::Sha1: - return 20; #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1 case QCryptographicHash::Md4: - return 16; case QCryptographicHash::Md5: - return 16; case QCryptographicHash::Sha224: - return SHA224HashSize; case QCryptographicHash::Sha256: - return SHA256HashSize; case QCryptographicHash::Sha384: - return SHA384HashSize; case QCryptographicHash::Sha512: - return SHA512HashSize; - case QCryptographicHash::Blake2s_128: - return 128 / 8; - case QCryptographicHash::Blake2b_160: - case QCryptographicHash::Blake2s_160: - return 160 / 8; case QCryptographicHash::RealSha3_224: case QCryptographicHash::Keccak_224: - case QCryptographicHash::Blake2s_224: - return 224 / 8; case QCryptographicHash::RealSha3_256: case QCryptographicHash::Keccak_256: + case QCryptographicHash::RealSha3_384: + case QCryptographicHash::Keccak_384: + case QCryptographicHash::RealSha3_512: + case QCryptographicHash::Keccak_512: + case QCryptographicHash::Blake2b_160: case QCryptographicHash::Blake2b_256: + case QCryptographicHash::Blake2b_384: + case QCryptographicHash::Blake2b_512: + case QCryptographicHash::Blake2s_128: + case QCryptographicHash::Blake2s_160: + case QCryptographicHash::Blake2s_224: case QCryptographicHash::Blake2s_256: - return 256 / 8; +#endif + return true; + case QCryptographicHash::NumAlgorithms: ; + }; + return false; +#endif // !USING_OPENSSL3 +} + +static constexpr int qt_hash_block_size(QCryptographicHash::Algorithm method) +{ + switch (method) { + case QCryptographicHash::Sha1: + return SHA1_Message_Block_Size; +#ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1 + case QCryptographicHash::Md4: + return 64; + case QCryptographicHash::Md5: + return 64; + case QCryptographicHash::Sha224: + return SHA224_Message_Block_Size; + case QCryptographicHash::Sha256: + return SHA256_Message_Block_Size; + case QCryptographicHash::Sha384: + return SHA384_Message_Block_Size; + case QCryptographicHash::Sha512: + return SHA512_Message_Block_Size; + case QCryptographicHash::RealSha3_224: + case QCryptographicHash::Keccak_224: + return 144; + case QCryptographicHash::RealSha3_256: + case QCryptographicHash::Keccak_256: + return 136; case QCryptographicHash::RealSha3_384: case QCryptographicHash::Keccak_384: - case QCryptographicHash::Blake2b_384: - return 384 / 8; + return 104; case QCryptographicHash::RealSha3_512: case QCryptographicHash::Keccak_512: + return 72; + case QCryptographicHash::Blake2b_160: + case QCryptographicHash::Blake2b_256: + case QCryptographicHash::Blake2b_384: case QCryptographicHash::Blake2b_512: - return 512 / 8; + return BLAKE2B_BLOCKBYTES; + case QCryptographicHash::Blake2s_128: + case QCryptographicHash::Blake2s_160: + case QCryptographicHash::Blake2s_224: + case QCryptographicHash::Blake2s_256: + return BLAKE2S_BLOCKBYTES; +#endif // QT_CRYPTOGRAPHICHASH_ONLY_SHA1 + case QCryptographicHash::NumAlgorithms: +#if !defined(Q_CC_GNU_ONLY) || Q_CC_GNU >= 900 + // GCC 8 has trouble with Q_UNREACHABLE() in constexpr functions + Q_UNREACHABLE(); #endif + break; } return 0; } +constexpr int maxHashBlockSize() +{ + int result = 0; + using A = QCryptographicHash::Algorithm; + for (int i = 0; i < A::NumAlgorithms ; ++i) + result = std::max(result, qt_hash_block_size(A(i))); + return result; +} + +[[maybe_unused]] +constexpr int minHashBlockSize() +{ + int result = INT_MAX; + using A = QCryptographicHash::Algorithm; + for (int i = 0; i < A::NumAlgorithms ; ++i) + result = std::min(result, qt_hash_block_size(A(i))); + return result; +} + +[[maybe_unused]] +constexpr int gcdHashBlockSize() +{ + int result = 0; + using A = QCryptographicHash::Algorithm; + for (int i = 0; i < A::NumAlgorithms ; ++i) + result = std::gcd(result, qt_hash_block_size(A(i))); + return result; +} + +using HashBlock = QSmallByteArray<maxHashBlockSize()>; + +static HashBlock xored(const HashBlock &block, quint8 val) noexcept +{ + // some hints for the optimizer: + Q_ASSERT(block.size() >= minHashBlockSize()); + Q_ASSERT(block.size() <= maxHashBlockSize()); + Q_ASSERT(block.size() % gcdHashBlockSize() == 0); + HashBlock result; + result.resizeForOverwrite(block.size()); + for (qsizetype i = 0; i < block.size(); ++i) + result[i] = block[i] ^ val; + return result; +} + +class QMessageAuthenticationCodePrivate +{ +public: + QMessageAuthenticationCodePrivate(QCryptographicHash::Algorithm m) + : messageHash(m) + { + } + + HashBlock key; + QCryptographicHashPrivate messageHash; + + void setKey(QByteArrayView k) noexcept; + void initMessageHash() noexcept; + void finalize(); + + // when not called from the static hash() function, this function needs to be + // called with messageHash.finalizeMutex held: + void finalizeUnchecked() noexcept; + // END functions that need to be called with finalizeMutex held +}; + +/*! + \internal + + Transforms key \a newKey into a block-sized format and stores it in member + \c key. + + This function assumes it can use messageHash (i.e. it's in its initial + state (reset() has been called)). +*/ +void QMessageAuthenticationCodePrivate::setKey(QByteArrayView newKey) noexcept +{ + const int blockSize = qt_hash_block_size(messageHash.method); + + if (newKey.size() > blockSize) { + messageHash.addData(newKey); + messageHash.finalizeUnchecked(); + static_assert([] { + using A = QCryptographicHash::Algorithm; + for (int i = 0; i < A::NumAlgorithms; ++i) { + if (hashLengthInternal(A(i)) > qt_hash_block_size(A(i))) + return false; + } + return true; + }(), "this code assumes that a hash's result always fits into that hash's block size"); + key = messageHash.result; + messageHash.reset(); + } else { + key.assign(newKey); + } + + if (key.size() < blockSize) + key.resize(blockSize, '\0'); + + initMessageHash(); +} + +/*! + \internal + + Seeds messageHash from \c key. + + This function assumes that messageHash is in its initial state (reset() has + been called). +*/ +void QMessageAuthenticationCodePrivate::initMessageHash() noexcept +{ + messageHash.addData(xored(key, 0x36)); +} + +/*! + \class QMessageAuthenticationCode + \inmodule QtCore + + \brief The QMessageAuthenticationCode class provides a way to generate + hash-based message authentication codes. + + \since 5.1 + + \ingroup tools + \reentrant + + Use the QMessageAuthenticationCode class to generate hash-based message + authentication codes (HMACs). The class supports all cryptographic + hash algorithms from \l QCryptographicHash (see also + \l{QCryptographicHash::Algorithm}). + + To generate a message authentication code, pass a suitable hash + algorithm and secret key to the constructor. Then process the message + data by calling \l addData() one or more times. After the full + message has been processed, get the final authentication code + via the \l result() function: + + \snippet qmessageauthenticationcode/main.cpp 0 + \dots + \snippet qmessageauthenticationcode/main.cpp 1 + + For simple cases like above, you can also use the static + \l hash() function: + + \snippet qmessageauthenticationcode/main.cpp 2 + + + \note The cryptographic strength of the HMAC depends upon the + size of the secret key, and the security of the + underlying hash function. + + \sa QCryptographicHash, QCryptographicHash::Algorithm +*/ + +/*! + Constructs an object that can be used to create a cryptographic hash from data + using method \a method and key \a key. + +//! [qba-to-qbav-6.6] + \note In Qt versions prior to 6.6, this function took its arguments as + QByteArray, not QByteArrayView. If you experience compile errors, it's + because your code is passing objects that are implicitly convertible to + QByteArray, but not QByteArrayView. Wrap the corresponding argument in + \c{QByteArray{~~~}} to make the cast explicit. This is backwards-compatible + with old Qt versions. +//! [qba-to-qbav-6.6] +*/ +QMessageAuthenticationCode::QMessageAuthenticationCode(QCryptographicHash::Algorithm method, + QByteArrayView key) + : d(new QMessageAuthenticationCodePrivate(method)) +{ + d->setKey(key); +} + +/*! + Destroys the object. +*/ +QMessageAuthenticationCode::~QMessageAuthenticationCode() +{ + delete d; +} + +/*! + \fn QMessageAuthenticationCode::QMessageAuthenticationCode(QMessageAuthenticationCode &&other) + + Move-constructs a new QMessageAuthenticationCode from \a other. + + \note The moved-from object \a other is placed in a + partially-formed state, in which the only valid operations are + destruction and assignment of a new object. + + \since 6.6 +*/ + +/*! + \fn QMessageAuthenticationCode &QMessageAuthenticationCode::operator=(QMessageAuthenticationCode &&other) + + Move-assigns \a other to this QMessageAuthenticationCode instance. + + \note The moved-from object \a other is placed in a + partially-formed state, in which the only valid operations are + destruction and assignment of a new object. + + \since 6.6 +*/ + +/*! + \fn void QMessageAuthenticationCode::swap(QMessageAuthenticationCode &other) + + Swaps message authentication code \a other with this message authentication + code. This operation is very fast and never fails. + + \since 6.6 +*/ + +/*! + Resets message data. Calling this function doesn't affect the key. +*/ +void QMessageAuthenticationCode::reset() noexcept +{ + d->messageHash.reset(); + d->initMessageHash(); +} + +/*! + Sets secret \a key. Calling this function automatically resets the object state. + + For optimal performance, call this function only to \e change the active key, + not to set an \e initial key, as in + + \code + QMessageAuthenticationCode mac(method); + mac.setKey(key); // does extra work + use(mac); + \endcode + + Prefer to pass initial keys as the constructor argument: + + \code + QMessageAuthenticationCode mac(method, key); // OK, optimal + use(mac); + \endcode + + You can use std::optional to delay construction of a + QMessageAuthenticationCode until you know the key: + + \code + std::optional<QMessageAuthenticationCode> mac; + ~~~ + key = ~~~; + mac.emplace(method, key); + use(*mac); + \endcode + + \include qcryptographichash.cpp {qba-to-qbav-6.6} +*/ +void QMessageAuthenticationCode::setKey(QByteArrayView key) noexcept +{ + d->messageHash.reset(); + d->setKey(key); +} + +/*! + \overload + Adds the first \a length chars of \a data to the message. +*/ +void QMessageAuthenticationCode::addData(const char *data, qsizetype length) +{ + d->messageHash.addData({data, length}); +} + +/*! + Adds \a data to the message. + + \include qcryptographichash.cpp {qba-to-qbav-6.6} + + \sa resultView(), result() +*/ +void QMessageAuthenticationCode::addData(QByteArrayView data) noexcept +{ + d->messageHash.addData(data); +} + +/*! + Reads the data from the open QIODevice \a device until it ends + and adds it to message. Returns \c true if reading was successful. + + \note \a device must be already opened. + */ +bool QMessageAuthenticationCode::addData(QIODevice *device) +{ + return d->messageHash.addData(device); +} + +/*! + \since 6.6 + + Returns the final hash value. + + Note that the returned view remains valid only as long as the + QMessageAuthenticationCode object is not modified by other means. + + \sa result() +*/ +QByteArrayView QMessageAuthenticationCode::resultView() const noexcept +{ + d->finalize(); + return d->messageHash.resultView(); +} + +/*! + Returns the final authentication code. + + \sa resultView(), QByteArray::toHex() +*/ +QByteArray QMessageAuthenticationCode::result() const +{ + return resultView().toByteArray(); +} + +void QMessageAuthenticationCodePrivate::finalize() +{ + const auto lock = qt_scoped_lock(messageHash.finalizeMutex); + if (!messageHash.result.isEmpty()) + return; + finalizeUnchecked(); +} + +void QMessageAuthenticationCodePrivate::finalizeUnchecked() noexcept +{ + messageHash.finalizeUnchecked(); + const HashResult hashedMessage = messageHash.result; + + messageHash.reset(); + messageHash.addData(xored(key, 0x5c)); + messageHash.addData(hashedMessage); + messageHash.finalizeUnchecked(); +} + +/*! + Returns the authentication code for the message \a message using + the key \a key and the method \a method. + + \include qcryptographichash.cpp {qba-to-qbav-6.6} +*/ +QByteArray QMessageAuthenticationCode::hash(QByteArrayView message, QByteArrayView key, + QCryptographicHash::Algorithm method) +{ + QMessageAuthenticationCodePrivate mac(method); + mac.setKey(key); + mac.messageHash.addData(message); + mac.finalizeUnchecked(); + return mac.messageHash.resultView().toByteArray(); +} + QT_END_NAMESPACE #ifndef QT_NO_QOBJECT |