QCryptographicHash: use a std::array to hold result (was: QByteArray)

The maximum size for a hash result is 64 atm. Even if, and esp when,
we'll get to 128 and 256 bytes in the future, there's no reason to use
dynamic memory, because the sizes will always be statically known.

So use, essentially, a std::array<char, 64> to hold the result
internally. Add a bit of convenience API on top to limit impact on the
rest of the code and add a few static_asserts that ensure this is large
enough. Then give users access to the internal buffer by adding
QByteArrayView resultView() const noexcept. The documentation snippet
is taken from QString::data(), suitably adjusted.

Use resultView() in a few places instead of result().

[ChangeLog][QtCore][QCryptographicHash] Changed to use a
statically-sized buffer internally. Added resultView() to access it.

Change-Id: I96c35e55acacbe94529446d720c18325273ffd2f
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
Marc Mutz 2021-07-06 20:48:36 +02:00
parent c6e092a5f8
commit 27d6314b95
6 changed files with 78 additions and 37 deletions

View File

@ -138,47 +138,53 @@ static inline int SHA384_512AddLength(SHA512Context *context, unsigned int lengt
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
static constexpr qsizetype MaxHashLength = 64;
static constexpr int hashLengthInternal(QCryptographicHash::Algorithm method) noexcept static constexpr int hashLengthInternal(QCryptographicHash::Algorithm method) noexcept
{ {
switch (method) { switch (method) {
case QCryptographicHash::Sha1: #define CASE(Enum, Size) \
return 20; case QCryptographicHash:: Enum : \
/* if this triggers, then increase MaxHashLength accordingly */ \
static_assert(MaxHashLength >= qsizetype(Size) ); \
return Size \
/*end*/
CASE(Sha1, 20);
#ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1 #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
case QCryptographicHash::Md4: CASE(Md4, 16);
return 16; CASE(Md5, 16);
case QCryptographicHash::Md5: CASE(Sha224, SHA224HashSize);
return 16; CASE(Sha256, SHA256HashSize);
case QCryptographicHash::Sha224: CASE(Sha384, SHA384HashSize);
return SHA224HashSize; CASE(Sha512, SHA512HashSize);
case QCryptographicHash::Sha256: CASE(Blake2s_128, 128 / 8);
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::Blake2b_160:
case QCryptographicHash::Blake2s_160: case QCryptographicHash::Blake2s_160:
static_assert(160 / 8 <= MaxHashLength);
return 160 / 8; return 160 / 8;
case QCryptographicHash::RealSha3_224: case QCryptographicHash::RealSha3_224:
case QCryptographicHash::Keccak_224: case QCryptographicHash::Keccak_224:
case QCryptographicHash::Blake2s_224: case QCryptographicHash::Blake2s_224:
static_assert(224 / 8 <= MaxHashLength);
return 224 / 8; return 224 / 8;
case QCryptographicHash::RealSha3_256: case QCryptographicHash::RealSha3_256:
case QCryptographicHash::Keccak_256: case QCryptographicHash::Keccak_256:
case QCryptographicHash::Blake2b_256: case QCryptographicHash::Blake2b_256:
case QCryptographicHash::Blake2s_256: case QCryptographicHash::Blake2s_256:
static_assert(256 / 8 <= MaxHashLength);
return 256 / 8; return 256 / 8;
case QCryptographicHash::RealSha3_384: case QCryptographicHash::RealSha3_384:
case QCryptographicHash::Keccak_384: case QCryptographicHash::Keccak_384:
case QCryptographicHash::Blake2b_384: case QCryptographicHash::Blake2b_384:
static_assert(384 / 8 <= MaxHashLength);
return 384 / 8; return 384 / 8;
case QCryptographicHash::RealSha3_512: case QCryptographicHash::RealSha3_512:
case QCryptographicHash::Keccak_512: case QCryptographicHash::Keccak_512:
case QCryptographicHash::Blake2b_512: case QCryptographicHash::Blake2b_512:
static_assert(512 / 8 <= MaxHashLength);
return 512 / 8; return 512 / 8;
#endif #endif
#undef CASE
} }
return 0; return 0;
} }
@ -194,7 +200,8 @@ public:
void reset() noexcept; void reset() noexcept;
void addData(QByteArrayView bytes) noexcept; void addData(QByteArrayView bytes) noexcept;
QByteArray finalize(); void finalize() noexcept;
QByteArrayView resultView() const noexcept { return result.toByteArrayView(); }
const QCryptographicHash::Algorithm method; const QCryptographicHash::Algorithm method;
union { union {
@ -219,7 +226,25 @@ public:
}; };
void sha3Finish(int bitCount, Sha3Variant sha3Variant); void sha3Finish(int bitCount, Sha3Variant sha3Variant);
#endif #endif
QByteArray result; 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 resize(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;
}; };
#ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1 #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1
@ -337,7 +362,7 @@ QCryptographicHash::~QCryptographicHash()
/*! /*!
Resets the object. Resets the object.
*/ */
void QCryptographicHash::reset() void QCryptographicHash::reset() noexcept
{ {
d->reset(); d->reset();
} }
@ -532,17 +557,33 @@ bool QCryptographicHash::addData(QIODevice *device)
/*! /*!
Returns the final hash value. Returns the final hash value.
\sa QByteArray::toHex() \sa resultView(), QByteArray::toHex()
*/ */
QByteArray QCryptographicHash::result() const QByteArray QCryptographicHash::result() const
{ {
return d->finalize(); return resultView().toByteArray();
} }
QByteArray QCryptographicHashPrivate::finalize() /*!
\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::toHex()
*/
QByteArrayView QCryptographicHash::resultView() const noexcept
{
d->finalize();
return d->resultView();
}
void QCryptographicHashPrivate::finalize() noexcept
{ {
if (!result.isEmpty()) if (!result.isEmpty())
return result; return;
switch (method) { switch (method) {
case QCryptographicHash::Sha1: { case QCryptographicHash::Sha1: {
@ -630,7 +671,6 @@ QByteArray QCryptographicHashPrivate::finalize()
} }
#endif #endif
} }
return result;
} }
/*! /*!
@ -643,7 +683,8 @@ QByteArray QCryptographicHash::hash(QByteArrayView data, Algorithm method)
{ {
QCryptographicHashPrivate hash(method); QCryptographicHashPrivate hash(method);
hash.addData(data); hash.addData(data);
return hash.finalize(); hash.finalize();
return hash.resultView().toByteArray();
} }
/*! /*!

View File

@ -102,7 +102,7 @@ public:
explicit QCryptographicHash(Algorithm method); explicit QCryptographicHash(Algorithm method);
~QCryptographicHash(); ~QCryptographicHash();
void reset(); void reset() noexcept;
#if QT_DEPRECATED_SINCE(6, 4) #if QT_DEPRECATED_SINCE(6, 4)
QT_DEPRECATED_VERSION_X_6_4("Use the QByteArrayView overload instead") QT_DEPRECATED_VERSION_X_6_4("Use the QByteArrayView overload instead")
@ -115,6 +115,7 @@ public:
bool addData(QIODevice *device); bool addData(QIODevice *device);
QByteArray result() const; QByteArray result() const;
QByteArrayView resultView() const noexcept;
#ifdef QT_BUILD_FUNCTIONS_REMOVED_IN_6_3 #ifdef QT_BUILD_FUNCTIONS_REMOVED_IN_6_3
static QByteArray hash(const QByteArray &data, Algorithm method); static QByteArray hash(const QByteArray &data, Algorithm method);

View File

@ -251,7 +251,7 @@ QByteArray QMessageAuthenticationCode::result() const
const int blockSize = qt_hash_block_size(d->method); const int blockSize = qt_hash_block_size(d->method);
QByteArray hashedMessage = d->messageHash.result(); QByteArrayView hashedMessage = d->messageHash.resultView();
QVarLengthArray<char> oKeyPad(blockSize); QVarLengthArray<char> oKeyPad(blockSize);
const char * const keyData = d->key.constData(); const char * const keyData = d->key.constData();

View File

@ -1239,7 +1239,6 @@ QByteArray qEncodeHmacMd5(QByteArray &key, const QByteArray &message)
Q_ASSERT_X(!(key.isEmpty()),"qEncodeHmacMd5", "Empty key check"); Q_ASSERT_X(!(key.isEmpty()),"qEncodeHmacMd5", "Empty key check");
QCryptographicHash hash(QCryptographicHash::Md5); QCryptographicHash hash(QCryptographicHash::Md5);
QByteArray hMsg;
QByteArray iKeyPad(blockSize, 0x36); QByteArray iKeyPad(blockSize, 0x36);
QByteArray oKeyPad(blockSize, 0x5c); QByteArray oKeyPad(blockSize, 0x5c);
@ -1272,7 +1271,7 @@ QByteArray qEncodeHmacMd5(QByteArray &key, const QByteArray &message)
hash.reset(); hash.reset();
hash.addData(iKeyPad); hash.addData(iKeyPad);
hMsg = hash.result(); QByteArrayView hMsg = hash.resultView();
//Digest gen after pass-1: H((K0 xor ipad)||text) //Digest gen after pass-1: H((K0 xor ipad)||text)
QByteArray hmacDigest; QByteArray hmacDigest;

View File

@ -424,9 +424,9 @@ QByteArray deriveAesKey(QSslKeyPrivate::Cipher cipher, const QByteArray &passPhr
hash.addData(data); hash.addData(data);
if (cipher == Cipher::Aes192Cbc) if (cipher == Cipher::Aes192Cbc)
return key.append(hash.result().constData(), 8); return key.append(hash.resultView().first(8));
return key.append(hash.result()); return key.append(hash.resultView());
} }
QByteArray deriveKey(QSslKeyPrivate::Cipher cipher, const QByteArray &passPhrase, QByteArray deriveKey(QSslKeyPrivate::Cipher cipher, const QByteArray &passPhrase,

View File

@ -78,15 +78,17 @@ void tst_QCryptographicHash::repeated_result()
hash.addData(first); hash.addData(first);
QFETCH(QByteArray, hash_first); QFETCH(QByteArray, hash_first);
QByteArray result = hash.result(); QByteArrayView result = hash.resultView();
QCOMPARE(result, hash_first); QCOMPARE(result, hash_first);
QCOMPARE(result, hash.resultView());
QCOMPARE(result, hash.result()); QCOMPARE(result, hash.result());
hash.reset(); hash.reset();
hash.addData(first); hash.addData(first);
result = hash.result(); result = hash.resultView();
QCOMPARE(result, hash_first); QCOMPARE(result, hash_first);
QCOMPARE(result, hash.result()); QCOMPARE(result, hash.result());
QCOMPARE(result, hash.resultView());
} }
void tst_QCryptographicHash::intermediary_result_data() void tst_QCryptographicHash::intermediary_result_data()
@ -179,16 +181,14 @@ void tst_QCryptographicHash::intermediary_result()
hash.addData(first); hash.addData(first);
QFETCH(QByteArray, hash_first); QFETCH(QByteArray, hash_first);
QByteArray result = hash.result(); QCOMPARE(hash.resultView(), hash_first);
QCOMPARE(result, hash_first);
// don't reset // don't reset
QFETCH(QByteArray, second); QFETCH(QByteArray, second);
QFETCH(QByteArray, hash_firstsecond); QFETCH(QByteArray, hash_firstsecond);
hash.addData(second); hash.addData(second);
result = hash.result(); QCOMPARE(hash.resultView(), hash_firstsecond);
QCOMPARE(result, hash_firstsecond);
hash.reset(); hash.reset();
} }