Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions src/algorithms/cryptography/sha256/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# SHA-256 Hash Function

In cryptography, **SHA-256** (Secure Hash Algorithm 256-bit) is a patented cryptographic hash function that outputs a value that is 256 bits long. It is a member of the SHA-2 family of algorithms, designed by the NSA, and published in 2001 by the NIST as a U.S. Federal Information Processing Standard (FIPS).

## What is a Cryptographic Hash Function?

A cryptographic hash function is a mathematical algorithm that maps data of arbitrary size to a bit string of a fixed size (a "hash"). It is a one-way function, that is, a function which is practically infeasible to invert.

The ideal cryptographic hash function has five main properties:
* **Deterministic:** The same message always results in the same hash.
* **Pre-image resistant (one-way):** It should be computationally hard to reverse the process, i.e., to find any input that produces a given output hash.
* **Second pre-image resistant:** Given an input, it should be hard to find a different input with the same hash.
* **Collision resistant:** It should be hard to find two different messages with the same hash.
* **Avalanche effect:** A small change to the message (e.g., changing a single bit) should change the hash value so extensively that the new hash value appears uncorrelated with the old hash value.

## The Algorithm Explained

SHA-256 transforms a message of any length into a fixed-size 256-bit (32-byte) hash. This process involves several steps:

1. **Pre-processing (Padding):** The input message is padded to ensure its total length is a multiple of 512 bits. This involves appending a '1' bit, followed by a sequence of '0' bits, and finally, a 64-bit integer representing the original message's length.

2. **Initialization:** The algorithm starts with eight initial 32-bit integer hash values, labeled `H[0]` through `H[7]`. These are standard, pre-defined constants derived from the fractional parts of the square roots of the first eight prime numbers.

3. **Processing in Chunks:** The padded message is processed in 512-bit (64-byte) chunks. Each chunk is put through 64 rounds of computation.

4. **Compression Function:** For each chunk, the algorithm performs a series of complex logical operations. This includes creating a "message schedule" (`W`) from the chunk and then using it to manipulate a set of eight working variables (initialized with the current hash values). After 64 rounds, the working variables are added to the initial hash values to produce the new intermediate hash values.

5. **Final Hash:** After the last chunk has been processed, the final 256-bit hash value is the concatenation of the eight 32-bit hash values.

## Complexity

* **Time Complexity:** $O(L)$, where $L$ is the length of the message. The algorithm processes the input message in a single pass.
* **Space Complexity:** $O(1)$. The amount of memory required is constant and does not depend on the size of the input message.

## References

* [SHA-2 (SHA-256 Pseudocode Section) on Wikipedia](https://en.wikipedia.org/wiki/SHA-2#Pseudocode)
* [FIPS 180-4 Secure Hash Standard (Official PDF)](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf)
46 changes: 46 additions & 0 deletions src/algorithms/cryptography/sha256/__tests__/sha256.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import sha256 from '../sha256';

describe('sha256', () => {
it('should calculate the hash of an empty string correctly', () => {
// Test vector for an empty string from the NIST specification.
const input = '';
const expectedHash = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';
expect(sha256(input)).toBe(expectedHash);
});

it('should calculate the hash of a short string correctly', () => {
// Standard "abc" test vector.
const input = 'abc';
const expectedHash = 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad';
expect(sha256(input)).toBe(expectedHash);
});

it('should calculate the hash of a longer string correctly', () => {
// This string is longer than a single 512-bit block, testing the padding and chunking logic.
const input = 'The quick brown fox jumps over the lazy dog';
const expectedHash = 'd7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592';
expect(sha256(input)).toBe(expectedHash);
});

it('should demonstrate the avalanche effect', () => {
// A minor change to the input (adding a period) should result in a completely different hash.
const input1 = 'The quick brown fox jumps over the lazy dog';
const input2 = 'The quick brown fox jumps over the lazy dog.';

const hash1 = sha256(input1);
const hash2 = sha256(input2);

expect(hash1).not.toBe(hash2);

// Known correct hash for the second input string.
const expectedHash2 = 'ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c';
expect(hash2).toBe(expectedHash2);
});

it('should calculate the hash of a very long string correctly', () => {
// A string that requires multiple blocks.
const input = 'abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq';
const expectedHash = '248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1';
expect(sha256(input)).toBe(expectedHash);
});
});
96 changes: 96 additions & 0 deletions src/algorithms/cryptography/sha256/sha256.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* @fileoverview A pure JavaScript implementation of the SHA-256 cryptographic
* hash function, as defined in the FIPS 180-4 standard.
*
* NOTE: This implementation is for educational purposes to demonstrate the
* underlying algorithm. For production applications, it is strongly recommended
* to use the native Web Crypto API (`crypto.subtle.digest`) in browsers or the
* `crypto` module in Node.js, as they are faster and hardened against
* side-channel attacks.
*/

/**
* Hashes a string message using the SHA-256 algorithm.
*
* @param {string} message The message to hash.
* @returns {string} The 64-character hexadecimal representation of the hash.
*/
export default function sha256(message) {
// --- SHA-256 Constants (as defined in the FIPS 180-4 standard) ---
const K = [
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,
];

const H_INITIAL = [
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
];

// --- SHA-256 Helper Functions ---
const rotR = (x, n) => (x >>> n) | (x << (32 - n));
const Ch = (x, y, z) => (x & y) ^ (~x & z);
const Maj = (x, y, z) => (x & y) ^ (x & z) ^ (y & z);
const Sigma0 = (x) => rotR(x, 2) ^ rotR(x, 13) ^ rotR(x, 22);
const Sigma1 = (x) => rotR(x, 6) ^ rotR(x, 11) ^ rotR(x, 25);
const sigma0 = (x) => rotR(x, 7) ^ rotR(x, 18) ^ (x >>> 3);
const sigma1 = (x) => rotR(x, 17) ^ rotR(x, 19) ^ (x >>> 10);

// --- Pre-processing and Padding ---
const msgBytes = new TextEncoder().encode(message);

const msgLenBits = msgBytes.length * 8;
// Use two 32-bit integers to represent the 64-bit message length.
const highBits = Math.floor(msgLenBits / 0x100000000);
const lowBits = msgLenBits;

const k = (448 - ((msgBytes.length * 8 + 1) % 512) + 512) % 512;
const paddedLength = msgBytes.length + 1 + k / 8 + 8;
const M = new Uint8Array(paddedLength);
const view = new DataView(M.buffer);

M.set(msgBytes);
M[msgBytes.length] = 0x80;
view.setUint32(paddedLength - 8, highBits, false);
view.setUint32(paddedLength - 4, lowBits, false);

// --- Hash Computation ---
const hash = [...H_INITIAL];

for (let i = 0; i < M.length; i += 64) {
const chunkView = new DataView(M.buffer, i, 64);
const W = new Uint32Array(64);

for (let t = 0; t < 16; t += 1) {
W[t] = chunkView.getUint32(t * 4, false);
}
for (let t = 16; t < 64; t += 1) {
W[t] = (sigma1(W[t - 2]) + W[t - 7] + sigma0(W[t - 15]) + W[t - 16]) | 0;
}

let [a, b, c, d, e, f, g, h] = hash;

for (let t = 0; t < 64; t += 1) {
const T1 = (h + Sigma1(e) + Ch(e, f, g) + K[t] + W[t]) | 0;
const T2 = (Sigma0(a) + Maj(a, b, c)) | 0;
h = g; g = f; f = e; e = (d + T1) | 0;
d = c; c = b; b = a; a = (T1 + T2) | 0;
}

hash[0] = (hash[0] + a) | 0;
hash[1] = (hash[1] + b) | 0;
hash[2] = (hash[2] + c) | 0;
hash[3] = (hash[3] + d) | 0;
hash[4] = (hash[4] + e) | 0;
hash[5] = (hash[5] + f) | 0;
hash[6] = (hash[6] + g) | 0;
hash[7] = (hash[7] + h) | 0;
}

return hash.map((val) => (val >>> 0).toString(16).padStart(8, '0')).join('');
}