Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge PR #2868: CryptographicHash: new class for computing cryptograp…
…hic hashes.
- Loading branch information
Showing
5 changed files
with
393 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
// Copyright 2005-2017 The Mumble Developers. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license | ||
// that can be found in the LICENSE file at the root of the | ||
// Mumble source tree or at <https://www.mumble.info/LICENSE>. | ||
|
||
#include "murmur_pch.h" | ||
|
||
#include "CryptographicHash.h" | ||
|
||
class CryptographicHashPrivate { | ||
public: | ||
CryptographicHashPrivate(const EVP_MD *type); | ||
~CryptographicHashPrivate(); | ||
void addData(const QByteArray &buf); | ||
QByteArray result(); | ||
|
||
private: | ||
EVP_MD_CTX *m_mdctx; | ||
|
||
/// The result of the cryptographic hash. | ||
/// This field is filled out by the result() | ||
/// method. | ||
/// Once this field is set, the m_mdctx is finalized | ||
/// and can't be used anymore, so result() and addData() | ||
/// must guard against that possibility. | ||
QByteArray m_result; | ||
|
||
CryptographicHashPrivate(); | ||
|
||
/// If m_mdctx is set, cleanupMdctx will destroy | ||
/// the EVP_MD_CTX object and set m_mdctx to NULL. | ||
void cleanupMdctx(); | ||
}; | ||
|
||
CryptographicHashPrivate::CryptographicHashPrivate() | ||
: m_mdctx(NULL) { | ||
} | ||
|
||
void CryptographicHashPrivate::cleanupMdctx() { | ||
if (m_mdctx) { | ||
EVP_MD_CTX_destroy(m_mdctx); | ||
m_mdctx = NULL; | ||
} | ||
} | ||
|
||
CryptographicHashPrivate::CryptographicHashPrivate(const EVP_MD *type) | ||
: m_mdctx(NULL) { | ||
|
||
m_mdctx = EVP_MD_CTX_create(); | ||
if (m_mdctx == NULL) { | ||
return; | ||
} | ||
|
||
int err = EVP_DigestInit_ex(m_mdctx, type, NULL); | ||
if (err != 1) { | ||
cleanupMdctx(); | ||
return; | ||
} | ||
} | ||
|
||
CryptographicHashPrivate::~CryptographicHashPrivate() { | ||
cleanupMdctx(); | ||
} | ||
|
||
void CryptographicHashPrivate::addData(const QByteArray &buf) { | ||
if (m_mdctx == NULL) { | ||
return; | ||
} | ||
|
||
// If we have a result already, that means our m_mdctx | ||
// object has been finalized, and can't be used anymore. | ||
// In that case, transition into an error state by cleaning | ||
// up the mdctx -- that way, subsequent calls to result() | ||
// will return an empty QByteArray. | ||
if (!m_result.isNull()) { | ||
cleanupMdctx(); | ||
return; | ||
} | ||
|
||
int err = EVP_DigestUpdate(m_mdctx, buf.constData(), buf.size()); | ||
if (err != 1) { | ||
cleanupMdctx(); | ||
} | ||
} | ||
|
||
QByteArray CryptographicHashPrivate::result() { | ||
if (m_mdctx == NULL) { | ||
return QByteArray(); | ||
} | ||
|
||
// If we have a result already, that means our m_mdctx | ||
// object has been finalized, and can't be used anymore. | ||
// In that case, just return the already computed result. | ||
if (!m_result.isNull()) { | ||
return m_result; | ||
} | ||
|
||
QByteArray digest(EVP_MD_CTX_size(m_mdctx), '\0'); | ||
int err = EVP_DigestFinal_ex(m_mdctx, reinterpret_cast<unsigned char *>(digest.data()), NULL); | ||
if (err != 1) { | ||
cleanupMdctx(); | ||
return QByteArray(); | ||
} | ||
|
||
m_result = digest; | ||
|
||
return m_result; | ||
} | ||
|
||
QByteArray CryptographicHash::hash(const QByteArray &buf, Algorithm algo) { | ||
CryptographicHash h(algo); | ||
h.addData(buf); | ||
return h.result(); | ||
} | ||
|
||
QString CryptographicHash::humanReadableAlgorithmName(CryptographicHash::Algorithm algo) { | ||
switch (algo) { | ||
case CryptographicHash::Sha1: | ||
return QLatin1String("SHA-1"); | ||
case CryptographicHash::Sha256: | ||
return QLatin1String("SHA-256"); | ||
} | ||
return QString(); | ||
} | ||
|
||
QString CryptographicHash::shortAlgorithmName(CryptographicHash::Algorithm algo) { | ||
switch (algo) { | ||
case CryptographicHash::Sha1: | ||
return QLatin1String("sha1"); | ||
case CryptographicHash::Sha256: | ||
return QLatin1String("sha256"); | ||
} | ||
return QString(); | ||
} | ||
|
||
CryptographicHash::CryptographicHash() | ||
: d(NULL) { | ||
} | ||
|
||
CryptographicHash::CryptographicHash(CryptographicHash::Algorithm algo) | ||
: d(NULL) { | ||
|
||
switch (algo) { | ||
case CryptographicHash::Sha1: | ||
d = new CryptographicHashPrivate(EVP_sha1()); | ||
break; | ||
case CryptographicHash::Sha256: | ||
d = new CryptographicHashPrivate(EVP_sha256()); | ||
break; | ||
} | ||
} | ||
|
||
void CryptographicHash::addData(const QByteArray &buf) { | ||
if (d) { | ||
d->addData(buf); | ||
} | ||
} | ||
|
||
QByteArray CryptographicHash::result() const { | ||
if (d) { | ||
return d->result(); | ||
} | ||
return QByteArray(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
// Copyright 2005-2017 The Mumble Developers. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license | ||
// that can be found in the LICENSE file at the root of the | ||
// Mumble source tree or at <https://www.mumble.info/LICENSE>. | ||
|
||
#ifndef MUMBLE_CRYPTOGRAPHICHASH_H_ | ||
#define MUMBLE_CRYPTOGRAPHICHASH_H_ | ||
|
||
#include <QByteArray> | ||
|
||
class CryptographicHashPrivate; | ||
|
||
/// CryptographicHash is used for computing cryptographic hashes. | ||
class CryptographicHash { | ||
public: | ||
/// Algorithm specifies an algorithm for use with CryptographicHash. | ||
enum Algorithm { | ||
/// The SHA-1 hashing algorithm. | ||
Sha1, | ||
/// The SHA-256 hashing algorithm. | ||
Sha256 | ||
}; | ||
|
||
/// Compute the cryptographic hash of |buf| using algorithm |algo| | ||
/// and return the result. | ||
/// On failure, the returned QByteArray will be empty. | ||
static QByteArray hash(const QByteArray &buf, Algorithm algo); | ||
|
||
/// Return the human readable name of |algo|. | ||
/// | ||
/// Examples: | ||
/// CryptographicHash::Sha1 -> "SHA-1" | ||
/// CryptographicHash::Sha256 -> "SHA-256" | ||
static QString humanReadableAlgorithmName(CryptographicHash::Algorithm algo); | ||
|
||
/// Return a machine-readable short name of |algo|. | ||
/// This variant is a short, lower-case name for the algorithm, suitable | ||
/// for prefixing when serializing a cryptographic hash. | ||
/// | ||
/// Examples: | ||
/// CryptographicHash::Sha1 -> "sha1" | ||
/// CryptographicHash::Sha256 -> "sha256" | ||
static QString shortAlgorithmName(CryptographicHash::Algorithm algo); | ||
|
||
/// Consruct a CryptographicHash object that can be used | ||
/// to incrementally compute a cryptographic hash using the | ||
/// hash function specified in |algo|. | ||
CryptographicHash(Algorithm algo); | ||
|
||
/// addData adds the content of |buf| to the hash computation. | ||
/// | ||
/// Attempting to add data to the CryptographicHash after result() | ||
/// has been called is an error, and will cause the CryptographicHash | ||
/// object to transition to an error state. Subsequent calls to result() | ||
/// will return an empty QByteArray. | ||
void addData(const QByteArray &buf); | ||
|
||
/// result() finalizes the CryptographicHash object, | ||
/// and returns the computed hash. | ||
/// After calling this method, it is no longer possible | ||
/// to add more data to the CryptographicHash object. | ||
/// If an error occurred during the computation, the | ||
/// returned QByteArray will be empty. | ||
QByteArray result() const; | ||
|
||
private: | ||
CryptographicHash(); | ||
CryptographicHashPrivate *d; | ||
}; | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
// Copyright 2005-2017 The Mumble Developers. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license | ||
// that can be found in the LICENSE file at the root of the | ||
// Mumble source tree or at <https://www.mumble.info/LICENSE>. | ||
|
||
#include <QtCore> | ||
#include <QtTest> | ||
#include <QLatin1String> | ||
|
||
#include "CryptographicHash.h" | ||
|
||
class TestCryptographicHash : public QObject { | ||
Q_OBJECT | ||
private slots: | ||
void sha1_data(); | ||
void sha1(); | ||
|
||
void sha256_data(); | ||
void sha256(); | ||
|
||
void staticHashVsObject(); | ||
void multipleResultCalls(); | ||
void addDataAfterResult(); | ||
}; | ||
|
||
/// normalizeHash removes all whitespace from the hex-encoded hash string. | ||
static QString normalizeHash(QString str) { | ||
str.replace(QLatin1String(" "), QLatin1String("")); | ||
str.replace(QLatin1String("\t"), QLatin1String("")); | ||
str.replace(QLatin1String("\r"), QLatin1String("")); | ||
str.replace(QLatin1String("\n"), QLatin1String("")); | ||
return str; | ||
} | ||
|
||
/// normalizeHash removes all whitespace from the hex-encoded hash byte array. | ||
static QByteArray normalizeHash(QByteArray buf) { | ||
buf.replace(" ", ""); | ||
buf.replace("\t", ""); | ||
buf.replace("\r", ""); | ||
buf.replace("\n", ""); | ||
return buf; | ||
} | ||
|
||
void TestCryptographicHash::sha1_data() { | ||
QTest::addColumn<QString>("input"); | ||
QTest::addColumn<QString>("expectedOutput"); | ||
|
||
// First 4 SHA1 test vectors from http://www.di-mgt.com.au/sha_testvectors.html | ||
// ...The rest are a bit too excessive to put in a table-based test. | ||
QTest::newRow("1") << QString(QLatin1String("abc")) << normalizeHash(QLatin1String("a9993e36 4706816a ba3e2571 7850c26c 9cd0d89d")); | ||
QTest::newRow("2") << QString(QLatin1String("")) << normalizeHash(QLatin1String("da39a3ee 5e6b4b0d 3255bfef 95601890 afd80709")); | ||
QTest::newRow("3") << QString(QLatin1String("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq")) << normalizeHash(QLatin1String("84983e44 1c3bd26e baae4aa1 f95129e5 e54670f1")); | ||
QTest::newRow("4") << QString(QLatin1String("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu")) << normalizeHash(QLatin1String("a49b2446 a02c645b f419f995 b6709125 3a04a259")); | ||
} | ||
|
||
void TestCryptographicHash::sha1() { | ||
QFETCH(QString, input); | ||
QFETCH(QString, expectedOutput); | ||
|
||
QByteArray outBuf = CryptographicHash::hash(input.toLatin1(), CryptographicHash::Sha1); | ||
QByteArray hexBuf = outBuf.toHex(); | ||
QString output = QString::fromLatin1(hexBuf); | ||
|
||
QCOMPARE(output, expectedOutput); | ||
} | ||
|
||
void TestCryptographicHash::sha256_data() { | ||
QTest::addColumn<QString>("input"); | ||
QTest::addColumn<QString>("expectedOutput"); | ||
|
||
// First 4 SHA256 test vectors from http://www.di-mgt.com.au/sha_testvectors.html | ||
// ...The rest are a bit too excessive to put in a table-based test. | ||
QTest::newRow("1") << QString(QLatin1String("abc")) << normalizeHash(QLatin1String("ba7816bf 8f01cfea 414140de 5dae2223 b00361a3 96177a9c b410ff61 f20015ad")); | ||
QTest::newRow("2") << QString(QLatin1String("")) << normalizeHash(QLatin1String("e3b0c442 98fc1c14 9afbf4c8 996fb924 27ae41e4 649b934c a495991b 7852b855")); | ||
QTest::newRow("3") << QString(QLatin1String("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq")) << normalizeHash(QLatin1String("248d6a61 d20638b8 e5c02693 0c3e6039 a33ce459 64ff2167 f6ecedd4 19db06c1")); | ||
QTest::newRow("4") << QString(QLatin1String("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu")) << normalizeHash(QLatin1String("cf5b16a7 78af8380 036ce59e 7b049237 0b249b11 e8f07a51 afac4503 7afee9d1")); | ||
} | ||
|
||
void TestCryptographicHash::sha256() { | ||
QFETCH(QString, input); | ||
QFETCH(QString, expectedOutput); | ||
|
||
QByteArray outBuf = CryptographicHash::hash(input.toLatin1(), CryptographicHash::Sha256); | ||
|
||
QVERIFY(!outBuf.isEmpty()); | ||
|
||
QByteArray hexBuf = outBuf.toHex(); | ||
QString output = QString::fromLatin1(hexBuf); | ||
|
||
QCOMPARE(output, expectedOutput); | ||
} | ||
|
||
void TestCryptographicHash::staticHashVsObject() { | ||
CryptographicHash h(CryptographicHash::Sha256); | ||
h.addData(QByteArray("abc")); | ||
QByteArray objectResult = h.result(); | ||
|
||
QByteArray staticResult = CryptographicHash::hash(QByteArray("abc"), CryptographicHash::Sha256); | ||
|
||
QVERIFY(!objectResult.isEmpty()); | ||
QVERIFY(!staticResult.isEmpty()); | ||
|
||
QCOMPARE(objectResult, staticResult); | ||
} | ||
|
||
void TestCryptographicHash::multipleResultCalls() { | ||
CryptographicHash h(CryptographicHash::Sha256); | ||
h.addData(QByteArray("abc")); | ||
|
||
QByteArray result1 = h.result(); | ||
QByteArray result2 = h.result(); | ||
QByteArray result3 = h.result(); | ||
|
||
QVERIFY(!result1.isEmpty()); | ||
QVERIFY(!result2.isEmpty()); | ||
QVERIFY(!result3.isEmpty()); | ||
|
||
QCOMPARE(result1.toHex(), normalizeHash(QByteArray("ba7816bf 8f01cfea 414140de 5dae2223 b00361a3 96177a9c b410ff61 f20015ad"))); | ||
QCOMPARE(result1, result2); | ||
QCOMPARE(result1, result3); | ||
} | ||
|
||
void TestCryptographicHash::addDataAfterResult() { | ||
CryptographicHash h(CryptographicHash::Sha256); | ||
h.addData(QByteArray("abc")); | ||
|
||
QByteArray result1 = h.result(); | ||
|
||
QCOMPARE(result1.toHex(), normalizeHash(QByteArray("ba7816bf 8f01cfea 414140de 5dae2223 b00361a3 96177a9c b410ff61 f20015ad"))); | ||
|
||
// Adding data after getting the result of a | ||
// CryptographicHash is an error. | ||
h.addData(QByteArray("abc")); | ||
QByteArray result2 = h.result(); | ||
QVERIFY(result2.isEmpty()); | ||
} | ||
|
||
QTEST_MAIN(TestCryptographicHash) | ||
#include "TestCryptographicHash.moc" |
Oops, something went wrong.