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
static constexpr qsizetype MaxHashLength = 64;
static constexpr int hashLengthInternal(QCryptographicHash::Algorithm method) noexcept
{
switch (method) {
case QCryptographicHash::Sha1:
return 20;
#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);
#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(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:
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
}
return 0;
}
@ -194,7 +200,8 @@ public:
void reset() noexcept;
void addData(QByteArrayView bytes) noexcept;
QByteArray finalize();
void finalize() noexcept;
QByteArrayView resultView() const noexcept { return result.toByteArrayView(); }
const QCryptographicHash::Algorithm method;
union {
@ -219,7 +226,25 @@ public:
};
void sha3Finish(int bitCount, Sha3Variant sha3Variant);
#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
@ -337,7 +362,7 @@ QCryptographicHash::~QCryptographicHash()
/*!
Resets the object.
*/
void QCryptographicHash::reset()
void QCryptographicHash::reset() noexcept
{
d->reset();
}
@ -532,17 +557,33 @@ bool QCryptographicHash::addData(QIODevice *device)
/*!
Returns the final hash value.
\sa QByteArray::toHex()
\sa resultView(), QByteArray::toHex()
*/
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())
return result;
return;
switch (method) {
case QCryptographicHash::Sha1: {
@ -630,7 +671,6 @@ QByteArray QCryptographicHashPrivate::finalize()
}
#endif
}
return result;
}
/*!
@ -643,7 +683,8 @@ QByteArray QCryptographicHash::hash(QByteArrayView data, Algorithm method)
{
QCryptographicHashPrivate hash(method);
hash.addData(data);
return hash.finalize();
hash.finalize();
return hash.resultView().toByteArray();
}
/*!

View File

@ -102,7 +102,7 @@ public:
explicit QCryptographicHash(Algorithm method);
~QCryptographicHash();
void reset();
void reset() noexcept;
#if QT_DEPRECATED_SINCE(6, 4)
QT_DEPRECATED_VERSION_X_6_4("Use the QByteArrayView overload instead")
@ -115,6 +115,7 @@ public:
bool addData(QIODevice *device);
QByteArray result() const;
QByteArrayView resultView() const noexcept;
#ifdef QT_BUILD_FUNCTIONS_REMOVED_IN_6_3
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);
QByteArray hashedMessage = d->messageHash.result();
QByteArrayView hashedMessage = d->messageHash.resultView();
QVarLengthArray<char> oKeyPad(blockSize);
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");
QCryptographicHash hash(QCryptographicHash::Md5);
QByteArray hMsg;
QByteArray iKeyPad(blockSize, 0x36);
QByteArray oKeyPad(blockSize, 0x5c);
@ -1272,7 +1271,7 @@ QByteArray qEncodeHmacMd5(QByteArray &key, const QByteArray &message)
hash.reset();
hash.addData(iKeyPad);
hMsg = hash.result();
QByteArrayView hMsg = hash.resultView();
//Digest gen after pass-1: H((K0 xor ipad)||text)
QByteArray hmacDigest;

View File

@ -424,9 +424,9 @@ QByteArray deriveAesKey(QSslKeyPrivate::Cipher cipher, const QByteArray &passPhr
hash.addData(data);
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,

View File

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