diff options
Diffstat (limited to 'src/corelib/tools/qcryptographichash.cpp')
-rw-r--r-- | src/corelib/tools/qcryptographichash.cpp | 1207 |
1 files changed, 1133 insertions, 74 deletions
diff --git a/src/corelib/tools/qcryptographichash.cpp b/src/corelib/tools/qcryptographichash.cpp index 8d60be175e..d0ed17eba2 100644 --- a/src/corelib/tools/qcryptographichash.cpp +++ b/src/corelib/tools/qcryptographichash.cpp @@ -1,9 +1,19 @@ -// Copyright (C) 2016 The Qt Company Ltd. +// 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" @@ -11,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; @@ -57,9 +72,7 @@ Q_CONSTINIT static SHA3Final * const sha3Final = Final; #endif -// Header from rfc6234 -#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' @@ -91,6 +104,9 @@ static inline int SHA384_512AddLength(SHA512Context *context, unsigned int lengt 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> @@ -100,17 +116,88 @@ 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 -static constexpr qsizetype MaxHashLength = 64; +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 : \ - /* if this triggers, then increase MaxHashLength accordingly */ \ - static_assert(MaxHashLength >= qsizetype(Size) ); \ return Size \ /*end*/ CASE(Sha1, 20); @@ -124,95 +211,184 @@ static constexpr int hashLengthInternal(QCryptographicHash::Algorithm method) no CASE(Blake2s_128, 128 / 8); case QCryptographicHash::Blake2b_160: case QCryptographicHash::Blake2s_160: - static_assert(160 / 8 <= MaxHashLength); return 160 / 8; case QCryptographicHash::RealSha3_224: case QCryptographicHash::Keccak_224: case QCryptographicHash::Blake2s_224: - static_assert(224 / 8 <= MaxHashLength); return 224 / 8; case QCryptographicHash::RealSha3_256: case QCryptographicHash::Keccak_256: case QCryptographicHash::Blake2b_256: case QCryptographicHash::Blake2s_256: - static_assert(256 / 8 <= MaxHashLength); return 256 / 8; case QCryptographicHash::RealSha3_384: case QCryptographicHash::Keccak_384: case QCryptographicHash::Blake2b_384: - static_assert(384 / 8 <= MaxHashLength); return 384 / 8; case QCryptographicHash::RealSha3_512: case QCryptographicHash::Keccak_512: case QCryptographicHash::Blake2b_512: - static_assert(512 / 8 <= MaxHashLength); 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: explicit QCryptographicHashPrivate(QCryptographicHash::Algorithm method) noexcept - : method(method) + : state(method), method(method) { - reset(); + } + ~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; - const QCryptographicHash::Algorithm method; - union { 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 - class SmallByteArray { - std::array<char, MaxHashLength> m_data; - static_assert(MaxHashLength <= std::numeric_limits<std::uint8_t>::max()); - std::uint8_t m_size; - public: - char *data() noexcept { return m_data.data(); } - const char *data() const noexcept { return m_data.data(); } - qsizetype size() const noexcept { return qsizetype{m_size}; } - bool isEmpty() const noexcept { return size() == 0; } - void clear() noexcept { m_size = 0; } - void resizeForOverwrite(qsizetype s) { - Q_ASSERT(s >= 0); - Q_ASSERT(s <= MaxHashLength); - m_size = std::uint8_t(s); - } - QByteArrayView toByteArrayView() const noexcept - { return QByteArrayView{data(), size()}; } - }; - SmallByteArray 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 @@ -248,7 +424,7 @@ void QCryptographicHashPrivate::sha3Finish(int bitCount, Sha3Variant sha3Variant break; } - sha3Final(©, reinterpret_cast<BitSequence *>(result.data())); + sha3Final(©, result.data()); } #endif @@ -305,6 +481,7 @@ void QCryptographicHashPrivate::sha3Finish(int bitCount, Sha3Variant sha3Variant \omitvalue RealSha3_256 \omitvalue RealSha3_384 \omitvalue RealSha3_512 + \omitvalue NumAlgorithms */ /*! @@ -316,6 +493,18 @@ QCryptographicHash::QCryptographicHash(Algorithm method) } /*! + \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() @@ -324,6 +513,27 @@ 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() noexcept @@ -331,12 +541,101 @@ void QCryptographicHash::reset() noexcept d->reset(); } -void QCryptographicHashPrivate::reset() noexcept +/*! + 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; - sha1InitState(&sha1Context); break; #ifdef QT_CRYPTOGRAPHICHASH_ONLY_SHA1 default: @@ -346,27 +645,21 @@ void QCryptographicHashPrivate::reset() noexcept #else case QCryptographicHash::Md4: new (&md4Context) md4_context; - md4_init(&md4Context); break; case QCryptographicHash::Md5: new (&md5Context) MD5Context; - MD5Init(&md5Context); break; case QCryptographicHash::Sha224: new (&sha224Context) SHA224Context; - SHA224Reset(&sha224Context); break; case QCryptographicHash::Sha256: new (&sha256Context) SHA256Context; - SHA256Reset(&sha256Context); break; case QCryptographicHash::Sha384: new (&sha384Context) SHA384Context; - SHA384Reset(&sha384Context); break; case QCryptographicHash::Sha512: new (&sha512Context) SHA512Context; - SHA512Reset(&sha512Context); break; case QCryptographicHash::RealSha3_224: case QCryptographicHash::Keccak_224: @@ -377,27 +670,134 @@ void QCryptographicHashPrivate::reset() noexcept case QCryptographicHash::RealSha3_512: case QCryptographicHash::Keccak_512: new (&sha3Context) SHA3Context; - sha3Init(&sha3Context, hashLengthInternal(method) * 8); break; case QCryptographicHash::Blake2b_160: case QCryptographicHash::Blake2b_256: case QCryptographicHash::Blake2b_384: case QCryptographicHash::Blake2b_512: new (&blake2bContext) blake2b_state; - blake2b_init(&blake2bContext, hashLengthInternal(method)); break; case QCryptographicHash::Blake2s_128: case QCryptographicHash::Blake2s_160: case QCryptographicHash::Blake2s_224: case QCryptographicHash::Blake2s_256: new (&blake2sContext) blake2s_state; - blake2s_init(&blake2sContext, hashLengthInternal(method)); 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); + } + // 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 @@ -426,6 +826,43 @@ void QCryptographicHash::addData(QByteArrayView bytes) noexcept 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(); @@ -489,10 +926,12 @@ void QCryptographicHashPrivate::addData(QByteArrayView bytes) noexcept blake2s_update(&blake2sContext, reinterpret_cast<const uint8_t *>(data), length); break; #endif + case QCryptographicHash::NumAlgorithms: + Q_UNREACHABLE(); } } - result.clear(); } +#endif // !USING_OPENSSL30 /*! Reads the data from the open QIODevice \a device until it ends @@ -501,6 +940,11 @@ void QCryptographicHashPrivate::addData(QByteArrayView bytes) noexcept */ bool QCryptographicHash::addData(QIODevice *device) { + return d->addData(device); +} + +bool QCryptographicHashPrivate::addData(QIODevice *device) +{ if (!device->isReadable()) return false; @@ -508,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) - d->addData({buffer, length}); + addData({buffer, qsizetype(length)}); // length always <= 1024 return device->atEnd(); } @@ -539,21 +983,86 @@ QByteArray QCryptographicHash::result() const */ 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 + +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 *)result.data()); + sha1ToHash(©, result.data()); break; } #ifdef QT_CRYPTOGRAPHICHASH_ONLY_SHA1 @@ -565,51 +1074,51 @@ void QCryptographicHashPrivate::finalize() noexcept case QCryptographicHash::Md4: { md4_context copy = md4Context; result.resizeForOverwrite(MD4_RESULTLEN); - md4_final(©, (unsigned char *)result.data()); + md4_final(©, result.data()); break; } case QCryptographicHash::Md5: { MD5Context copy = md5Context; result.resizeForOverwrite(16); - MD5Final(©, (unsigned char *)result.data()); + MD5Final(©, result.data()); break; } case QCryptographicHash::Sha224: { SHA224Context copy = sha224Context; result.resizeForOverwrite(SHA224HashSize); - SHA224Result(©, reinterpret_cast<unsigned char *>(result.data())); + SHA224Result(©, result.data()); break; } case QCryptographicHash::Sha256: { SHA256Context copy = sha256Context; result.resizeForOverwrite(SHA256HashSize); - SHA256Result(©, reinterpret_cast<unsigned char *>(result.data())); + SHA256Result(©, result.data()); break; } case QCryptographicHash::Sha384: { SHA384Context copy = sha384Context; result.resizeForOverwrite(SHA384HashSize); - SHA384Result(©, reinterpret_cast<unsigned char *>(result.data())); + SHA384Result(©, result.data()); break; } case QCryptographicHash::Sha512: { SHA512Context copy = sha512Context; result.resizeForOverwrite(SHA512HashSize); - SHA512Result(©, reinterpret_cast<unsigned char *>(result.data())); + SHA512Result(©, result.data()); break; } case QCryptographicHash::RealSha3_224: case QCryptographicHash::RealSha3_256: case QCryptographicHash::RealSha3_384: case QCryptographicHash::RealSha3_512: { - sha3Finish(8 * hashLengthInternal(method), Sha3Variant::Sha3); + sha3Finish(result, 8 * hashLengthInternal(method), Sha3Variant::Sha3); break; } case QCryptographicHash::Keccak_224: case QCryptographicHash::Keccak_256: case QCryptographicHash::Keccak_384: case QCryptographicHash::Keccak_512: { - sha3Finish(8 * hashLengthInternal(method), Sha3Variant::Keccak); + sha3Finish(result, 8 * hashLengthInternal(method), Sha3Variant::Keccak); break; } case QCryptographicHash::Blake2b_160: @@ -619,7 +1128,7 @@ void QCryptographicHashPrivate::finalize() noexcept const auto length = hashLengthInternal(method); blake2b_state copy = blake2bContext; result.resizeForOverwrite(length); - blake2b_final(©, reinterpret_cast<uint8_t *>(result.data()), length); + blake2b_final(©, result.data(), length); break; } case QCryptographicHash::Blake2s_128: @@ -629,25 +1138,64 @@ void QCryptographicHashPrivate::finalize() noexcept const auto length = hashLengthInternal(method); blake2s_state copy = blake2sContext; result.resizeForOverwrite(length); - blake2s_final(©, reinterpret_cast<uint8_t *>(result.data()), length); + blake2s_final(©, result.data(), length); break; } #endif + case QCryptographicHash::NumAlgorithms: + Q_UNREACHABLE(); } } +#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. + + \sa hashInto() */ QByteArray QCryptographicHash::hash(QByteArrayView data, Algorithm method) { + QByteArray ba(hashLengthInternal(method), Qt::Uninitialized); + [[maybe_unused]] const auto r = hashInto(ba, data, method); + Q_ASSERT(r.size() == ba.size()); + return ba; +} + +/*! + \since 6.8 + \fn QCryptographicHash::hashInto(QSpan<char> buffer, QSpan<const QByteArrayView> data, Algorithm method); + \fn QCryptographicHash::hashInto(QSpan<uchar> buffer, QSpan<const QByteArrayView> data, Algorithm method); + \fn QCryptographicHash::hashInto(QSpan<std::byte> buffer, QSpan<const QByteArrayView> data, Algorithm method); + \fn QCryptographicHash::hashInto(QSpan<char> buffer, QByteArrayView data, Algorithm method); + \fn QCryptographicHash::hashInto(QSpan<uchar> buffer, QByteArrayView data, Algorithm method); + \fn QCryptographicHash::hashInto(QSpan<std::byte> buffer, QByteArrayView data, Algorithm method); + + Returns the hash of \a data using \a method, using \a buffer to store the result. + + If \a data is a span, adds all the byte array views to the hash, in the order given. + + The return value will be a sub-span of \a buffer, unless \a buffer is of + insufficient size, in which case a null QByteArrayView is returned. + + \sa hash() +*/ +QByteArrayView QCryptographicHash::hashInto(QSpan<std::byte> buffer, + QSpan<const QByteArrayView> data, + Algorithm method) noexcept +{ QCryptographicHashPrivate hash(method); - hash.addData(data); - hash.finalize(); - return hash.resultView().toByteArray(); + for (QByteArrayView part : data) + hash.addData(part); + hash.finalizeUnchecked(); // no mutex needed: no-one but us has access to 'hash' + auto result = hash.resultView(); + if (buffer.size() < result.size()) + return {}; // buffer too small + // ### optimize: have the method directly write into `buffer` + memcpy(buffer.data(), result.data(), result.size()); + return buffer.first(result.size()); } /*! @@ -660,6 +1208,517 @@ 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: +#ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1 + case QCryptographicHash::Md4: + case QCryptographicHash::Md5: + case QCryptographicHash::Sha224: + case QCryptographicHash::Sha256: + case QCryptographicHash::Sha384: + case QCryptographicHash::Sha512: + 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: + 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: +#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: + 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 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: + explicit QMessageAuthenticationCodePrivate(QCryptographicHash::Algorithm m) noexcept + : 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} + + \sa hashInto() +*/ +QByteArray QMessageAuthenticationCode::hash(QByteArrayView message, QByteArrayView key, + QCryptographicHash::Algorithm method) +{ + QByteArray ba(hashLengthInternal(method), Qt::Uninitialized); + [[maybe_unused]] const auto r = hashInto(ba, message, key, method); + Q_ASSERT(r.size() == ba.size()); + return ba; +} + +/*! + \since 6.8 + \fn QMessageAuthenticationCode::hashInto(QSpan<char> buffer, QSpan<const QByteArrayView> messageParts, QByteArrayView key, QCryptographicHash::Algorithm method); + \fn QMessageAuthenticationCode::hashInto(QSpan<uchar> buffer, QSpan<const QByteArrayView> messageParts, QByteArrayView key, QCryptographicHash::Algorithm method); + \fn QMessageAuthenticationCode::hashInto(QSpan<std::byte> buffer, QSpan<const QByteArrayView> messageParts, QByteArrayView key, QCryptographicHash::Algorithm method); + \fn QMessageAuthenticationCode::hashInto(QSpan<char> buffer, QByteArrayView message, QByteArrayView key, QCryptographicHash::Algorithm method); + \fn QMessageAuthenticationCode::hashInto(QSpan<uchar> buffer, QByteArrayView message, QByteArrayView key, QCryptographicHash::Algorithm method); + \fn QMessageAuthenticationCode::hashInto(QSpan<std::byte> buffer, QByteArrayView message, QByteArrayView key, QCryptographicHash::Algorithm method); + + Returns the authentication code for the message (\a message or, for the + QSpan overloads, the concatenation of \a messageParts) using the key \a key + and the method \a method. + + The return value will be a sub-span of \a buffer, unless \a buffer is of + insufficient size, in which case a null QByteArrayView is returned. + + \sa hash() +*/ +QByteArrayView QMessageAuthenticationCode::hashInto(QSpan<std::byte> buffer, + QSpan<const QByteArrayView> messageParts, + QByteArrayView key, + QCryptographicHash::Algorithm method) noexcept +{ + QMessageAuthenticationCodePrivate mac(method); + mac.setKey(key); + for (QByteArrayView part : messageParts) + mac.messageHash.addData(part); + mac.finalizeUnchecked(); + auto result = mac.messageHash.resultView(); + if (buffer.size() < result.size()) + return {}; // buffer too small + // ### optimize: have the method directly write into `buffer` + memcpy(buffer.data(), result.data(), result.size()); + return buffer.first(result.size()); +} + QT_END_NAMESPACE #ifndef QT_NO_QOBJECT |