Last active
July 15, 2017 11:37
-
-
Save bellbind/18ff65781512151f53ed6eb093bac0f9 to your computer and use it in GitHub Desktop.
[JavaScript]tiny pure ES6 SHA1 implementation (in 50 lines)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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}; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{"name": "sha1", "version": "1.0.0", "main": "sha1.js", "bin": "main.js"} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"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; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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