Skip to content

Commit

Permalink
Initial design for export of Merkle-Damgard hash state
Browse files Browse the repository at this point in the history
This commit provides a building block to implement HMAC with precomputed
keys from #1574.

It adds internal functions to export the state of SHA-256 and use such
exported state to initialize a SHA-256 context.

Once the design is reviewed and approved, we will similar functions to
the other Merkle-Damgard hash functions (MD4, MD5, SHA-1, SHA-2).
  • Loading branch information
Fabrice Benhamouda committed May 6, 2024
1 parent 33637ff commit fa25f37
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 1 deletion.
43 changes: 43 additions & 0 deletions crypto/digest_extra/digest_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -476,3 +476,46 @@ TEST(DigestTest, TransformBlocks) {

EXPECT_TRUE(0 == OPENSSL_memcmp(ctx1.h, ctx2.h, sizeof(ctx1.h)));
}

// FIXME: Need to implement this for all hash functions used by HMAC
TEST(DigestTest, InitAndGetStateSHA256) {
const size_t nb_blocks = 10;
const size_t block_size = SHA256_CBLOCK;
uint8_t data[block_size * nb_blocks];
for (size_t i = 0; i < sizeof(data); i++) {
data[i] = i*3;
}

// SHA-256

// Compute the hash of the data for the baseline
SHA256_CTX ctx1;
EXPECT_TRUE(SHA256_Init(&ctx1));
EXPECT_TRUE(SHA256_Update(&ctx1, data, sizeof(data)));
uint8_t hash1[SHA256_DIGEST_LENGTH];
EXPECT_TRUE(SHA256_Final(hash1, &ctx1));

// Compute it by stopping in the middle, getting the state, and restoring it
SHA256_CTX ctx2;
EXPECT_TRUE(SHA256_Init(&ctx2));
EXPECT_TRUE(SHA256_Update(&ctx2, data, 1));
uint8_t state_h[SHA256_DIGEST_LENGTH];
uint64_t state_n;
// we should not be able to export the state before a full block
EXPECT_FALSE(SHA256_get_state(&ctx2, state_h, &state_n));
// finish 2 blocks
EXPECT_TRUE(SHA256_Update(&ctx2, data+1, 2*block_size-1));
// now we should be able to export the state
EXPECT_TRUE(SHA256_get_state(&ctx2, state_h, &state_n));
// check that state_n corresponds to 2 blocks
EXPECT_EQ(2*block_size*8, state_n);

// and we continue on a fresh new context
SHA256_CTX ctx3;
EXPECT_TRUE(SHA256_Init_from_state(&ctx3, state_h, state_n));
EXPECT_TRUE(SHA256_Update(&ctx2, data+2*block_size, (nb_blocks-2)*block_size));
uint8_t hash2[SHA256_DIGEST_LENGTH];
EXPECT_TRUE(SHA256_Final(hash2, &ctx2));

EXPECT_EQ(Bytes(hash1), Bytes(hash2));
}
31 changes: 31 additions & 0 deletions crypto/fipsmodule/sha/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@
extern "C" {
#endif

// Internal SHA2 constants

// SHA224_CHAINING_LENGTH is the chaining length in bytes of SHA-224
// It corresponds to the length in bytes of the h part of the state
#define SHA224_CHAINING_LENGTH 32

// SHA256_CHAINING_LENGTH is the chaining length in bytes of SHA-256
// It corresponds to the length in bytes of the h part of the state
#define SHA256_CHAINING_LENGTH 32


// SHA3 constants, from NIST FIPS202.
// https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf
#define SHA3_ROWS 5
Expand Down Expand Up @@ -92,6 +103,26 @@ void sha512_block_data_order(uint64_t *state, const uint8_t *in,
#define KECCAK1600_ASM
#endif

// SHA256_Init_from_state is a low-level function that initializes |sha| with a
// custom state. |h| is the hash state in big endian. |n| is the number of bits
// processed at this point. It must be a multiple of |SHA256_CBLOCK*8|.
// It returns one on success and zero on programmer error.
// External users should never use directly this function.
OPENSSL_EXPORT int SHA256_Init_from_state(
SHA256_CTX *sha, const uint8_t h[SHA256_CHAINING_LENGTH], uint64_t n);

// SHA256_get_state is a low-level function that exports the hash state in big
// endian into |out_n| and the number of bits processed at this point in
// |out_n|. SHA256_Final must not have been called before (otherwise results
// are not guaranteed). Furthermore, the number of bytes processed by
// SHA256_Update must be a multiple of the block length |SHA256_CBLOCK|
// (otherwise it fails). It returns one on success and zero on programmer
// error.
// External users should never use directly this function.
OPENSSL_EXPORT int SHA256_get_state(SHA256_CTX *ctx,
uint8_t out_h[SHA256_CHAINING_LENGTH],
uint64_t *out_n);

// SHA3_224 writes the digest of |len| bytes from |data| to |out| and returns |out|.
// There must be at least |SHA3_224_DIGEST_LENGTH| bytes of space in |out|.
// On failure |SHA3_224| returns NULL.
Expand Down
64 changes: 63 additions & 1 deletion crypto/fipsmodule/sha/sha256.c
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,41 @@ int SHA256_Init(SHA256_CTX *sha) {
return 1;
}

OPENSSL_STATIC_ASSERT(SHA256_CHAINING_LENGTH==SHA224_CHAINING_LENGTH,
sha256_and_sha224_have_same_chaining_length)

// sha256_init_from_state_impl is the implementation of
// SHA256_Init_from_state and SHA224_Init_from_state
// Note that the state h is always SHA256_CHAINING_LENGTH-byte long
static int sha256_init_from_state_impl(SHA256_CTX *sha, int md_len,
const uint8_t h[SHA256_CHAINING_LENGTH],
uint64_t n) {
if(n % ((uint64_t) SHA256_CBLOCK * 8) != 0) {
// n is not a multiple of the block size in bits, so it fails
return 0;
}

OPENSSL_memset(sha, 0, sizeof(SHA256_CTX));
sha->md_len = md_len;

const size_t out_words = SHA256_CHAINING_LENGTH / 4;
for (size_t i = 0; i < out_words; i++) {
sha->h[i] = CRYPTO_load_u32_be(h);
h += 4;
}

sha->Nh = n >> 32;
sha->Nl = n & 0xffffffff;

return 1;
}

int SHA256_Init_from_state(SHA256_CTX *sha,
const uint8_t h[SHA256_CHAINING_LENGTH],
uint64_t n) {
return sha256_init_from_state_impl(sha, SHA256_DIGEST_LENGTH, h, n);
}

uint8_t *SHA224(const uint8_t *data, size_t len,
uint8_t out[SHA224_DIGEST_LENGTH]) {
// We have to verify that all the SHA services actually succeed before
Expand Down Expand Up @@ -163,14 +198,41 @@ static int sha256_final_impl(uint8_t *out, size_t md_len, SHA256_CTX *c) {
return 1;
}

int SHA256_Final(uint8_t out[SHA256_DIGEST_LENGTH], SHA256_CTX *c) {
int SHA256_Final(uint8_t out[SHA256_CHAINING_LENGTH], SHA256_CTX *c) {
return sha256_final_impl(out, SHA256_DIGEST_LENGTH, c);
}

int SHA224_Final(uint8_t out[SHA224_DIGEST_LENGTH], SHA256_CTX *ctx) {
return sha256_final_impl(out, SHA224_DIGEST_LENGTH, ctx);
}

// sha256_get_state_impl is the implementation of
// SHA256_get_state and SHA224_get_state
// Note that the state out_h is always SHA256_CHAINING_LENGTH-byte long
static int sha256_get_state_impl(SHA256_CTX *ctx,
uint8_t out_h[SHA256_CHAINING_LENGTH],
uint64_t *out_n) {
if (ctx->Nl % ((uint64_t)SHA256_CBLOCK * 8) != 0) {
// ctx->Nl is not a multiple of the block size in bits, so it fails
return 0;
}

const size_t out_words = SHA256_CHAINING_LENGTH / 4;
for (size_t i = 0; i < out_words; i++) {
CRYPTO_store_u32_be(out_h, ctx->h[i]);
out_h += 4;
}

*out_n = (((uint64_t)ctx->Nh) << 32) + ctx->Nl;

return 1;
}

int SHA256_get_state(SHA256_CTX *ctx, uint8_t out_h[SHA256_CHAINING_LENGTH],
uint64_t *out_n) {
return sha256_get_state_impl(ctx, out_h, out_n);
}

#ifndef SHA256_ASM
static const uint32_t K256[64] = {
0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL, 0x3956c25bUL,
Expand Down
6 changes: 6 additions & 0 deletions include/openssl/digest.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,12 @@ OPENSSL_EXPORT int EVP_DigestUpdate(EVP_MD_CTX *ctx, const void *data,
// at least this much space.
#define EVP_MAX_MD_SIZE 64 // SHA-512 is the longest so far.

// EVP_MAX_MD_CHAINING_LENGTH is the largest chaining length supported, in
// bytes. This constant is only for Merkle-Damgard-based hashed functions
// like SHA-1, SHA-2, and MD5.
// This constant is only used internally by HMAC.
#define EVP_MAX_MD_CHAINING_LENGTH 64 // SHA-512 has the longest chaining length so far

// EVP_MAX_MD_BLOCK_SIZE is the largest digest block size supported, in
// bytes.
#define EVP_MAX_MD_BLOCK_SIZE 128 // SHA-512 is the longest so far.
Expand Down

0 comments on commit fa25f37

Please sign in to comment.