Mohamed Mahmoud Elbaba
Level 1
Hash Family and How Hash Functions Work
Overview of Hash Functions
Hash functions are a critical part of computer science, cryptography, and data
integrity. They are algorithms that take an input (or 'message') and return a fixed-
size string of bytes. This output is typically a digest that appears random and is
unique to the input.
Characteristics of Hash Functions
1. Deterministic: A specific input will always produce the same output hash.
2. Fixed Length Output: Regardless of input size, the output is always the same
length (e.g., 256 bits for SHA-256).
3. Pre-image Resistance: It should be computationally infeasible to reverse-
engineer the original input from its hash.
4. Small Changes in Input Produce Drastically Different Outputs: A minor change
in the input should result in a vastly different hash.
5. Collision Resistance: It should be infeasible to find two different inputs that
produce the same hash output.
Common Hash Functions and Families
1. MD5 (Message Digest Algorithm 5): Produces a 128-bit hash. It is now
considered broken and unsuitable for further use due to vulnerabilities.
2. SHA-1 (Secure Hash Algorithm 1): Produces a 160-bit hash. Also considered
broken and deprecated due to vulnerabilities.
3. SHA-2 Family: Includes SHA-224, SHA-256, SHA-384, and SHA-512. These are
widely used and considered secure.
4. SHA-3 Family: The latest member of the Secure Hash Algorithm family,
designed to provide an alternative to SHA-2 with different internal structures.
How Hash Functions Work
Hash functions process data through a series of steps to produce a hash value.
Here's a general overview of the process:
1. Initialization: Set up initial parameters, constants, and variables.
2. Preprocessing: The input is padded to ensure its length is a multiple of a certain
block size. Padding typically involves adding a single '1' bit followed by a series of
'0' bits and ending with a length value of the original input.
3. Processing: The input data is divided into fixed-size blocks, and each block is
processed iteratively through a series of mathematical transformations. These
transformations involve bitwise operations, modular additions, and other
arithmetic operations.
4. Finalization: After all blocks are processed, the final hash value is produced.
#include <iostream>
#include <vector>
Example: SHA-512 #include <cstdint>
#include <cstring>
SHA-512 follows a similar process to SHA-256 but works
class SHA512 {
with 64-bit words and produces a 512-bit hash.
public:
1. Initialization: Define constants and initial hash SHA512() { reset(); }
values derived from the first 64 bits of the fractional void update(const uint8_t* data, size_t length);
parts of the square roots of the first 8 prime numbers. void finalize(uint8_t hash[64]);
void reset();
2. Preprocessing: Pad the input so that its length is
private:
congruent to 896 modulo 1024. Append the original
void transform(const uint8_t* data);
length of the message as a 128-bit integer.
static const uint64_t k[80];
3. Processing: Divide the input into 1024-bit blocks. For uint64_t state[8];
each block: std::vector<uint8_t> buffer;
uint64_t bit_length;
• Expand the block into 80 words of 64 bits each.
};
• Initialize working variables to the current hash value. // Constants
const uint64_t SHA512::k[80] = {
• Perform 80 rounds of processing using a specific
// Initialize the SHA-512 constants here
sequence of bitwise operations and modular additions.
};
• Update the hash values with the results of the // Initial hash values
rounds. const uint64_t initial_state[8] = {
0x6a09e667f3bcc908, 0xbb67ae8584caa73b,
4. Finalization: Concatenate the hash values to
0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1,
produce the final 512-bit hash.
0x510e527fade682d1, 0x9b05688c2b3e6c1f,
0x1f83d9abfb41bd6b, 0x5be0cd19137e2179
};
// Other methods omitted for brevity...
// You need to implement update, transform, and finalize methods
int main() {
SHA512 sha512;
std::string input = "Hello, World!";
sha512.update(reinterpret_cast<const uint8_t*>(input.c_str()), input.size());
uint8_t hash[64];
sha512.finalize(hash);
// Output the hash
for (int i = 0; i < 64; ++i) {
printf("%02x", hash[i]);
std::cout << std::endl;
return 0;
}
#include <iostream>
Example: SHA-256 #include <vector>
#include <cstdint>
SHA-256 is a member of the SHA-2 family, producing a #include <cstring>
256-bit hash. Here's a detailed look at its processing class SHA256 {
steps: public:
1. Initialization: Define constants and initial hash SHA256() { reset(); }
values. These are derived from the first 32 bits of the void update(const uint8_t* data, size_t length);
fractional parts of the square roots of the first 8 prime void finalize(uint8_t hash[32]);
numbers. void reset();
private:
2. Preprocessing: Pad the input so that its length is void transform(const uint8_t* data);
congruent to 448 modulo 512. Append the original static const uint32_t k[64];
length of the message as a 64-bit integer. uint32_t state[8];
3. Processing: Divide the input into 512-bit blocks. For std::vector<uint8_t> buffer;
each block: uint64_t bit_length;
};
• Expand the block into 64 words of 32 bits each. // Constants
• Initialize working variables to the current hash value. const uint32_t SHA256::k[64] = {
// ... (Initialize the SHA-256 constants here)
• Perform 64 rounds of processing using a specific };
sequence of bitwise operations and modular additions. // Initial hash values
• Update the hash values with the results of the const uint32_t initial_state[8] = {
rounds. 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
4. Finalization: Concatenate the hash values to };
produce the final 256-bit hash. // Other methods omitted for brevity...
// You need to implement update, transform, and finalize methods
int main() {
SHA256 sha256;
std::string input = "Hello, World!";
sha256.update(reinterpret_cast<const uint8_t*>(input.c_str()), input.size());
uint8_t hash[32];
sha256.finalize(hash);
// Output the hash
for (int i = 0; i < 32; ++i) {
printf("%02x", hash[i]);
std::cout << std::endl;
return 0;