Introduction
In this tutorial, we'll implement the SHA-224 cryptographic hash function in Python. SHA-224 is a member of the SHA-2 family of hash functions, producing a 224-bit (28-byte) hash value. It's essentially SHA-256 with different initialization vectors and truncated output.
By the end of this tutorial, you'll have:
- A clear understanding of how SHA-224 works
- Knowledge of Python's built-in cryptographic libraries
- Your own custom SHA-224 implementation
- Insights into performance optimization and security considerations
hashlib
library, which provides optimized, well-tested implementations of cryptographic hash functions.
Understanding SHA-224
Before diving into implementation, let's understand what makes SHA-224 unique:
- Relationship to SHA-256: SHA-224 is derived from SHA-256 with different initialization values and truncated output (224 bits instead of 256 bits).
- Security Properties: SHA-224 offers 112-bit security against collision attacks (compared to SHA-256's 128-bit security level).
- Process Overview: Like other SHA-2 variants, SHA-224 processes messages in 512-bit blocks and maintains an internal state that's updated through multiple rounds of computation.
At a high level, the SHA-224 algorithm follows these steps:
- Initialize internal state with specific values
- Pad the message to a multiple of 512 bits
- Process the message in 512-bit chunks through 64 rounds of computation
- Return the final 224-bit digest

The basic flow of the SHA-224 algorithm
Python's Built-in Libraries
Python's standard library includes the hashlib
module, which provides an easy-to-use interface for many cryptographic hash functions, including SHA-224:
import hashlib
# Create a SHA-224 hash object
hash_object = hashlib.sha224()
# Update the hash object with the data to be hashed
hash_object.update(b'Hello, world!')
# Get the hexadecimal digest
hex_digest = hash_object.hexdigest()
print(f"SHA-224 hash: {hex_digest}")
# Get the binary digest
binary_digest = hash_object.digest()
print(f"Digest length: {len(binary_digest)} bytes") # Should be 28 bytes (224 bits)
The hashlib
implementation is highly optimized and uses C extensions under the hood. It supports:
- Incremental Updates: You can update the hash object with new data repeatedly
- Different Output Formats: Get the digest in hexadecimal or binary form
- Thread Safety: Hash objects can be used safely across threads
import hashlib
hash_object = hashlib.sha224()
# Add data incrementally
hash_object.update(b'Part 1 of the message. ')
hash_object.update(b'Part 2 of the message. ')
hash_object.update(b'Part 3 of the message.')
# This is equivalent to hashing the entire message at once
print(hash_object.hexdigest())
# Verification
verification = hashlib.sha224(b'Part 1 of the message. Part 2 of the message. Part 3 of the message.').hexdigest()
print(verification)
# The results should match
assert hash_object.hexdigest() == verification
hashlib
module also supports other hash functions like MD5, SHA-1, SHA-256, SHA-384, and SHA-512. You can check which algorithms are available on your system using hashlib.algorithms_available
.
Building a Custom Implementation
Now, let's implement SHA-224 from scratch in Python for educational purposes. We'll break this down into steps, following the official NIST FIPS 180-4 specification.
Step 1: Setting Up Constants
SHA-224 requires several constants for its operation, including initialization vectors and round constants:
class SHA224:
"""A pure Python implementation of the SHA-224 hash algorithm.
Based on the NIST FIPS 180-4 standard.
"""
# Initial hash values for SHA-224
# These are the first 32 bits of the fractional parts of the square
# roots of the first 8 prime numbers (2, 3, 5, 7, 11, 13, 17, 19)
# Unique to SHA-224 compared to SHA-256
initial_hash_values = [
0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939,
0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4
]
# SHA-224 and SHA-256 use the same round constants
# First 32 bits of the fractional parts of the cube roots of the first 64 prime numbers
round_constants = [
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
]
These constants are specific to the SHA-224 algorithm and are defined in the NIST standard. The initial hash values are what differentiate SHA-224 from SHA-256 (along with the truncated output).
Step 2: Helper Functions
Next, we'll implement the bitwise operations used in the SHA-224 algorithm. These functions operate on 32-bit words:
def _rotr(x, n):
"""Perform a right circular rotation on a 32-bit word.
Args:
x: The 32-bit integer to rotate
n: The number of bits to rotate by
Returns:
The rotated 32-bit integer
"""
return ((x >> n) | (x << (32 - n))) & 0xFFFFFFFF
def _ch(x, y, z):
"""SHA-224/256 Ch function.
For each bit position i, Ch(x,y,z) = (x_i AND y_i) XOR ((NOT x_i) AND z_i)
"""
return (x & y) ^ (~x & z)
def _maj(x, y, z):
"""SHA-224/256 Maj function.
For each bit position i, Maj(x,y,z) = (x_i AND y_i) XOR (x_i AND z_i) XOR (y_i AND z_i)
"""
return (x & y) ^ (x & z) ^ (y & z)
def _sigma0(x):
"""SHA-224/256 sigma_0 function.
ROTR_2(x) XOR ROTR_13(x) XOR ROTR_22(x)
"""
return _rotr(x, 2) ^ _rotr(x, 13) ^ _rotr(x, 22)
def _sigma1(x):
"""SHA-224/256 sigma_1 function.
ROTR_6(x) XOR ROTR_11(x) XOR ROTR_25(x)
"""
return _rotr(x, 6) ^ _rotr(x, 11) ^ _rotr(x, 25)
def _gamma0(x):
"""SHA-224/256 gamma_0 function.
ROTR_7(x) XOR ROTR_18(x) XOR SHR_3(x)
"""
return _rotr(x, 7) ^ _rotr(x, 18) ^ (x >> 3)
def _gamma1(x):
"""SHA-224/256 gamma_1 function.
ROTR_17(x) XOR ROTR_19(x) XOR SHR_10(x)
"""
return _rotr(x, 17) ^ _rotr(x, 19) ^ (x >> 10)
These functions perform the core operations of the SHA-224 algorithm, including bitwise rotations, shifts, and logical operations.
&
(AND), |
(OR), ^
(XOR), ~
(NOT), <<
(left shift), and >>
(right shift) operators are fundamental to the SHA-224 algorithm. The & 0xFFFFFFFF
ensures that the result is a 32-bit integer.
Step 3: Message Padding
SHA-224 requires that the input message be padded to a multiple of 512 bits. Let's implement the padding function:
def _pad_message(message):
"""Pad the message according to SHA-224/256 padding rules.
The message is padded as follows:
1. Append a '1' bit to the message
2. Append k '0' bits, where k is the smallest non-negative solution to:
(message_length + 1 + k + 64) % 512 = 0
3. Append the message length as a 64-bit big-endian integer
Args:
message: The message as bytes
Returns:
The padded message as bytes
"""
message_length_bits = len(message) * 8
# Append the bit '1' to the message
padded_message = bytearray(message) + b'\x80'
# Append '0' bits until the message length (in bits) + 1 + k + 64 is a multiple of 512
# Since we're working with bytes, we need (message_length % 64) to be 56
padding_length = 64 - ((len(padded_message) + 8) % 64)
if padding_length == 64:
padding_length = 0
padded_message += b'\x00' * padding_length
# Append the original message length as a 64-bit big-endian integer
padded_message += message_length_bits.to_bytes(8, byteorder='big')
return padded_message
This padding ensures that the message length is a multiple of 512 bits (64 bytes) as required by the algorithm. The padding includes:
- A '1' bit (represented by the byte 0x80, which is 10000000 in binary)
- Zero or more '0' bits
- The original message length as a 64-bit big-endian integer

The message padding process for SHA-224
Step 4: Message Processing
Now, let's implement the core message processing function that will process each 512-bit (64-byte) block of the padded message:
def _process_block(block, hash_values):
"""Process a single 512-bit block according to the SHA-224/256 algorithm.
Args:
block: A 64-byte block of data
hash_values: The current hash values (8 32-bit integers)
Returns:
Updated hash values after processing the block
"""
# Create the message schedule (64 32-bit words)
w = [0] * 64
# Copy the block's 16 words into the first 16 words of the message schedule
for i in range(16):
w[i] = int.from_bytes(block[i*4:i*4+4], byteorder='big')
# Extend the message schedule
for i in range(16, 64):
w[i] = (_gamma1(w[i-2]) + w[i-7] + _gamma0(w[i-15]) + w[i-16]) & 0xFFFFFFFF
# Initialize working variables with current hash values
a, b, c, d, e, f, g, h = hash_values
# Main loop
for i in range(64):
t1 = (h + _sigma1(e) + _ch(e, f, g) + self.round_constants[i] + w[i]) & 0xFFFFFFFF
t2 = (_sigma0(a) + _maj(a, b, c)) & 0xFFFFFFFF
h = g
g = f
f = e
e = (d + t1) & 0xFFFFFFFF
d = c
c = b
b = a
a = (t1 + t2) & 0xFFFFFFFF
# Compute intermediate hash values
hash_values[0] = (hash_values[0] + a) & 0xFFFFFFFF
hash_values[1] = (hash_values[1] + b) & 0xFFFFFFFF
hash_values[2] = (hash_values[2] + c) & 0xFFFFFFFF
hash_values[3] = (hash_values[3] + d) & 0xFFFFFFFF
hash_values[4] = (hash_values[4] + e) & 0xFFFFFFFF
hash_values[5] = (hash_values[5] + f) & 0xFFFFFFFF
hash_values[6] = (hash_values[6] + g) & 0xFFFFFFFF
hash_values[7] = (hash_values[7] + h) & 0xFFFFFFFF
return hash_values
This function performs the core of the SHA-224 algorithm:
- It prepares a message schedule (W) of 64 32-bit words
- It initializes working variables with the current hash values
- It performs 64 rounds of computation, updating the working variables in each round
- It adds the final working variables to the hash values

The SHA-224 compression function for a single block
Step 5: Putting It All Together
Finally, let's implement the main SHA-224 class that ties everything together:
class SHA224:
"""A pure Python implementation of the SHA-224 hash algorithm.
Based on the NIST FIPS 180-4 standard.
"""
# Initial hash values for SHA-224
initial_hash_values = [
0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939,
0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4
]
# SHA-224 and SHA-256 use the same round constants
round_constants = [
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
]
def __init__(self):
"""Initialize a new SHA-224 hash object."""
# Initialize hash values
self.hash_values = self.initial_hash_values.copy()
# Initialize message buffer
self.buffer = bytearray()
# Initialize message length (in bits)
self.message_length = 0
@staticmethod
def _rotr(x, n):
"""Perform a right circular rotation on a 32-bit word."""
return ((x >> n) | (x << (32 - n))) & 0xFFFFFFFF
@staticmethod
def _ch(x, y, z):
"""SHA-224/256 Ch function."""
return (x & y) ^ (~x & z)
@staticmethod
def _maj(x, y, z):
"""SHA-224/256 Maj function."""
return (x & y) ^ (x & z) ^ (y & z)
@staticmethod
def _sigma0(x):
"""SHA-224/256 sigma_0 function."""
return SHA224._rotr(x, 2) ^ SHA224._rotr(x, 13) ^ SHA224._rotr(x, 22)
@staticmethod
def _sigma1(x):
"""SHA-224/256 sigma_1 function."""
return SHA224._rotr(x, 6) ^ SHA224._rotr(x, 11) ^ SHA224._rotr(x, 25)
@staticmethod
def _gamma0(x):
"""SHA-224/256 gamma_0 function."""
return SHA224._rotr(x, 7) ^ SHA224._rotr(x, 18) ^ (x >> 3)
@staticmethod
def _gamma1(x):
"""SHA-224/256 gamma_1 function."""
return SHA224._rotr(x, 17) ^ SHA224._rotr(x, 19) ^ (x >> 10)
def update(self, message):
"""Update the hash object with the message.
Args:
message: The message bytes to hash
"""
if isinstance(message, str):
message = message.encode('utf-8')
# Update message length
self.message_length += len(message) * 8
# Add the new message to the buffer
self.buffer.extend(message)
# Process complete blocks
while len(self.buffer) >= 64:
self._process_block(self.buffer[:64])
self.buffer = self.buffer[64:]
def _process_block(self, block):
"""Process a single 512-bit block according to the SHA-224/256 algorithm."""
# Create the message schedule (64 32-bit words)
w = [0] * 64
# Copy the block's 16 words into the first 16 words of the message schedule
for i in range(16):
w[i] = int.from_bytes(block[i*4:i*4+4], byteorder='big')
# Extend the message schedule
for i in range(16, 64):
w[i] = (SHA224._gamma1(w[i-2]) + w[i-7] + SHA224._gamma0(w[i-15]) + w[i-16]) & 0xFFFFFFFF
# Initialize working variables with current hash values
a, b, c, d, e, f, g, h = self.hash_values
# Main loop
for i in range(64):
t1 = (h + SHA224._sigma1(e) + SHA224._ch(e, f, g) + self.round_constants[i] + w[i]) & 0xFFFFFFFF
t2 = (SHA224._sigma0(a) + SHA224._maj(a, b, c)) & 0xFFFFFFFF
h = g
g = f
f = e
e = (d + t1) & 0xFFFFFFFF
d = c
c = b
b = a
a = (t1 + t2) & 0xFFFFFFFF
# Compute intermediate hash values
self.hash_values[0] = (self.hash_values[0] + a) & 0xFFFFFFFF
self.hash_values[1] = (self.hash_values[1] + b) & 0xFFFFFFFF
self.hash_values[2] = (self.hash_values[2] + c) & 0xFFFFFFFF
self.hash_values[3] = (self.hash_values[3] + d) & 0xFFFFFFFF
self.hash_values[4] = (self.hash_values[4] + e) & 0xFFFFFFFF
self.hash_values[5] = (self.hash_values[5] + f) & 0xFFFFFFFF
self.hash_values[6] = (self.hash_values[6] + g) & 0xFFFFFFFF
self.hash_values[7] = (self.hash_values[7] + h) & 0xFFFFFFFF
def digest(self):
"""Return the digest of the data passed to update() so far.
Returns:
The 224-bit digest as a bytes object
"""
# Create a copy of the current state
current_hash = SHA224()
current_hash.hash_values = self.hash_values.copy()
current_hash.buffer = self.buffer.copy()
current_hash.message_length = self.message_length
# Pad the message
message_len_bytes = current_hash.message_length.to_bytes(8, byteorder='big')
# Append the bit '1' to the message
current_hash.buffer.append(0x80)
# Append '0' bits until the message length (in bits) + 1 + k + 64 is a multiple of 512
# Since we're working with bytes, we need (buffer_length % 64) to be 56
padding_length = 56 - (len(current_hash.buffer) % 64)
if padding_length <= 0:
padding_length += 64
current_hash.buffer.extend([0] * padding_length)
# Append the original message length as a 64-bit big-endian integer
current_hash.buffer.extend(message_len_bytes)
# Process the remaining blocks
for i in range(0, len(current_hash.buffer), 64):
current_hash._process_block(current_hash.buffer[i:i+64])
# Concatenate the first 7 hash values (224 bits)
# SHA-224 only uses 7 out of the 8 hash values (truncates the result)
result = bytearray()
for i in range(7):
result.extend(current_hash.hash_values[i].to_bytes(4, byteorder='big'))
return bytes(result)
def hexdigest(self):
"""Return the digest of the data passed to update() so far as a hexadecimal string.
Returns:
The 224-bit digest as a hexadecimal string
"""
return self.digest().hex()
@classmethod
def hash(cls, message):
"""Convenience method to compute the SHA-224 digest of a message in one step.
Args:
message: The message to hash (string or bytes)
Returns:
The 224-bit digest as a bytes object
"""
sha = cls()
sha.update(message)
return sha.digest()
@classmethod
def hexhash(cls, message):
"""Convenience method to compute the SHA-224 hexadecimal digest of a message in one step.
Args:
message: The message to hash (string or bytes)
Returns:
The 224-bit digest as a hexadecimal string
"""
sha = cls()
sha.update(message)
return sha.hexdigest()
This implementation provides a complete SHA-224 hasher with an interface similar to Python's built-in hashlib
module. It includes:
- An
update()
method for adding data incrementally - A
digest()
method for getting the binary digest - A
hexdigest()
method for getting the hexadecimal digest - Convenience classmethods
hash()
andhexhash()
for one-step hashing
Note that we only use the first 7 of the 8 hash values for the final digest, which is what gives SHA-224 its 224-bit output (7 * 32 = 224 bits).
Testing and Verification
Let's test our implementation against known test vectors to ensure it's working correctly:
import hashlib
def test_sha224():
"""Test the SHA-224 implementation against the standard library."""
test_cases = [
b"",
b"abc",
b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
b"a" * 1000000
]
for i, test_case in enumerate(test_cases):
# Our implementation
custom_sha = SHA224()
custom_sha.update(test_case)
custom_result = custom_sha.hexdigest()
# Standard library implementation
std_result = hashlib.sha224(test_case).hexdigest()
# Compare results
assert custom_result == std_result, f"Test case {i} failed: {custom_result} != {std_result}"
print(f"Test case {i} passed: {custom_result}")
print("All test cases passed!")
# Run the tests
test_sha224()
These test cases include:
- An empty string
- A simple string ("abc")
- A complex string with various characters
- A long string (one million 'a' characters) to test performance with large inputs
The expected outputs for these test cases are:
Empty string:
d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f
"abc":
23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7
"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq":
75388b16512776cc5dba5da1fd890150b0c6455cb4f58b1952522525
One million "a" characters:
20794655980c91d8bbb4c1ea97618a4bf03f42581948b2ee4ee7ad67
hashlib
implementation.
Performance Considerations
Our pure Python implementation is significantly slower than the built-in hashlib
implementation, which uses C extensions. Let's compare the performance:
import hashlib
import time
def benchmark():
"""Compare the performance of our implementation with the standard library."""
test_data = b"a" * 10000 # 10KB of data
iterations = 100
# Benchmark custom implementation
start_time = time.time()
for _ in range(iterations):
custom_sha = SHA224()
custom_sha.update(test_data)
custom_sha.hexdigest()
custom_time = time.time() - start_time
# Benchmark standard library
start_time = time.time()
for _ in range(iterations):
std_sha = hashlib.sha224()
std_sha.update(test_data)
std_sha.hexdigest()
std_time = time.time() - start_time
print(f"Custom implementation: {custom_time:.4f} seconds for {iterations} iterations")
print(f"Standard library: {std_time:.4f} seconds for {iterations} iterations")
print(f"The standard library is {custom_time/std_time:.1f}x faster")
# Run the benchmark
benchmark()
The built-in implementation is typically 50-100x faster than our pure Python implementation, depending on the system and Python version.
Here are some ways to optimize our implementation if needed:
- Use NumPy: Utilizing NumPy arrays for the internal operations can improve performance
- Cython: Converting the implementation to Cython can provide significant speedups
- PyPy: Running the implementation with PyPy instead of CPython can improve performance
- Parallelization: For large messages, processing multiple blocks in parallel could improve performance
import numpy as np
# Use NumPy for the message schedule
def _process_block_numpy(self, block):
"""Process a single 512-bit block using NumPy for better performance."""
# Create the message schedule (64 32-bit words) as a NumPy array
w = np.zeros(64, dtype=np.uint32)
# Copy the block's 16 words into the first 16 words of the message schedule
for i in range(16):
w[i] = int.from_bytes(block[i*4:i*4+4], byteorder='big')
# Extend the message schedule using NumPy operations
for i in range(16, 64):
w[i] = (SHA224._gamma1(w[i-2]) + w[i-7] + SHA224._gamma0(w[i-15]) + w[i-16]) & 0xFFFFFFFF
# Rest of the function remains the same...
# ...
hashlib
implementation, which is well-tested, optimized, and regularly updated with security patches. Our implementation is for educational purposes only.
Security Considerations
When implementing cryptographic algorithms, security is paramount. Here are some important considerations:
Timing Attacks
Our implementation is not designed to be resistant to timing attacks. In particular, comparisons of hash values should be done in constant time to prevent timing side-channel attacks.
import hmac
def constant_time_compare(a, b):
"""Compare two strings/bytes in constant time."""
return hmac.compare_digest(a, b)
Memory Security
Sensitive data should be cleared from memory when no longer needed. Python doesn't provide direct memory management, so this is challenging to implement correctly.
Input Validation
Our implementation includes basic input validation, but in a production environment, more robust validation would be necessary.
Use Cases
SHA-224 is a general-purpose cryptographic hash function. For specific use cases like password hashing, specialized algorithms like Argon2, bcrypt, or PBKDF2 should be used instead.
import hashlib
import os
import binascii
def hash_password(password):
"""Securely hash a password using PBKDF2-SHA224."""
# Generate a random salt
salt = os.urandom(16)
# Use PBKDF2 with SHA-224 as the hash function
# 100,000 iterations is a reasonable default as of 2025
iterations = 100000
# Derive a key using PBKDF2
derived_key = hashlib.pbkdf2_hmac('sha224', password.encode(), salt, iterations)
# Format salt:iterations:key
return binascii.hexlify(salt).decode() + ':' + str(iterations) + ':' + binascii.hexlify(derived_key).decode()
def verify_password(stored_password, provided_password):
"""Verify a password against a stored hash."""
# Extract components
salt_hex, iterations_str, key_hex = stored_password.split(':')
salt = binascii.unhexlify(salt_hex)
iterations = int(iterations_str)
stored_key = binascii.unhexlify(key_hex)
# Derive the key from the provided password
derived_key = hashlib.pbkdf2_hmac('sha224', provided_password.encode(), salt, iterations)
# Compare in constant time
return hmac.compare_digest(stored_key, derived_key)
Conclusion
In this tutorial, we've implemented the SHA-224 hash function from scratch in Python. We've learned:
- How SHA-224 works internally, including its padding, processing, and finalization steps
- How to use Python's built-in
hashlib
module for efficient SHA-224 hashing - How to create a custom implementation with an API similar to the standard library
- Performance and security considerations for cryptographic hash functions
Remember, while implementing cryptographic algorithms is educational, for production use, always rely on well-tested libraries like hashlib
.
Next Steps
Now that you've learned how to implement SHA-224 in Python, consider exploring:
- Implementing HMAC-SHA224 for message authentication
- Building a file integrity checker using SHA-224
- Implementing secure password storage with SHA-224 and key stretching
- Implementing SHA-224 in Java for comparison