// The island's node:crypto shim, differentially against Node: the // hashing slice byte-exact (md5/sha1/sha256 digests and HMACs ride the // same C implementations the static lowerings use), pbkdf2 over the HMAC // bridge, Node's exact error shapes for unknown digests / finalized // hashes / timingSafeEqual length mismatches, and shape-only checks for // the randomness surface (values are random; lengths and types are not). import crypto, { createHash, createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto"; export async function report() { const lines = []; const push = (label, v) => lines.push(label + " " + v); const errOf = (fn) => { try { fn(); return "no-throw"; } catch (e) { return (e.code || e.name) + ":" + e.message; } }; const unsupportedAlgorithm = () => "nope"; for (const alg of ["md5", "sha1", "sha256"]) { push(alg + "-empty:", createHash(alg).digest("hex")); push(alg + "-abc:", createHash(alg).update("abc").digest("hex")); push(alg + "-chunks:", createHash(alg).update("a").update(Buffer.from("bc")).digest("base64")); push(alg + "-unicode:", createHash(alg).update("héllo ☃").digest("hex")); push(alg + "-hexin:", createHash(alg).update("deadbeef", "hex").digest("hex")); push(alg + "-hmac:", createHmac(alg, "key").update("The quick brown fox").digest("hex")); push(alg + "-hmac-longkey:", createHmac(alg, "k".repeat(100)).update("data").digest("hex")); } const h = createHash("sha256").update("ab"); const h2 = h.copy(); h.update("X"); h2.update("c"); push("copy:", h2.digest("hex")); const fin = createHash("sha1"); fin.digest(); push("digest-twice:", errOf(() => fin.digest("hex"))); push("bad-alg:", errOf(() => createHash(unsupportedAlgorithm()))); push("one-shot:", crypto.hash("sha256", "abc") + "|" + crypto.hash("sha1", Buffer.from("abc"), "base64")); push("digest-buffer:", Buffer.isBuffer(createHash("sha1").update("x").digest())); push("tse:", timingSafeEqual(Buffer.from([1, 2]), Buffer.from([1, 2])) + "|" + timingSafeEqual(Buffer.from([1, 2]), Buffer.from([1, 3]))); push("tse-len:", errOf(() => timingSafeEqual(Buffer.from([1]), Buffer.from([1, 2])))); const rb = randomBytes(16); push("random-shape:", rb.length + "|" + Buffer.isBuffer(rb) + "|" + randomBytes(0).length); push("random-neg:", errOf(() => randomBytes(-1))); const rf = Buffer.alloc(8); const rfr = crypto.randomFillSync(rf, 2, 4); push("randomfill:", (rfr === rf) + "|" + rf.readUInt16LE(0) + "|" + rf.readUInt16LE(6)); let intsOk = true; for (let i = 0; i < 100; i++) { const v = crypto.randomInt(3, 6); if (v < 3 || v >= 6 || !Number.isInteger(v)) intsOk = false; } push("randomint:", intsOk + "|" + errOf(() => crypto.randomInt(5, 2))); push("uuid:", /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(randomUUID())); push("pbkdf2:", crypto.pbkdf2Sync("password", "salt", 3, 20, "sha256").toString("hex")); push("pbkdf2-sha1:", crypto.pbkdf2Sync("pw", "st", 2, 25, "sha1").toString("hex")); const cb = await new Promise((res) => crypto.pbkdf2("password", "salt", 2, 8, "sha256", (err, dk) => res((err === null ? "null" : String(err)) + ":" + dk.toString("hex")))); push("pbkdf2-cb:", cb); let callbackSync = true; const rbPending = new Promise((res) => randomBytes(4, (err, b) => res(callbackSync + ":" + (err === null ? "null" : String(err)) + ":" + b.length))); callbackSync = false; const rbcb = await rbPending; push("randombytes-cb:", rbcb); push("hashes:", ["md5", "sha1", "sha256"].every((a) => crypto.getHashes().includes(a))); push("constants:", crypto.constants.RSA_PKCS1_PADDING + "," + crypto.constants.RSA_PKCS1_OAEP_PADDING); return lines.join("\n"); }