WebCrypto API is available in many environments, but there are cases like some Web Workers implementations where the crypto object cannot be used. This is a pure JavaScript implementation for such situations. However, you need to be prepared for performance to be inferior to native implementations.
Code
const K: Readonly<Uint32Array> = initUint32([ 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 MAX_UINT32_PLUS_1 = 0x100000000;const N_64 = 64;const N_512 = 512;
function sha256(message: Uint8Array | string) { const state = initUint32([ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, ]);
const data = isString(message) ? strToBuffer(message) : message; const blocks = preprocess(data); const w = initUint32(N_64); const view = new DataView(blocks.buffer); for (let offset = 0; offset < blocks.length; offset += N_64) { // Initialize message schedule for (let i = 0; i < 16; i++) { w[i] = view.getUint32(offset + i * 4, false); }
// Extend message schedule for (let i = 16; i < N_64; i++) { const im15 = w[i - 15]; const im2 = w[i - 2]; const im16 = w[i - 16]; const im7 = w[i - 7];
assertIsDefined(im15); assertIsDefined(im2); assertIsDefined(im16); assertIsDefined(im7);
const s0 = rightRotate(im15, 7) ^ rightRotate(im15, 18) ^ (im15 >>> 3);
const s1 = rightRotate(im2, 17) ^ rightRotate(im2, 19) ^ (im2 >>> 10); w[i] = (im16 + s0 + im7 + s1) >>> 0; }
let a = nonNull(state[0]); let b = nonNull(state[1]); let c = nonNull(state[2]); let d = nonNull(state[3]); let e = nonNull(state[4]); let f = nonNull(state[5]); let g = nonNull(state[6]); let h = nonNull(state[7]);
// Main loop for (let i = 0; i < N_64; i++) { const S1 = rightRotate(nonNull(e), 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25); const ch = (e & f) ^ (~e & g); const temp1 = (h + S1 + ch + nonNull(K[i]) + nonNull(w[i])) >>> 0; const S0 = rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22); const maj = (a & b) ^ (a & c) ^ (b & c); const temp2 = (S0 + maj) >>> 0;
h = g; g = f; f = e; e = (d + temp1) >>> 0; d = c; c = b; b = a; a = (temp1 + temp2) >>> 0; }
// Update state const current = state; state[0] = (nonNull(current[0]) + a) >>> 0; state[1] = (nonNull(current[1]) + b) >>> 0; state[2] = (nonNull(current[2]) + c) >>> 0; state[3] = (nonNull(current[3]) + d) >>> 0; state[4] = (nonNull(current[4]) + e) >>> 0; state[5] = (nonNull(current[5]) + f) >>> 0; state[6] = (nonNull(current[6]) + g) >>> 0; state[7] = (nonNull(current[7]) + h) >>> 0; }
const hash = new Uint8Array(32); const hashView = new DataView(hash.buffer); for (let i = 0; i < 8; i++) { hashView.setUint32(i * 4, nonNull(state[i]), false); }
return toHex(hash);}
function isString(arg: unknown): arg is string { return typeof arg === "string";}
let te: TextEncoderfunction strToBuffer(str: string): Uint8Array { if (!te) { te = new TextEncoder(); } return te.encode(str); }
function preprocess(data: Uint8Array): Uint8Array { const bitLength = data.length * 8; const paddingLength = (N_512 + 448 - ((bitLength + 1) % N_512)) % N_512; const paddedLength = Math.ceil((bitLength + 1 + paddingLength + N_64) / 8);
const padded = new Uint8Array(paddedLength); padded.set(data); padded[data.length] = 0x80; const view = new DataView(padded.buffer); const lengthBytes = paddedLength - 8;
view.setUint32(lengthBytes, Math.floor(bitLength / MAX_UINT32_PLUS_1), false); view.setUint32(lengthBytes + 4, bitLength % MAX_UINT32_PLUS_1, false);
return padded;}
function toHex(buffer: Uint8Array): string { return [...buffer].map((b) => b.toString(16).padStart(2, "0")).join("");}
function rightRotate(value: number, shift: number): number { return (value >>> shift) | (value << (32 - shift));}
function initUint32(length: number): Uint32Array;function initUint32(array: ArrayLike<number> | ArrayBufferLike): Uint32Array;function initUint32( buffer: ArrayBufferLike, byteOffset?: number, length?: number,): Uint32Array;function initUint32( arg1: number | ArrayLike<number> | ArrayBufferLike, arg2?: number, arg3?: number,): Uint32Array { return typeof arg1 === "number" ? new Uint32Array(arg1) : "byteLength" in arg1 ? new Uint32Array(arg1, arg2, arg3) : new Uint32Array(arg1);}
function assertIsDefined<T>(value: T): asserts value is NonNullable<T> { if (value === undefined || value === null) { throw new Error(`Expected 'value' to be defined, but received ${value}`); }}
function nonNull<T>(value: T): NonNullable<T> { assertIsDefined(value); return value;}Usage Example
console.log(sha256("Hello, World!"));// dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986fImplementation Overview
- Padding: Aligns message into 512-bit blocks
- Message Schedule: Generates a 64-word schedule
- Main Loop: Updates eight working variables
- Hash Value: Outputs the final 256-bit hash
This implementation is for learning purposes and constrained environments. For normal use, the WebCrypto API is recommended.
hsb.horse