コンテンツにスキップ

SHA256ハッシュを算出する同期関数(WebCrypto APIを使わない)

TypeScript playground

sha256-sync.ts
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) {
// メッセージスケジュールの初期化
for (let i = 0; i < 16; i++) {
w[i] = view.getUint32(offset + i * 4, false);
}
// メッセージスケジュールの拡張
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]);
// メインループ
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;
}
// 状態の更新
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: TextEncoder
function 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;
// 64ビットのメッセージ長を8バイトで表現
const view = new DataView(padded.buffer);
const lengthBytes = paddedLength - 8;
// 上位32ビット(ほとんどの場合0)
view.setUint32(lengthBytes, Math.floor(bitLength / MAX_UINT32_PLUS_1), false);
// 下位32ビット
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;
}
console.log(sha256("Hello, World!"));