<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css" />
</head>
<body class="workers">
<h1>Work! Work, my little web workers!</h1>
<div class="global-message"></div>
<script src="crypto.js"></script>
<script src="main.js"></script>
</body>
</html>
(function(w, d) {
var hashOptions = {
passphrase: '94713',
// Hashing salt
salt: 'secret-salt',
// Key stretching iterations
iterations: 64
},
hashToCrack = hash(hashOptions.passphrase, hashOptions.salt, hashOptions.iterations),
workerCount = 4,
passphraseLimit = 100000,
workers = [];
function toArray(obj) {
return [].map.call(obj, function(element) {
return element;
});
}
function splitNumIntoRanges(num, count) {
var inc = Math.ceil(num / count),
seq = 1,
chunks = [];
for (var i = 1; i < count; i++) {
chunks.push({
low: seq,
high: seq + inc
});
seq += inc;
}
chunks.push({
low: seq,
high: num + 1
});
return chunks;
}
function createWorkerMonitor(index) {
var element = d.createElement('section');
element.id = 'worker-' + index;
element.classList.add('worker');
element.innerHTML = '<header><span class="title">Worker ' + index + '</span></header><div class="data-element passphrase"><span class="label">Passphrase</span><span class="value"></span></div><div class="data-element hash"><span class="label">Hash</span><span class="value"></span></div><div class="data-element remaining"><span class="label">Combinations left</span><span class="value"></span></div>';
d.querySelector('body').appendChild(element);
}
function updateTextContent(selector, content) {
d.querySelector(selector).textContent = content;
}
updateTextContent('.global-message', 'Starting ' + workerCount +
' workers to brute force the SHA1 hash ' + hashToCrack + '.');
// Splitting the limit number into pieces and distribute equally along the
// workers.
splitNumIntoRanges(passphraseLimit, workerCount).forEach(function(range, index) {
var worker = new Worker('worker.js');
createWorkerMonitor(index);
worker.addEventListener('message', function(e) {
if (e.data.update) {
// On a update we update the data of the specific worker
updateTextContent('#worker-' + index + ' > .passphrase > .value', e.data.update.passphrase);
updateTextContent('#worker-' + index + ' > .hash > .value', e.data.update.hash);
updateTextContent('#worker-' + index + ' > .remaining > .value', e.data.update.remaining);
} else if (e.data.found) {
// If a worker found the correct hash we will set the global message and
// terminate all workers.
d.querySelector('#worker-' + index).classList.add('found');
updateTextContent('.global-message', 'Worker ' + index +
' found the passphrase ' + e.data.found.passphrase + ' within ' +
e.data.found.timeSpent + 'ms!');
// Terminate all workers
workers.forEach(function(w) {
// Worker.terminate() to interrupt the web worker
w.terminate();
// Add done class to all worker elements
toArray(d.querySelectorAll('.worker')).forEach(function(e) {
e.classList.add('done');
});
});
} else if (e.data.done) {
// If a worker is done before we found a result lets update the data and
// style.
updateTextContent('#worker-' + index + ' > .passphrase > .value', e.data.done.passphrase);
updateTextContent('#worker-' + index + ' > .hash > .value', e.data.done.hash);
updateTextContent('#worker-' + index + ' > .remaining > .value', '0');
d.querySelector('#worker-' + index).classList.add('done');
}
});
// Start the worker with a postMessage and pass the parameters
worker.postMessage({
hash: hashToCrack,
range: range,
salt: hashOptions.salt,
iterations: hashOptions.iterations,
updateRate: 50
});
// Push into the global workers array so we have controll later on
workers.push(worker);
});
}(window, document));
@import url("http://fonts.googleapis.com/css?family=Open+Sans:400,300,600");
*, *:before, *:after {
box-sizing: border-box;
-webkit-appearance: none;
-moz-appearance: none;
-ms-appearance: none;
appearance: none;
}
*:before, *:after {
content: '';
display: table;
clear: both;
}
.workers {
font-family: "Open Sans", sans-serif;
color: rgba(0, 0, 0, 0.8);
&:after {
display: table;
clear: both;
}
h1 {
color: rgba(0, 0, 0, 0.7);
text-align: center;
text-transform: uppercase;
font-size: 3em;
letter-spacing: -1px;
}
> .global-message {
text-align: center;
margin-bottom: 5rem;
}
.worker {
overflow: hidden;
float: left;
width: 320px;
padding: 1rem;
margin: 0 2rem 2rem 0;
background-color: #d3d3d3;
border-radius: 10px;
font-size: 1rem;
text-align: center;
opacity: 1;
transition: opacity 0.3s ease-out;
&.found {
> header {
background-color: rgba(0, 80, 0, 0.5);
}
}
&.done:not(.found) {
opacity: 0.4;
> header {
background-color: rgba(0, 0, 0, 0.5);
}
}
> header {
margin: -1rem -1rem 1rem -1rem;
padding: 0.5rem;
background-color: rgba(80, 0, 0, 0.5);
color: rgba(255, 255, 255, 0.9);
transition: background 0.3s ease-out;
> .title {
font-weight: 600;
font-size: 2em;
text-transform: uppercase;
}
}
> .data-element {
margin-bottom: 1rem;
> .label {
font-size: 1.3em;
text-transform: uppercase;
}
> .value {
display: block;
font-weight: 600;
font-size: 0.8em;
}
&.remaining {
> .value {
font-weight: 400;
font-size: 2em;
}
}
}
}
}
# Multi-threaded SHA1 hash cracker using Web Workers
This example should demonstrate how to make use of Web Workers to use all CPU
power that is available on the machine.
(function(s){
// In web workers we can use importScripts to load external javascripts
importScripts('crypto.js');
// This is the entry point for our worker
s.addEventListener('message', function(e) {
// Save a timestamp when we started
var startTime = new Date(),
lastUpdateTime = new Date(),
currentTime = new Date(),
i,
hexHash;
// Perform the loop on the range and start generating hashes :-)
for(i = e.data.range.low; i < e.data.range.high; i++) {
// Hash our number password with a salt and key stretching
hexHash = hash('' + i, e.data.salt, e.data.iterations);
// Get current time for resporting the status
currentTime = new Date();
// If last status update is older than updateRate in ms or fallback
// to 100ms
if(currentTime.getTime() - lastUpdateTime.getTime() > e.data.updateRate || 100) {
// Update status to host
s.postMessage({
update: {
hash: hexHash,
passphrase: i,
remaining: e.data.range.high - i
}
});
// Update lastUpdateTime
lastUpdateTime = currentTime;
}
// We found the hash!!!! :-)
if(hexHash === e.data.hash) {
s.postMessage({
found: {
hash: hexHash,
passphrase: i,
timeSpent: new Date().getTime() - startTime.getTime()
}
});
break;
}
}
s.postMessage({
done: {
timeSpent: new Date().getTime() - startTime.getTime(),
hash: hexHash
}
});
});
}(self));
/*
CryptoJS v3.1.2
code.google.com/p/crypto-js
(c) 2009-2013 by Jeff Mott. All rights reserved.
code.google.com/p/crypto-js/wiki/License
*/
var CryptoJS=CryptoJS||function(e,m){var p={},j=p.lib={},l=function(){},f=j.Base={extend:function(a){l.prototype=this;var c=new l;a&&c.mixIn(a);c.hasOwnProperty("init")||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}},
n=j.WordArray=f.extend({init:function(a,c){a=this.words=a||[];this.sigBytes=c!=m?c:4*a.length},toString:function(a){return(a||h).stringify(this)},concat:function(a){var c=this.words,q=a.words,d=this.sigBytes;a=a.sigBytes;this.clamp();if(d%4)for(var b=0;b<a;b++)c[d+b>>>2]|=(q[b>>>2]>>>24-8*(b%4)&255)<<24-8*((d+b)%4);else if(65535<q.length)for(b=0;b<a;b+=4)c[d+b>>>2]=q[b>>>2];else c.push.apply(c,q);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<<
32-8*(c%4);a.length=e.ceil(c/4)},clone:function(){var a=f.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],b=0;b<a;b+=4)c.push(4294967296*e.random()|0);return new n.init(c,a)}}),b=p.enc={},h=b.Hex={stringify:function(a){var c=a.words;a=a.sigBytes;for(var b=[],d=0;d<a;d++){var f=c[d>>>2]>>>24-8*(d%4)&255;b.push((f>>>4).toString(16));b.push((f&15).toString(16))}return b.join("")},parse:function(a){for(var c=a.length,b=[],d=0;d<c;d+=2)b[d>>>3]|=parseInt(a.substr(d,
2),16)<<24-4*(d%8);return new n.init(b,c/2)}},g=b.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var b=[],d=0;d<a;d++)b.push(String.fromCharCode(c[d>>>2]>>>24-8*(d%4)&255));return b.join("")},parse:function(a){for(var c=a.length,b=[],d=0;d<c;d++)b[d>>>2]|=(a.charCodeAt(d)&255)<<24-8*(d%4);return new n.init(b,c)}},r=b.Utf8={stringify:function(a){try{return decodeURIComponent(escape(g.stringify(a)))}catch(c){throw Error("Malformed UTF-8 data");}},parse:function(a){return g.parse(unescape(encodeURIComponent(a)))}},
k=j.BufferedBlockAlgorithm=f.extend({reset:function(){this._data=new n.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=r.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,b=c.words,d=c.sigBytes,f=this.blockSize,h=d/(4*f),h=a?e.ceil(h):e.max((h|0)-this._minBufferSize,0);a=h*f;d=e.min(4*a,d);if(a){for(var g=0;g<a;g+=f)this._doProcessBlock(b,g);g=b.splice(0,a);c.sigBytes-=d}return new n.init(g,d)},clone:function(){var a=f.clone.call(this);
a._data=this._data.clone();return a},_minBufferSize:0});j.Hasher=k.extend({cfg:f.extend(),init:function(a){this.cfg=this.cfg.extend(a);this.reset()},reset:function(){k.reset.call(this);this._doReset()},update:function(a){this._append(a);this._process();return this},finalize:function(a){a&&this._append(a);return this._doFinalize()},blockSize:16,_createHelper:function(a){return function(c,b){return(new a.init(b)).finalize(c)}},_createHmacHelper:function(a){return function(b,f){return(new s.HMAC.init(a,
f)).finalize(b)}}});var s=p.algo={};return p}(Math);
(function(){var e=CryptoJS,m=e.lib,p=m.WordArray,j=m.Hasher,l=[],m=e.algo.SHA1=j.extend({_doReset:function(){this._hash=new p.init([1732584193,4023233417,2562383102,271733878,3285377520])},_doProcessBlock:function(f,n){for(var b=this._hash.words,h=b[0],g=b[1],e=b[2],k=b[3],j=b[4],a=0;80>a;a++){if(16>a)l[a]=f[n+a]|0;else{var c=l[a-3]^l[a-8]^l[a-14]^l[a-16];l[a]=c<<1|c>>>31}c=(h<<5|h>>>27)+j+l[a];c=20>a?c+((g&e|~g&k)+1518500249):40>a?c+((g^e^k)+1859775393):60>a?c+((g&e|g&k|e&k)-1894007588):c+((g^e^
k)-899497514);j=k;k=e;e=g<<30|g>>>2;g=h;h=c}b[0]=b[0]+h|0;b[1]=b[1]+g|0;b[2]=b[2]+e|0;b[3]=b[3]+k|0;b[4]=b[4]+j|0},_doFinalize:function(){var f=this._data,e=f.words,b=8*this._nDataBytes,h=8*f.sigBytes;e[h>>>5]|=128<<24-h%32;e[(h+64>>>9<<4)+14]=Math.floor(b/4294967296);e[(h+64>>>9<<4)+15]=b;f.sigBytes=4*e.length;this._process();return this._hash},clone:function(){var e=j.clone.call(this);e._hash=this._hash.clone();return e}});e.SHA1=j._createHelper(m);e.HmacSHA1=j._createHmacHelper(m)})();
// Standard hashing function that uses SHA1 a salt and salted key stretching
function hash(passphrase, salt, iterations) {
var sha1 = CryptoJS.SHA1(salt + passphrase),
i;
for(i = 0; i < iterations; i++) {
sha1 = CryptoJS.SHA1(salt + sha1.toString());
}
return sha1.toString();
}