HMAC implementation for SHA256





HMAC (Hash Message Authentication Code) is a mechanism that combines a cryptographic hash function with a secret key. It ensures authenticity (the user knows it came from the right sender) and integrity (the user knows it wasn’t changed).

In real-world usage, every secure API call, crypto wallet, or payment processor you use today relies on HMAC. Without it, attackers could forge requests or alter data.

Let's start with the 9 primitives used in HMAC.

The 9 primitives used in HMAC

  1. check the size of the block (b bytes).
  2. a key (K).
  3. a fixed-size key (k0), identical to the block size.
  4. ipad (inner padding).
  5. opad (outer padding).
  6. XOR logic (to flip the bits).
  7. a message in bytes.
  8. appending.
  9. the hash function of choice (for this blog that'll be SHA256).

Now you know the 9 primitives used in HMAC, let's look at how the key is normalized to k0 so it matches the block-bytes (64 bytes for SHA256).

Normalizing k0 to b-bytes

First we make sure the key matches the right block-size (b-bytes) equaling 64 bytes or 512 bits, since 64 * 8 = 512. We use if else logic to normalize the key to the right size.

Here is the if else logic we use to normalize the key to variable k0.

  • If k is equal to b bytes, then k becomes k0
  • If k is less than b bytes, append all 0s to k until k is b bytes (for SHA256 if k is 23 bytes or 184 bits than append 41 bytes or 328 bits to k) then k becomes k0.
  • If k is greater than b-bytes hash k such that k becomes less than b-bytes and append all 0s to make k equal to b-bytes, then k becomes k0.

Let's look at Figure 1.0 to see how it looks in Rust:


pub fn get_right_block_size(k: &[u8]) -> Vec {
    let mut k0: Vec = k.to_vec();
    let mut key_length = k0.len();
    let block_size = 64;

    // k = k0 if key length == block_size.
    if key_length == block_size {
        return k0;
    };

    // Hash k0 and append 0s if key_length > block_size.
    if key_length > block_size {
        k0 = sha256(&k0).to_vec();
        let mut k_l = k0.len();
        while k_l < block_size {
            k0.push(0x00);
            k_l += 1;
        };
    };

    // Append all 0s if key_length < block_size.
    if key_length < block_size {
        let mut k_l = k.len();
        while k_l < block_size {
            k0.push(0x00);
            k_l += 1;
        };
    };

    k0

}
Figure 1.0: Rust code normalizing HMAC keys for SHA256

Now, depending on the size of the key, an if branch is chosen and the key is normalized to the right size.

When the key is normalized, we use it with ipad, opad, XOR, and the message to authenticate the message. Let's look at Authenticating the key so you can see how the process takes place.

Authenticating the key

To authenticate the key we use ipad (inner padding) which is an array of 64 times x'36' (HEX 36; 0011_0110) which is XOR'd with the normalized key, and stored in an array of 64 bytes with the updated HEX values after ipad ^ k0 is computed.

After updating the array with ipad ^ k0 we append the message to the updated array, and then ((ipad ^ k0) || msg) is hashed with SHA256, in equation it's: SHA256((ipad ^ k0) || msg).

For opad (outer padding) we repeat the same process, opad is an array that contains 64 times x'5c' (HEX 5c; 0101_1100) those hex values are XOR'd with the normalized key (k0), after k0 is authenticated with opad, it gets stored in an array with the updated HEX values after the computation opad ^ k0.

Let's look at the bigger picture in figure 1.1 where the HMAC flow is displayed.

            Key (K)
              |
       Normalize to blocksize
              ↓
            K0
              |
   ┌──────────┴──────────┐
   │                     │
K0 ⊕ ipad           K0 ⊕ opad
   │                     │
   + Message             + (Hash of left side)
   │                     │
   SHA256 ---------------┘
              ↓
         Final HMAC
Figure 1.1:

Now you've seen the flow of HMAC in figure 1.1., let's take a look at figure 1.2 where you'll see it programmed in Rust.


pub fn hmac(k: &[u8], m: &[u8]) -> [u8; 32] {
    let mut result: Vec = Vec::new();
    let mut ipad: Vec = vec![0x36u8; 64];
    let mut opad: Vec = vec![0x5cu8; 64];

    // Vector ipad[i] and opad[i] XORed with k[i].
    for i in 0..64 {
        ipad[i] ^= k[i];
        opad[i] ^= k[i];
    };

    let msg: Vec = m.to_vec();
    let msg_len = m.len();

    // ((K0 ^ ipad) || text)).
    let mut j = 0;
    while j < msg_len {
        ipad.push(msg[j]);
        j += 1;
    };

    // H((K0 ^ ipad) || text)).
    let sha = sha256(&ipad);

    // ((K0 ^ opad )|| H((K0 ^ ipad) || text))
    for k in 0..32 {
        opad.push(sha[k]);
    };

    // h((K0 ^ opad )|| H((K0 ^ ipad) || text)).
    sha256(&opad)

}
Figure 1.2: Inner and outer pad authentication process in Rust

After this process, we append the equation SHA256((ipad ^ K0) || msg) to (opad ^ K0) after that we have ((opad ^ k) || SHA256((ipad ^ k) || msg)).

When we append opad to ipad the equation is hashed again: SHA256((opad ^ k) || SHA256((ipad ^ k) || msg)), and the final HMAC is a 32-byte output (since SHA-256 always returns 256 bits).

In figure 1.3 you can read an edge test case as a visual example, programmed in Rust to verify correctness.


#[test]
fn brown_fox() {
    let k = get_right_block_size(b"key");
    let msg = b"The quick brown fox jumps over the lazy dog";

    let result = hmac(&k, msg);
    let expected = [
      0xf7, 0xbc, 0x83, 0xf4, 0x30, 0x53, 0x84, 0x24,
      0xb1, 0x32, 0x98, 0xe6, 0xaa, 0x6f, 0xb1, 0x43,
      0xef, 0x4d, 0x59, 0xa1, 0x49, 0x46, 0x17, 0x59,
      0x97, 0x47, 0x9d, 0xbc, 0x2d, 0x1a, 0x3c, 0xd8,
    ];

    assert_eq!((result), (expected));

}

Figure 1.3: Edge test verifying correctness of HMAC implementation

If you’d like a deeper dive into edge test cases, take a look at RFT Home this is the website where I got the tests.

That’s it. Now you know how HMAC authenticates your key with SHA256.

Closing

If you're into Systems Thinking, Clear Thinking, Rust, Crypto, and Philosophy, check out X/Twitter, here I’ll share thoughts and update when new blogs go live.

For the full Rust HMAC SHA256 implementation, check out my repo crypto-primitives to see (or clone) the repository.

If you're building in the crypto space and see areas for improvement, reach me at: l@lmpkessels.com or, send me a DM on X/Twitter.

Until the next one.