Skip to content

Instantly share code, notes, and snippets.

@DvdKhl
Last active June 3, 2016 20:30
Show Gist options
  • Save DvdKhl/8559321 to your computer and use it in GitHub Desktop.
Save DvdKhl/8559321 to your computer and use it in GitHub Desktop.
JavaScript File Hasher ( asm.js )
//Copyright (c) 2013, DvdKhl
//All rights reserved.
//
//Redistribution and use in source and binary forms, with or without modification,
//are permitted provided that the following conditions are met:
//
// Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// Redistributions in binary form must reproduce the above copyright notice, this
// list of conditions and the following disclaimer in the documentation and/or
// other materials provided with the distribution.
//
//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
function ed2kasm(stdlib, foreign, heap) {
"use asm";
var A0 = 0x67452301;
var B0 = 0xEFCDAB89;
var C0 = 0x98BADCFE;
var D0 = 0x10325476;
var processedBytes = 0;
var heapOffset = 0;
var data = new Uint32Array(heap);
var dataUInt8 = new Uint8Array(heap);
var state = new Uint32Array(heap, heapOffset += 9500 * 1024, 4);
var blockState = new Uint32Array(heap, heapOffset += 16, 4);
var tailInt8 = new Uint8Array(heap, heapOffset += 16, 128);
var tailInt32 = new Uint32Array(heap, heapOffset, 32);
var blockHashCount = 0;
var blockHashBufferLength = 0;
var blockHashBufferUInt32 = new Uint32Array(heap, heapOffset += 128, 16);
var blockHashBufferUInt8 = new Uint8Array(heap, heapOffset, 64);
function initialize() {
state[0] = A0;
state[1] = B0;
state[2] = C0;
state[3] = D0;
processedBytes = 0;
blockHashBufferLength = 0;
blockHashCount = 0;
for(var i = 0; i < 32; i++) tailInt32[i] = 0;
}
function hashCore(length) {
var offset = 0;
processedBytes += length;
blockState[0] = A0;
blockState[1] = B0;
blockState[2] = C0;
blockState[3] = D0;
var remainingLength = length;
while(remainingLength >= 64) {
transformMd4Block(blockState, data, offset);
remainingLength -= 64;
offset += 16;
}
if(remainingLength != 0) {
for(var i = 0; i < remainingLength; i++) {
tailInt8[i] = dataUInt8[length - remainingLength + i];
}
for(var i = remainingLength; i < 128; i++) {
tailInt8[i] = 0;
}
}
var padding = remainingLength < 56 ? 56 : 120;
var bits = length << 3;
tailInt8[remainingLength] = 0x80;
tailInt8[padding + 0] = (bits ) & 0xFF;
tailInt8[padding + 1] = (bits >> 8) & 0xFF;
tailInt8[padding + 2] = (bits >> 16) & 0xFF;
tailInt8[padding + 3] = (bits >> 24) & 0xFF;
tailInt8[padding + 4] = ((bits >> 31) >> 1) & 0xFF;
tailInt8[padding + 5] = ((bits >> 31) >> 9) & 0xFF;
tailInt8[padding + 6] = ((bits >> 31) >> 17) & 0xFF;
tailInt8[padding + 7] = ((bits >> 31) >> 25) & 0xFF;
transformMd4Block(blockState, tailInt32, 0);
if(remainingLength >= 56) transformMd4Block(blockState, tailInt32, 16);
blockHashBufferUInt32.set(blockState, blockHashBufferLength);
blockHashBufferLength += 4;
blockHashCount++;
if(blockHashBufferLength == 16) {
transformMd4Block(state, blockHashBufferUInt32, 0);
blockHashBufferLength = 0;
console.log(state);
}
}
function transformMd4Block(state, data, offset) {
var aa = state[0], bb = state[1], cc = state[2], dd = state[3];
aa += ((bb & cc) | ((~bb) & dd)) + data[offset + 0x0];
aa = aa << 3 | aa >>> -3;
dd += ((aa & bb) | ((~aa) & cc)) + data[offset + 0x1];
dd = dd << 7 | dd >>> -7;
cc += ((dd & aa) | ((~dd) & bb)) + data[offset + 0x2];
cc = cc << 11 | cc >>> -11;
bb += ((cc & dd) | ((~cc) & aa)) + data[offset + 0x3];
bb = bb << 19 | bb >>> -19;
aa += ((bb & cc) | ((~bb) & dd)) + data[offset + 0x4];
aa = aa << 3 | aa >>> -3;
dd += ((aa & bb) | ((~aa) & cc)) + data[offset + 0x5];
dd = dd << 7 | dd >>> -7;
cc += ((dd & aa) | ((~dd) & bb)) + data[offset + 0x6];
cc = cc << 11 | cc >>> -11;
bb += ((cc & dd) | ((~cc) & aa)) + data[offset + 0x7];
bb = bb << 19 | bb >>> -19;
aa += ((bb & cc) | ((~bb) & dd)) + data[offset + 0x8];
aa = aa << 3 | aa >>> -3;
dd += ((aa & bb) | ((~aa) & cc)) + data[offset + 0x9];
dd = dd << 7 | dd >>> -7;
cc += ((dd & aa) | ((~dd) & bb)) + data[offset + 0xA];
cc = cc << 11 | cc >>> -11;
bb += ((cc & dd) | ((~cc) & aa)) + data[offset + 0xB];
bb = bb << 19 | bb >>> -19;
aa += ((bb & cc) | ((~bb) & dd)) + data[offset + 0xC];
aa = aa << 3 | aa >>> -3;
dd += ((aa & bb) | ((~aa) & cc)) + data[offset + 0xD];
dd = dd << 7 | dd >>> -7;
cc += ((dd & aa) | ((~dd) & bb)) + data[offset + 0xE];
cc = cc << 11 | cc >>> -11;
bb += ((cc & dd) | ((~cc) & aa)) + data[offset + 0xF];
bb = bb << 19 | bb >>> -19;
aa += ((bb & (cc | dd)) | (cc & dd)) + data[offset + 0x0] + 0x5A827999;
aa = aa << 3 | aa >>> -3;
dd += ((aa & (bb | cc)) | (bb & cc)) + data[offset + 0x4] + 0x5A827999;
dd = dd << 5 | dd >>> -5;
cc += ((dd & (aa | bb)) | (aa & bb)) + data[offset + 0x8] + 0x5A827999;
cc = cc << 9 | cc >>> -9;
bb += ((cc & (dd | aa)) | (dd & aa)) + data[offset + 0xC] + 0x5A827999;
bb = bb << 13 | bb >>> -13;
aa += ((bb & (cc | dd)) | (cc & dd)) + data[offset + 0x1] + 0x5A827999;
aa = aa << 3 | aa >>> -3;
dd += ((aa & (bb | cc)) | (bb & cc)) + data[offset + 0x5] + 0x5A827999;
dd = dd << 5 | dd >>> -5;
cc += ((dd & (aa | bb)) | (aa & bb)) + data[offset + 0x9] + 0x5A827999;
cc = cc << 9 | cc >>> -9;
bb += ((cc & (dd | aa)) | (dd & aa)) + data[offset + 0xD] + 0x5A827999;
bb = bb << 13 | bb >>> -13;
aa += ((bb & (cc | dd)) | (cc & dd)) + data[offset + 0x2] + 0x5A827999;
aa = aa << 3 | aa >>> -3;
dd += ((aa & (bb | cc)) | (bb & cc)) + data[offset + 0x6] + 0x5A827999;
dd = dd << 5 | dd >>> -5;
cc += ((dd & (aa | bb)) | (aa & bb)) + data[offset + 0xA] + 0x5A827999;
cc = cc << 9 | cc >>> -9;
bb += ((cc & (dd | aa)) | (dd & aa)) + data[offset + 0xE] + 0x5A827999;
bb = bb << 13 | bb >>> -13;
aa += ((bb & (cc | dd)) | (cc & dd)) + data[offset + 0x3] + 0x5A827999;
aa = aa << 3 | aa >>> -3;
dd += ((aa & (bb | cc)) | (bb & cc)) + data[offset + 0x7] + 0x5A827999;
dd = dd << 5 | dd >>> -5;
cc += ((dd & (aa | bb)) | (aa & bb)) + data[offset + 0xB] + 0x5A827999;
cc = cc << 9 | cc >>> -9;
bb += ((cc & (dd | aa)) | (dd & aa)) + data[offset + 0xF] + 0x5A827999;
bb = bb << 13 | bb >>> -13;
aa += (bb ^ cc ^ dd) + data[offset + 0x0] + 0x6ED9EBA1;
aa = aa << 3 | aa >>> -3;
dd += (aa ^ bb ^ cc) + data[offset + 0x8] + 0x6ED9EBA1;
dd = dd << 9 | dd >>> -9;
cc += (dd ^ aa ^ bb) + data[offset + 0x4] + 0x6ED9EBA1;
cc = cc << 11 | cc >>> -11;
bb += (cc ^ dd ^ aa) + data[offset + 0xC] + 0x6ED9EBA1;
bb = bb << 15 | bb >>> -15;
aa += (bb ^ cc ^ dd) + data[offset + 0x2] + 0x6ED9EBA1;
aa = aa << 3 | aa >>> -3;
dd += (aa ^ bb ^ cc) + data[offset + 0xA] + 0x6ED9EBA1;
dd = dd << 9 | dd >>> -9;
cc += (dd ^ aa ^ bb) + data[offset + 0x6] + 0x6ED9EBA1;
cc = cc << 11 | cc >>> -11;
bb += (cc ^ dd ^ aa) + data[offset + 0xE] + 0x6ED9EBA1;
bb = bb << 15 | bb >>> -15;
aa += (bb ^ cc ^ dd) + data[offset + 0x1] + 0x6ED9EBA1;
aa = aa << 3 | aa >>> -3;
dd += (aa ^ bb ^ cc) + data[offset + 0x9] + 0x6ED9EBA1;
dd = dd << 9 | dd >>> -9;
cc += (dd ^ aa ^ bb) + data[offset + 0x5] + 0x6ED9EBA1;
cc = cc << 11 | cc >>> -11;
bb += (cc ^ dd ^ aa) + data[offset + 0xD] + 0x6ED9EBA1;
bb = bb << 15 | bb >>> -15;
aa += (bb ^ cc ^ dd) + data[offset + 0x3] + 0x6ED9EBA1;
aa = aa << 3 | aa >>> -3;
dd += (aa ^ bb ^ cc) + data[offset + 0xB] + 0x6ED9EBA1;
dd = dd << 9 | dd >>> -9;
cc += (dd ^ aa ^ bb) + data[offset + 0x7] + 0x6ED9EBA1;
cc = cc << 11 | cc >>> -11;
bb += (cc ^ dd ^ aa) + data[offset + 0xF] + 0x6ED9EBA1;
bb = bb << 15 | bb >>> -15;
state[0] += aa;
state[1] += bb;
state[2] += cc;
state[3] += dd;
}
function hashFinal() {
var bits = (blockHashCount * 16) << 3;
for(var i = blockHashBufferLength; i < 16; i+=4) {
blockHashBufferUInt32[i + 0] = 0;
blockHashBufferUInt32[i + 1] = 0;
blockHashBufferUInt32[i + 2] = 0;
blockHashBufferUInt32[i + 3] = 0;
}
blockHashBufferUInt8[blockHashBufferLength * 4] = 0x80;
blockHashBufferUInt8[56] = (bits ) & 0xFF;
blockHashBufferUInt8[57] = (bits >> 8) & 0xFF;
blockHashBufferUInt8[58] = (bits >> 16) & 0xFF;
blockHashBufferUInt8[59] = (bits >> 24) & 0xFF;
blockHashBufferUInt8[60] = ((bits >> 31) >> 1) & 0xFF;
blockHashBufferUInt8[61] = ((bits >> 31) >> 9) & 0xFF;
blockHashBufferUInt8[62] = ((bits >> 31) >> 17) & 0xFF;
blockHashBufferUInt8[63] = ((bits >> 31) >> 25) & 0xFF;
transformMd4Block(state, blockHashBufferUInt32, 0);
}
return {
initialize: initialize,
hashCore: hashCore,
hashFinal: hashFinal
};
}
var CHUNKSIZE = 9500 * 1024;
function ed2k() {
var heap = new ArrayBuffer(CHUNKSIZE + 16 + 16 + 128 + 64);
this.asmBlock = new Uint8Array(heap);
this.asm = ed2kasm(null, {}, heap);
this.processedBytes = 0;
this.blueHash = new Uint8Array(16);
this.redHash = new Uint8Array(16);
this.blueIsRed = true;
}
ed2k.prototype.initialize = function() {
this.asm.initialize();
this.processedBytes = 0;
this.blueIsRed = true;
};
ed2k.prototype.transformBlock = function(data) {
var dv = new Uint8Array(data);
this.asmBlock.set(dv, 0);
this.asm.hashCore(data.byteLength);
this.processedBytes += data.byteLength;
};
function copyHash(dst, src, offset) {
for(var i = 0; i < 16; i++) dst[i] = src[CHUNKSIZE + offset + i];
}
ed2k.prototype.transformFinalBlock = function(data) {
if(data && data.byteLength != 0) throw "transformFinalBlock data.byteLength != 0 not supported";
this.asm.hashFinal();
if(this.processedBytes < CHUNKSIZE) {
copyHash(this.blueHash, this.asmBlock, 16);
} else if(this.processedBytes == CHUNKSIZE) {
copyHash(this.blueHash, this.asmBlock, 16);
this.asm.hashCore(0);
copyHash(this.redHash, this.asmBlock, 0);
this.blueIsRed = false;
} else {
copyHash(this.blueHash, this.asmBlock, 0);
if((this.processedBytes % CHUNKSIZE) == 0) {
this.asm.hashCore(0);
copyHash(this.redHash, this.asmBlock, 0);
this.blueIsRed = false;
}
}
}
ed2k.prototype.getHash = function() { return this.blueHash; }
ed2k.prototype.getAltHash = function() { return this.redHash ? this.redHash : this.blueHash; }
ed2k.prototype.hasAltHash = function() { return this.blueIsRed; }
ed2k.prototype.getChunkHash = function() {
copyHash(this.redHash, this.asmBlock, 16);
return this.redHash;
}
<!DOCTYPE html>
<html>
<head>
<title>JS Ed2k File Hasher</title>
</head>
<body>
<input type="file" id="files" name="file" />
<div id="readBytesButtons"><button>Hash File</button></div>
<div>Read Speed: <span id="readspeed"/>mb/s</div>
<div>Hash Speed: <span id="hashspeed"/>mb/s</div>
<div>Final Hash: <span id="hash"/></div>
<div>Chunk Hashes:<br> <pre id="chunkhashes"/></div>
<script src="reader.js"></script>
<script>
document.querySelector('#readBytesButtons').addEventListener('click', function(evt) {
if (evt.target.tagName.toLowerCase() == 'button') {
processFile(document.getElementById('files').files[0], 0);
}
}, false);
</script>
</body>
</html>
//Copyright (c) 2013, DvdKhl
//All rights reserved.
//
//Redistribution and use in source and binary forms, with or without modification,
//are permitted provided that the following conditions are met:
//
// Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// Redistributions in binary form must reproduce the above copyright notice, this
// list of conditions and the following disclaimer in the documentation and/or
// other materials provided with the distribution.
//
//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
var startedOn = 0;
var bytesRead = 0;
var pendingChunks = 0;
var bytesProcessed = 0;
var CHUNKSIZE = 9500 * 1024;
var callback;
var worker = new Worker("readerChunk.js");
worker.addEventListener('message', function(e) {
var data = e.data;
switch (data.cmd) {
case 'finalHash':
document.getElementById('hash').textContent = data.hash;
break;
case 'chunkDone':
bytesProcessed += data.chunkSize;
var speed = (bytesProcessed / (window.performance.now() - startedOn)) / 1024;
document.getElementById('hashspeed').textContent = speed;
pendingChunks--;
if(callback && pendingChunks < 4) {
callback();
callback = null;
}
};
}, false);
function processFile(file, chunkIndex) {
if(chunkIndex == 0) {
worker.postMessage({'cmd': 'initialize'});
bytesRead = 0;
pendingChunks = 0;
bytesProcessed = 0;
startedOn = window.performance.now();
document.getElementById('chunkhashes').textContent = "";
}
var first = chunkIndex * CHUNKSIZE;
var last = first + Math.min(file.size - first, CHUNKSIZE);
var isLastChunk = last == file.size;
var reader = new FileReader();
reader.onloadend = function(evt) {
if (evt.target.readyState == FileReader.DONE) { // DONE == 2
bytesRead += evt.target.result.byteLength;
var speed = (bytesRead / (window.performance.now() - startedOn)) / 1024;
document.getElementById('readspeed').textContent = speed;
if(!isLastChunk) {
if(pendingChunks > 8) {
callback = function() { processFile(file, chunkIndex + 1); };
} else {
processFile(file, chunkIndex + 1);
}
}
pendingChunks++;
worker.postMessage({'cmd': 'hashChunk', 'chunk': evt.target.result, 'isLastChunk': isLastChunk}, [evt.target.result]);
}
}
var blob = file.slice(first, last);
reader.readAsArrayBuffer(blob);
}
//Copyright (c) 2013, DvdKhl
//All rights reserved.
//
//Redistribution and use in source and binary forms, with or without modification,
//are permitted provided that the following conditions are met:
//
// Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// Redistributions in binary form must reproduce the above copyright notice, this
// list of conditions and the following disclaimer in the documentation and/or
// other materials provided with the distribution.
//
//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
importScripts('ed2k.js');
var hasher = new ed2k();
function processChunk(data, isLastChunk) {
//for(i=0; i<10000000; i++) var sf = {};
hasher.transformBlock(data);
self.postMessage({'cmd': 'chunkDone', 'chunkSize': data.byteLength});
if(isLastChunk) {
hasher.transformFinalBlock();
var hex = "";
var hash = hasher.getHash();
for (var i = 0; i < hash.length; i++) hex += ("00" + hash[i].toString(16)).slice(-2);
self.postMessage({'cmd': 'finalHash', 'hash': hex});
}
}
self.addEventListener('message', function(e) {
var data = e.data;
switch (data.cmd) {
case 'initialize':
hasher.initialize();
break;
case 'hashChunk':
processChunk(data.chunk, data.isLastChunk);
break;
};
}, false);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment