0

//crypto: start reworking hash & hmac interfaces

This change adds:

    crypto:#️⃣:Sha1
    crypto:#️⃣:Sha256
    crypto:#️⃣:Sha512

    crypto::hmac::SignSha1
    crypto::hmac::SignSha256
    crypto::hmac::SignSha512

    crypto::hmac::VerifySha1
    crypto::hmac::VerifySha256
    crypto::hmac::VerifySha512

and also three more generic functions that take a HashKind:

    crypto:#️⃣:Hash
    crypto::hmac::Sign
    crypto::hmac::Verify

These functions should be able to replace most users of the existing
SecureHash and HMAC APIs with single-shot calls. The kind-specific
wrapper functions exist primarily because they allow for compile-time
checking that the right digest sizes are being used.

This change also adds:

    crypto:#️⃣:Hasher

which is a streaming, spanified interface to replace crypto::SecureHash
and which does not require a factory function or heap allocations.

Bug: 374310081, 374334448
Change-Id: Ie18904063099eb5376b01f9e0deaf4c5eba134ab
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5943830
Commit-Queue: Elly FJ <ellyjones@chromium.org>
Reviewed-by: David Benjamin <davidben@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1372836}
This commit is contained in:
Elly
2024-10-23 18:06:46 +00:00
committed by Chromium LUCI CQ
parent b3410fbe68
commit 1e2fd41e22
9 changed files with 482 additions and 0 deletions

@ -31,6 +31,8 @@ component("crypto") {
"encryptor.h",
"features.cc",
"features.h",
"hash.cc",
"hash.h",
"hkdf.cc",
"hkdf.h",
"hmac.cc",
@ -182,6 +184,7 @@ test("crypto_unittests") {
"ec_private_key_unittest.cc",
"ec_signature_creator_unittest.cc",
"encryptor_unittest.cc",
"hash_unittest.cc",
"hmac_unittest.cc",
"kdf_unittest.cc",
"process_bound_string_unittest.cc",

90
crypto/hash.cc Normal file

@ -0,0 +1,90 @@
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "crypto/hash.h"
#include "base/notreached.h"
#include "third_party/boringssl/src/include/openssl/digest.h"
#include "third_party/boringssl/src/include/openssl/evp.h"
namespace crypto::hash {
namespace {
const EVP_MD* EVPMDForHashKind(HashKind kind) {
switch (kind) {
case HashKind::kSha1:
return EVP_sha1();
case HashKind::kSha256:
return EVP_sha256();
case HashKind::kSha512:
return EVP_sha512();
}
NOTREACHED();
}
} // namespace
void Hash(HashKind kind,
base::span<const uint8_t> data,
base::span<uint8_t> digest) {
const EVP_MD* md = EVPMDForHashKind(kind);
CHECK_EQ(digest.size(), EVP_MD_size(md));
CHECK(EVP_Digest(data.data(), data.size(), digest.data(), nullptr, md,
nullptr));
}
std::array<uint8_t, kSha1Size> Sha1(base::span<const uint8_t> data) {
std::array<uint8_t, kSha1Size> result;
Hash(HashKind::kSha1, data, result);
return result;
}
std::array<uint8_t, kSha256Size> Sha256(base::span<const uint8_t> data) {
std::array<uint8_t, kSha256Size> result;
Hash(HashKind::kSha256, data, result);
return result;
}
std::array<uint8_t, kSha512Size> Sha512(base::span<const uint8_t> data) {
std::array<uint8_t, kSha512Size> result;
Hash(HashKind::kSha512, data, result);
return result;
}
Hasher::Hasher(HashKind kind) {
CHECK(EVP_DigestInit(ctx_.get(), EVPMDForHashKind(kind)));
}
Hasher::Hasher(const Hasher& other) {
*this = other;
}
Hasher::Hasher(Hasher&& other) {
*this = other;
}
Hasher& Hasher::operator=(const Hasher& other) {
CHECK(EVP_MD_CTX_copy_ex(ctx_.get(), other.ctx_.get()));
return *this;
}
Hasher& Hasher::operator=(Hasher&& other) {
ctx_ = std::move(other.ctx_);
return *this;
}
Hasher::~Hasher() = default;
void Hasher::Update(base::span<const uint8_t> data) {
CHECK(EVP_DigestUpdate(ctx_.get(), data.data(), data.size()));
}
void Hasher::Finish(base::span<uint8_t> digest) {
CHECK_EQ(digest.size(), EVP_MD_CTX_size(ctx_.get()));
CHECK(EVP_DigestFinal(ctx_.get(), digest.data(), nullptr));
}
} // namespace crypto::hash

78
crypto/hash.h Normal file

@ -0,0 +1,78 @@
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CRYPTO_HASH_H_
#define CRYPTO_HASH_H_
#include "base/containers/span.h"
#include "base/notreached.h"
#include "crypto/crypto_export.h"
#include "third_party/boringssl/src/include/openssl/digest.h"
namespace crypto::hash {
inline constexpr size_t kSha1Size = 20;
inline constexpr size_t kSha256Size = 32;
inline constexpr size_t kSha512Size = 64;
// Unless your code needs to be generic over HashKind, use one of these
// kind-specific functions:
CRYPTO_EXPORT std::array<uint8_t, kSha1Size> Sha1(
base::span<const uint8_t> data);
CRYPTO_EXPORT std::array<uint8_t, kSha256Size> Sha256(
base::span<const uint8_t> data);
CRYPTO_EXPORT std::array<uint8_t, kSha512Size> Sha512(
base::span<const uint8_t> data);
// If you do need to be generic, you can use the Hash() function and pass a
// HashKind instead.
enum HashKind {
kSha1,
kSha256,
kSha512,
};
inline constexpr size_t DigestSizeForHashKind(HashKind k) {
switch (k) {
case kSha1:
return kSha1Size;
case kSha256:
return kSha256Size;
case kSha512:
return kSha512Size;
}
NOTREACHED();
}
// One-shot hashing. The passed-in digest span must be the correct size for the
// digest; use DigestSizeForHashKind() if your HashKind is variable.
CRYPTO_EXPORT void Hash(HashKind kind,
base::span<const uint8_t> data,
base::span<uint8_t> digest);
// A streaming hasher interface. Calling Finish() resets the hash context to the
// initial state after computing the digest.
class Hasher {
public:
explicit Hasher(HashKind kind);
Hasher(const Hasher& other);
Hasher(Hasher&& other);
Hasher& operator=(const Hasher& other);
Hasher& operator=(Hasher&& other);
~Hasher();
void Update(base::span<const uint8_t> data);
// The digest span must be the right size.
void Finish(base::span<uint8_t> digest);
private:
bssl::ScopedEVP_MD_CTX ctx_;
};
} // namespace crypto::hash
#endif // CRYPTO_HASH_H_

93
crypto/hash_unittest.cc Normal file

@ -0,0 +1,93 @@
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "crypto/hash.h"
#include <array>
#include "base/strings/string_number_conversions.h"
#include "testing/gtest/include/gtest/gtest.h"
struct TestCase {
const char* message;
const char* digest;
};
void PrepareTestCase(const TestCase& c,
std::vector<uint8_t>* message,
base::span<uint8_t> digest) {
CHECK(base::HexStringToBytes(c.message, message));
CHECK(base::HexStringToSpan(c.digest, digest));
}
TEST(HashTest, Sha1) {
const auto cases = std::to_array<TestCase>(
{// FIPS 180-4 "SHA1ShortMsg" test vector:
{
.message = "3552694cdf663fd94b224747ac406aaf",
.digest = "a150de927454202d94e656de4c7c0ca691de955d",
}});
for (const auto& c : cases) {
std::vector<uint8_t> message;
std::array<uint8_t, 20> digest;
PrepareTestCase(c, &message, digest);
auto computed_digest = crypto::hash::Sha1(base::as_byte_span(message));
EXPECT_EQ(digest, computed_digest);
}
}
TEST(HashTest, Sha256) {
const auto cases = std::to_array<TestCase>(
{// FIPS 180-4 "SHA256ShortMsg" test vector:
{
.message = "0a27847cdc98bd6f62220b046edd762b",
.digest = "80c25ec1600587e7f28b18b1b18e3cdc89928e39cab3bc25e4d4a4c13"
"9bcedc4",
}});
for (const auto& c : cases) {
std::vector<uint8_t> message;
std::array<uint8_t, 32> digest;
PrepareTestCase(c, &message, digest);
auto computed_digest = crypto::hash::Sha256(base::as_byte_span(message));
EXPECT_EQ(digest, computed_digest);
}
}
TEST(HashTest, Sha512) {
const auto cases = std::to_array<TestCase>(
{// FIPS 180-4 "SHA512ShortMsg" test vector:
{
.message = "cd67bd4054aaa3baa0db178ce232fd5a",
.digest = "0d8521f8f2f3900332d1a1a55c60ba81d04d28dfe8c504b6328ae7879"
"25fe018"
"8f2ba91c3a9f0c1653c4bf0ada356455ea36fd31f8e73e3951cad4ebb"
"a8c6e04",
}});
for (const auto& c : cases) {
std::vector<uint8_t> message;
std::array<uint8_t, 64> digest;
PrepareTestCase(c, &message, digest);
auto computed_digest = crypto::hash::Sha512(base::as_byte_span(message));
EXPECT_EQ(digest, computed_digest);
}
}
TEST(HashTest, WrongDigestSizeDies) {
std::array<uint8_t, 16> small_digest;
std::array<uint8_t, 128> big_digest;
std::array<uint8_t, 16> input;
EXPECT_DEATH_IF_SUPPORTED(
crypto::hash::Hash(crypto::hash::HashKind::kSha256, input, small_digest),
"size");
EXPECT_DEATH_IF_SUPPORTED(
crypto::hash::Hash(crypto::hash::HashKind::kSha256, input, big_digest),
"size");
}

@ -116,4 +116,95 @@ bool HMAC::VerifyTruncated(base::span<const uint8_t> data,
return SecureMemEqual(digest, computed_digest);
}
namespace hmac {
namespace {
const EVP_MD* EVPMDForHashKind(crypto::hash::HashKind kind) {
switch (kind) {
case crypto::hash::HashKind::kSha1:
return EVP_sha1();
case crypto::hash::HashKind::kSha256:
return EVP_sha256();
case crypto::hash::HashKind::kSha512:
return EVP_sha512();
}
NOTREACHED();
}
} // namespace
void Sign(crypto::hash::HashKind kind,
base::span<const uint8_t> key,
base::span<const uint8_t> data,
base::span<uint8_t> hmac) {
const EVP_MD* md = EVPMDForHashKind(kind);
CHECK_EQ(hmac.size(), EVP_MD_size(md));
bssl::ScopedHMAC_CTX ctx;
CHECK(HMAC_Init_ex(ctx.get(), key.data(), key.size(), EVPMDForHashKind(kind),
nullptr));
CHECK(HMAC_Update(ctx.get(), data.data(), data.size()));
CHECK(HMAC_Final(ctx.get(), hmac.data(), nullptr));
}
bool Verify(crypto::hash::HashKind kind,
base::span<const uint8_t> key,
base::span<const uint8_t> data,
base::span<const uint8_t> hmac) {
const EVP_MD* md = EVPMDForHashKind(kind);
CHECK_EQ(hmac.size(), EVP_MD_size(md));
std::array<uint8_t, EVP_MAX_MD_SIZE> computed_buf;
base::span<uint8_t> computed =
base::span(computed_buf).first(EVP_MD_size(md));
Sign(kind, key, data, computed);
return crypto::SecureMemEqual(computed, hmac);
}
std::array<uint8_t, crypto::hash::kSha1Size> SignSha1(
base::span<const uint8_t> key,
base::span<const uint8_t> data) {
std::array<uint8_t, crypto::hash::kSha1Size> result;
Sign(crypto::hash::HashKind::kSha1, key, data, result);
return result;
}
std::array<uint8_t, crypto::hash::kSha256Size> SignSha256(
base::span<const uint8_t> key,
base::span<const uint8_t> data) {
std::array<uint8_t, crypto::hash::kSha256Size> result;
Sign(crypto::hash::HashKind::kSha256, key, data, result);
return result;
}
std::array<uint8_t, crypto::hash::kSha512Size> SignSha512(
base::span<const uint8_t> key,
base::span<const uint8_t> data) {
std::array<uint8_t, crypto::hash::kSha512Size> result;
Sign(crypto::hash::HashKind::kSha512, key, data, result);
return result;
}
bool VerifySha1(base::span<const uint8_t> key,
base::span<const uint8_t> data,
base::span<const uint8_t, 20> hmac) {
return Verify(crypto::hash::HashKind::kSha1, key, data, hmac);
}
bool VerifySha256(base::span<const uint8_t> key,
base::span<const uint8_t> data,
base::span<const uint8_t, 32> hmac) {
return Verify(crypto::hash::HashKind::kSha256, key, data, hmac);
}
bool VerifySha512(base::span<const uint8_t> key,
base::span<const uint8_t> data,
base::span<const uint8_t, 64> hmac) {
return Verify(crypto::hash::HashKind::kSha512, key, data, hmac);
}
} // namespace hmac
} // namespace crypto

@ -18,12 +18,15 @@
#include "base/containers/span.h"
#include "crypto/crypto_export.h"
#include "crypto/hash.h"
namespace crypto {
// Simplify the interface and reduce includes by abstracting out the internals.
class SymmetricKey;
// TODO(https://issues.chromium.org/issues/374334448): Rework this interface and
// delete much of it.
class CRYPTO_EXPORT HMAC {
public:
// The set of supported hash functions. Extend as required.
@ -108,6 +111,52 @@ class CRYPTO_EXPORT HMAC {
std::vector<unsigned char> key_;
};
namespace hmac {
// Single-shot interfaces for working with HMACs. Unless your code needs to be
// generic over hash kinds, you should use the convenience interfaces that are
// named after a specific kind, since they allow compile-time error checking of
// the hmac size.
CRYPTO_EXPORT std::array<uint8_t, crypto::hash::kSha1Size> SignSha1(
base::span<const uint8_t> key,
base::span<const uint8_t> data);
CRYPTO_EXPORT std::array<uint8_t, crypto::hash::kSha256Size> SignSha256(
base::span<const uint8_t> key,
base::span<const uint8_t> data);
CRYPTO_EXPORT std::array<uint8_t, crypto::hash::kSha512Size> SignSha512(
base::span<const uint8_t> key,
base::span<const uint8_t> data);
[[nodiscard]] CRYPTO_EXPORT bool VerifySha1(
base::span<const uint8_t> key,
base::span<const uint8_t> data,
base::span<const uint8_t, crypto::hash::kSha1Size> hmac);
[[nodiscard]] CRYPTO_EXPORT bool VerifySha256(
base::span<const uint8_t> key,
base::span<const uint8_t> data,
base::span<const uint8_t, crypto::hash::kSha256Size> hmac);
[[nodiscard]] CRYPTO_EXPORT bool VerifySha512(
base::span<const uint8_t> key,
base::span<const uint8_t> data,
base::span<const uint8_t, crypto::hash::kSha512Size> hmac);
// If you need to be generic over hash types, you can instead use these, but you
// must pass the correct size buffer for |hmac|:
CRYPTO_EXPORT void Sign(crypto::hash::HashKind kind,
base::span<const uint8_t> key,
base::span<const uint8_t> data,
base::span<uint8_t> hmac);
[[nodiscard]] CRYPTO_EXPORT bool Verify(crypto::hash::HashKind kind,
base::span<const uint8_t> key,
base::span<const uint8_t> data,
base::span<const uint8_t> hmac);
} // namespace hmac
} // namespace crypto
#endif // CRYPTO_HMAC_H_

@ -15,6 +15,7 @@
#include <string>
#include <string_view>
#include "base/strings/string_number_conversions.h"
#include "testing/gtest/include/gtest/gtest.h"
static const size_t kSHA1DigestSize = 20;
@ -355,3 +356,74 @@ TEST(HMACTest, Bytes) {
EXPECT_FALSE(hmac.VerifyTruncated(
data, base::make_span(calculated_hmac, kSHA256DigestSize / 2)));
}
TEST(HMACTest, OneShotSha1) {
// RFC 2202 test case 3:
std::vector<uint8_t> key(20, 0xaa);
std::vector<uint8_t> data(50, 0xdd);
std::vector<uint8_t> expected;
CHECK(base::HexStringToBytes("125d7342b9ac11cd91a39af48aa17b4f63f175d3",
&expected));
auto result = crypto::hmac::SignSha1(key, data);
EXPECT_EQ(base::as_byte_span(result), base::as_byte_span(expected));
EXPECT_TRUE(crypto::hmac::VerifySha1(key, data, result));
result[0] ^= 0x01;
EXPECT_FALSE(crypto::hmac::VerifySha1(key, data, result));
}
TEST(HMACTest, OneShotSha256) {
// RFC 4231 test case 3:
std::vector<uint8_t> key(20, 0xaa);
std::vector<uint8_t> data(50, 0xdd);
std::vector<uint8_t> expected;
CHECK(base::HexStringToBytes(
"773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe",
&expected));
auto result = crypto::hmac::SignSha256(key, data);
EXPECT_EQ(base::as_byte_span(result), base::as_byte_span(expected));
EXPECT_TRUE(crypto::hmac::VerifySha256(key, data, result));
result[0] ^= 0x01;
EXPECT_FALSE(crypto::hmac::VerifySha256(key, data, result));
}
TEST(HMACTest, OneShotSha512) {
// RFC 4231 test case 3:
std::vector<uint8_t> key(20, 0xaa);
std::vector<uint8_t> data(50, 0xdd);
std::vector<uint8_t> expected;
CHECK(base::HexStringToBytes(
"fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39"
"bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb",
&expected));
auto result = crypto::hmac::SignSha512(key, data);
EXPECT_EQ(base::as_byte_span(result), base::as_byte_span(expected));
EXPECT_TRUE(crypto::hmac::VerifySha512(key, data, result));
result[0] ^= 0x01;
EXPECT_FALSE(crypto::hmac::VerifySha512(key, data, result));
}
TEST(HMACTest, OneShotWrongLengthDies) {
std::array<uint8_t, 32> key;
std::array<uint8_t, 32> data;
std::array<uint8_t, 16> small_hmac;
std::array<uint8_t, 128> big_hmac;
EXPECT_DEATH_IF_SUPPORTED(crypto::hmac::Sign(crypto::hash::HashKind::kSha256,
key, data, small_hmac),
"size");
EXPECT_DEATH_IF_SUPPORTED(
crypto::hmac::Sign(crypto::hash::HashKind::kSha256, key, data, big_hmac),
"size");
EXPECT_DEATH_IF_SUPPORTED(
(void)crypto::hmac::Verify(crypto::hash::HashKind::kSha256, key, data,
small_hmac),
"size");
EXPECT_DEATH_IF_SUPPORTED(
(void)crypto::hmac::Verify(crypto::hash::HashKind::kSha256, key, data,
big_hmac),
"size");
}

@ -17,6 +17,9 @@ namespace crypto {
// A wrapper to calculate secure hashes incrementally, allowing to
// be used when the full input is not known in advance. The end result will the
// same as if we have the full input in advance.
//
// TODO(https://issues.chromium.org/issues/374310081): Move this into
// crypto/hash.h along with the oneshot functions.
class CRYPTO_EXPORT SecureHash {
public:
enum Algorithm {

@ -19,6 +19,9 @@ namespace crypto {
// These functions perform SHA-256 operations.
//
// Functions for SHA-384 and SHA-512 can be added when the need arises.
//
// Deprecated: use the interface in crypto/hash.h instead.
// TODO(https://issues.chromium.org/issues/374310081): Delete these.
static const size_t kSHA256Length = 32; // Length in bytes of a SHA-256 hash.