Skip to content

Instantly share code, notes, and snippets.

@bellbind
Last active July 15, 2017 11:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bellbind/18ff65781512151f53ed6eb093bac0f9 to your computer and use it in GitHub Desktop.
Save bellbind/18ff65781512151f53ed6eb093bac0f9 to your computer and use it in GitHub Desktop.
[JavaScript]tiny pure ES6 SHA1 implementation (in 50 lines)
#!/usr/bin/env node
"use strict";
const fs = require("fs");
const hash = require(".");
function main() {
if (process.argv[2] === "-c") {
const names = process.argv.slice(3);
check(names).then(({fails, sumerror}) => {
if (fails > 0) {
console.error(`${process.argv[1]}: WARNING: ${fails
} computed checksum${fails > 1 ? "s" : ""} did NOT match`);
}
process.exit(fails > 0 || sumerror > 0 ? 1 : 0);
}).catch(err => {
console.error(err);
process.exit(1);
});
} else {
const names = process.argv.slice(2);
generate(names).then(_ => process.exit(0)).catch(err => {
console.error(err);
process.exit(1);
});
}
}
main();
function digestFile(name) {
return new Promise((resolve, reject) => {
const buf = fs.readFileSync(name);
resolve(Buffer.from(hash(buf)));
});
}
async function generate(names) {
for (const name of names) {
const digest = await digestFile(name);
console.log(`${digest.toString("hex")} ${name}`);
}
}
async function check(names) {
let fails = 0, sumerror = 0;
for (const name of names) {
try {
const r = await checkFile(name);
fails += r.fails;
} catch (err) {
sumerror++;
console.error(`${process.argv[1]}: ${name
}: No such file or directory`);
}
}
return {fails, sumerror};
}
async function checkFile(name) {
const sumReg = /^\s*([\da-fA-F]{40})[\t ][ ]?(.+)$/;
let fails = 0;
for (const sum of fs.readFileSync(name, "utf8").split(/\n/)) {
const match = sum.match(sumReg);
if (!match) continue;
const expected = Buffer.from(match[1], "hex");
try {
const digest = await digestFile(match[2]);
if (digest.compare(expected) === 0) {
console.log(`${match[2]}: OK`);
} else {
fails++;
console.log(`${match[2]}: FAILED`);
}
} catch (err) {
fails++;
console.log(`${match[2]}: FAILED open or read`);
}
}
return {fails};
}
{"name": "sha1", "version": "1.0.0", "main": "sha1.js", "bin": "main.js"}
"use strict";
const hs = Array.from(Array(16), (_, i) => i.toString(16));
const hsr = hs.slice().reverse();
const h2s = hs.join("").match(/../g), h2sr = hsr.join("").match(/../g);
const h2mix = hs.map((h, i) => `${hsr[i]}${h}`);
const hseq = h2s.concat(h2sr, h2mix).map(hex => parseInt(hex, 16));
const H = new Uint32Array(Uint8Array.from(hseq.slice(0, 20)).buffer);
const K = Uint32Array.from(
[2, 3, 5, 10], v => Math.floor(Math.sqrt(v) * (2 ** 30)));
const F = [
(b, c, d) => ((b & c) | ((~b >>> 0) & d)) >>> 0,
(b, c, d) => b ^ c ^ d,
(b, c, d) => (b & c) | (b & d) | (c & d),
(b, c, d) => b ^ c ^ d,
];
function rotl(v, n) {
return ((v << n) | (v >>> (32 - n))) >>> 0;
}
function sha1(buffer) {
const u8a = ArrayBuffer.isView(buffer) ?
new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength) :
new Uint8Array(buffer);
const total = Math.ceil((u8a.length + 9) / 64) * 64;
const chunks = new Uint8Array(total);
chunks.set(u8a);
chunks.fill(0, u8a.length);
chunks[u8a.length] = 0x80;
const lenbuf = new DataView(chunks.buffer, total - 8);
const low = u8a.length % (1 << 29);
const high = (u8a.length - low) / (1 << 29);
lenbuf.setUint32(0, high, false);
lenbuf.setUint32(4, low << 3, false);
const hash = H.slice();
const w = new Uint32Array(80);
for (let offs = 0; offs < total; offs += 64) {
const chunk = new DataView(chunks.buffer, offs, 64);
for (let i = 0; i < 16; i++) w[i] = chunk.getUint32(i * 4, false);
for (let i = 16; i < 80; i++) {
w[i] = rotl(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);
}
let [a, b, c, d, e] = hash;
for (let s = 0; s < 4; s++) {
for (let i = s * 20, end = i + 20; i < end; i++) {
const ne = rotl(a, 5) + F[s](b, c, d) + e + K[s] + w[i];
[a, b, c, d, e] = [ne >>> 0, a, rotl(b, 30), c, d];
}
}
hash[0] += a; hash[1] += b; hash[2] += c; hash[3] += d; hash[4] += e;
}
const digest = new DataView(new ArrayBuffer(20));
hash.forEach((v, i) => digest.setUint32(i * 4, v, false));
return digest.buffer;
}
// export for node.js
if (typeof require === "function" && require.main === module) {
const fs = require("fs");
process.argv.slice(2).forEach(name => {
const buf = fs.readFileSync(name);
const hex = Buffer.from(sha1(buf)).toString("hex");
console.log(`${hex} ${name}`);
});
} else if (typeof module !== "undefined") {
module.exports = sha1;
}
@bellbind
Copy link
Author

With npx:

$ npx https://gist.github.com/bellbind/18ff65781512151f53ed6eb093bac0f9 /bin/bash |tee sum
87e8300692a35010af8478978fab1ac4888114e1 /bin/bash
$ npx https://gist.github.com/bellbind/18ff65781512151f53ed6eb093bac0f9 -c sum
/bin/bash: OK

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment