L’API WebCrypto est disponible dans de nombreux environnements, mais il existe des cas comme certaines implémentations Web Workers où l’objet crypto ne peut pas être utilisé. Voici une implémentation en JavaScript pur pour de telles situations. Cependant, il faut s’attendre à ce que les performances soient inférieures à celles des implémentations natives.
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) { // Initialisation du planning de messages for (let i = 0; i < 16; i++) { w[i] = view.getUint32(offset + i * 4, false); }
// Extension du planning de messages 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]);
// Boucle principale 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; }
// Mise à jour de l'état 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;}Exemple d’utilisation
console.log(sha256("Hello, World!"));// dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986fAperçu de l’implémentation
- Padding : Aligne le message en blocs de 512 bits
- Planning de messages : Génère un planning de 64 mots
- Boucle principale : Met à jour huit variables de travail
- Valeur de hachage : Produit le hachage final de 256 bits
Cette implémentation est destinée à des fins d’apprentissage et à des environnements contraints. Pour une utilisation normale, l’API WebCrypto est recommandée.
hsb.horse