Skip to content

Instantly share code, notes, and snippets.

@adoc
Last active January 4, 2016 10:49
Show Gist options
  • Save adoc/8611494 to your computer and use it in GitHub Desktop.
Save adoc/8611494 to your computer and use it in GitHub Desktop.
hmac.js: Using CryptoJS, implements HMAC algorithm while also hashing and appending timestamp information.
/*
hmac.js
Using CryptoJS, implements HMAC algorithm while also hashing and appending
timestamp information.
Author: github.com/adoc
Location: https://gist.github.com/adoc/8611494
*/
(function(window) {
// Just a simple time provider that can be offset to another clock.
// Second-only accuracy.
function TimeProvider() {
var _timeProvider = {
offset: 0,
reset: function(time) {
if (!time) {
this.offset = 0;
} else {
var now = new Date().getTime() / 1000;
this.offset = time - parseInt(now);
}
},
get: function() {
var now = new Date().getTime() / 1000;
return parseInt(now + this.offset);
}
}
return _timeProvider;
}
var Hmac = function(opts) {
opts = opts || {};
var defaults = {
secret: null,
passes: 1,
hmacAlg: CryptoJS.algo.HMAC,
hashAlg: CryptoJS.algo.SHA256,
Encoder: CryptoJS.enc.Base64,
expiry: 1,
timeProvider: new TimeProvider(),
debug: false
};
var _Hmac = {
// Basic signing method.
_sign: function () {
if (this.debug) {
for(var arg=0; arg<arguments.length; arg++) {
console.log(arguments[arg]);
}
}
// Init HMAC alg.
var hmac = this.hmacAlg.create(this.hashAlg, this.secret);
// Do hashing.
for (pass=0; pass<this.passes; pass++) {
for(var arg=0; arg<arguments.length; arg++) {
hmac.update(arguments[arg].toString());
}
}
return hmac.finalize();
},
// Sign with timestamp.
sign: function() {
var args = [].slice.apply(arguments);
// Get current timestamp.
var ts = this.timeProvider.get().toString();
// Set up our own args.
args.splice(0, 0, ts);
// Sign and apply timestamp.
var digest = this._sign.apply(this, args);
// Convert so we can add some bytes.
digest = wordsToBytes(digest.words);
// Add the timestamp.
digest = stringToBytes(ts, digest);
// Back to WordArray then encoded.
digest = new CryptoJS.lib.WordArray.init(bytesToWords(digest))
return digest.toString(this.Encoder);
},
// Verify a challenge and payload.
verify: function(challenge) {
var args = [].slice.apply(arguments);
args.splice(0, 1); // Remove `challenge` arg.
challenge = this.Encoder.parse(challenge);
var challenge_bytes = stripArray(wordsToBytes(challenge.words));
var sig_bytes = challenge_bytes.slice(0,-10);
var ts_bytes = challenge_bytes.slice(-10);
// Get the timestamp from the bytes.
var ts = bytesToString(ts_bytes);
// Check the timestamp against current time.
var now = parseInt(this.timeProvider.get());
var delta = now - parseInt(ts);
if (Math.abs(delta) > this.expiry) {
throw "Hmac: Signature is too old.";
}
// Sign and check against challenge.
args.splice(0, 0, ts);
sig_bytes = new CryptoJS.lib.WordArray.init(bytesToWords(sig_bytes));
var chal = sig_bytes.toString(this.Encoder);
var sig = this._sign.apply(this, args).toString(this.Encoder);
if (sig == chal) {
return true;
} else {
if (this.debug) {
console.log("Sig: " + sig + " Challenge: " + chal)
}
throw "Hmac.verify: Incorrect HMAC challenge.";
}
}
};
return _.extend(_Hmac, defaults, opts);
}
// Set global object.
window.TimeProvider = TimeProvider;
window.Hmac = Hmac;
// AMD Hook
if (typeof define === "function" && define.amd) {
define( "mypackage", ['underscore'], function () {
return {Hmac: Hmac,
TimeProvider: TimeProvider};
});
}
}).call(window, this);
function Hmac_test() {
// sign
var h = new Hmac({secret: '12345'});
digest = h.sign('this is my sensitive data.');
// verify
var h = new Hmac({secret: '12345'});
h.verify(digest, 'this is my sensitive data.');
// sign
var h = new Hmac({secret: '12345'});
digest = h.sign('this is my sensitive data.', 'oh I forgot, this too.');
// verify
var h = new Hmac({secret: '12345'});
h.verify(digest, 'this is my sensitive data.', 'oh I forgot, this too.');
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment