Skip to content

Commit

Permalink
Add @coajaxial's HMAC
Browse files Browse the repository at this point in the history
  • Loading branch information
gfwilliams committed Oct 6, 2020
1 parent f408f8e commit ab0f7fa
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 107 deletions.
34 changes: 34 additions & 0 deletions modules/TOTP.js
@@ -0,0 +1,34 @@
/* Copyright (c) 2020 Dominik Enzinger. See the file LICENSE for copying permission. */
const HMAC = require('hmac');
const base32 = require('base32');

/// For internal use - generates the one time password
function generateOtp(hmac, digits) {
"compiled";
const o = hmac[hmac.byteLength - 1] & 0x0F;
const dt = ((hmac[o] & 0x7f) << 24) | (hmac[o + 1] << 16) | (hmac[o + 2] << 8) | hmac[o + 3];
let result = '' + dt % Math.pow(10, digits);
while ( result.length < digits ) {
result = '0' + result;
}
return result;
}

function TOTP(secret) {
this.hmac = new HMAC.FixedSHA1(base32.decode(secret), 8);
this.message = new Uint8Array(8);
};

/// Generate a TOTP with the given timestamp, period, and number of digits.
/// For example totp.generate(getTime(), 6/*digits*, 30/*seconds*/)
TOTP.prototype.generate = function(timestamp, digits, tokenPeriod) {
const epoch = Math.floor(timestamp / tokenPeriod);
this.message.set([epoch >> 24 & 0xFF, epoch >> 16 & 0xFF, epoch >> 8 & 0xFF, epoch & 0xFF], 4);
const hmac = this.hmac.digest(this.message.buffer);
return generateOtp(hmac, digits);
};

/// Create a TOTP generator with a secret code in base32
exports.create = function(secret) {
return new TOTP(secret);
};
24 changes: 24 additions & 0 deletions modules/TOTP.md
@@ -0,0 +1,24 @@
<!--- Copyright (c) 2020 Gordon Williams. See the file LICENSE for copying permission. -->
Time-based One-time Password
============================

<span style="color:red">:warning: **Please view the correctly rendered version of this page at https://www.espruino.com/TOTP. Links, lists, videos, search, and other features will not work correctly when viewed on GitHub** :warning:</span>

* KEYWORDS: Module,Crypto,TOTP,Time-based Password,OTP,One time password

This [[TOTP.js]] module implements a Time-based One-time Password for Espruino.


How to use the module:

```
const TOTP = require('totp');
const totp = TOTP.create('JBSWY3DPEHPK3PXP');
// 6 digits, period of 30 seconds
console.log(totp.generate(getTime(), 6, 30));
```

Reference
--------------

* APPEND_JSDOC: TOTP.js
22 changes: 22 additions & 0 deletions modules/base32.js
@@ -0,0 +1,22 @@
/* Copyright (c) 2020 Dominik Enzinger. See the file LICENSE for copying permission. */
const BASE32_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';

/// Decode the supplied encoded base 32 string into an ArrayBuffer
exports.decode = function(encoded) {
encoded = encoded.toUpperCase();
const result = new Uint8Array(Math.floor(encoded.length * 5 / 8));
let buffer = 0,
next = 0,
bitsLeft = 0,
charValue = 0;
for ( let i in encoded ) {
charValue = BASE32_CHARS.indexOf(encoded.charAt(i));
buffer = (buffer << 5) | charValue & 31;
bitsLeft += 5;
if (bitsLeft >= 8) {
bitsLeft -= 8;
result[next++] = (buffer >> bitsLeft) & 0xFF;
}
}
return result.buffer;
};
21 changes: 21 additions & 0 deletions modules/base32.md
@@ -0,0 +1,21 @@
<!--- Copyright (c) 2020 Gordon Williams. See the file LICENSE for copying permission. -->
Base 32 Decoder
=================

<span style="color:red">:warning: **Please view the correctly rendered version of this page at https://www.espruino.com/base32. Links, lists, videos, search, and other features will not work correctly when viewed on GitHub** :warning:</span>

* KEYWORDS: Module,Base32

This [[base32.js]] module implements a Base 32 decoder.

How to use the module:

```
var decoded = require('base32').decode('JBSWY3DPEHPK3PXP');
// decoded is now an ArrayBuffer
```

Reference
--------------

* APPEND_JSDOC: base32.js
131 changes: 40 additions & 91 deletions modules/hmac.js
@@ -1,101 +1,50 @@
/* Copyright (c) 2015 Mikael Ganehag Brorsson. See the file LICENSE for copying permission. */
/*
Small module to add HMAC support. Depends on 'hashlib'.
*/

function hmac(key, message, digestmod) {
if (digestmod == null) {
digestmod = hashlib.sha256;
}

this.finished = false;
this.inner = digestmod();
this.outer = digestmod();

var i, pad = new Uint8Array(this.inner.block_size);

if (key.length > this.inner.block_size) {
var h = (new digestmod()).update(k),
ah = E.toBufferArray(h.digest());

for(i = 0; i < ah.length; i++) {
pad[i] = ah[i].charCodeAt(0);
}
}
else {
for (i = 0; i < key.length; i++) {
pad[i] = key[i].charCodeAt(0);
}
}

for (i = 0; i < pad.length; i++) {
pad[i] ^= 0x36;
}
this.inner.update(String.fromCharCode.apply(null, pad));

for (i = 0; i < pad.length; i++) {
pad[i] ^= 0x36 ^ 0x5c;
}
this.outer.update(String.fromCharCode.apply(null, pad));

for (i = 0; i < pad.length; i++) {
pad[i] = 0;
}
/* Copyright (c) 2020 Dominik Enzinger. See the file LICENSE for copying permission. */
function bitwiseOr0x36(b) {"compiled"; return b ^ 0x36; }

function bitwiseOr0x5c(b) {"compiled"; return b ^ 0x5c; }

/// Returns an MAC instance using the given hash. Eg. HMAC(key, require('crypto').SHA1, 64, 20)
exports.HMAC = function(key, hash, blockSize, outputSize) {
if ( key.byteLength > blockSize )
key = hash(key);
this.hash = hash;
this.keyLength = Math.max(blockSize, key.byteLength);
key = new Uint8Array(key, 0, this.keyLength);
this.oBuf = new Uint8Array(this.keyLength + outputSize);
this.oBuf.set(key.map(bitwiseOr0x5c).buffer, 0);
this.iKeyPad = key.map(bitwiseOr0x36).buffer;
};

if(message) {
this.inner.update(message);
}
}

hmac.prototype.update = function(m) {
if(m) {
this.finished = false;
this.inner.update(m);
}
return this;
/// Take a message as an arraybuffer or string, return an arraybuffer
exports.HMAC.prototype.digest = function(message) {
const iBuf = new Uint8Array(this.keyLength + message.byteLength);
iBuf.set(this.iKeyPad, 0);
iBuf.set(message, this.keyLength);
this.oBuf.set(this.hash(iBuf), this.keyLength);
return this.hash(this.oBuf);
};

hmac.prototype.digest = function() {
if(!this.finished) {
this.outer.update(this.inner.digest())
this.finished = true;
}
return this.outer.digest();
function FixedHMAC(key, messageSize, hash, blockSize, outputSize) {
exports.HMAC.call(this, key, hash, blockSize, outputSize);
this.iBuf = new Uint8Array(this.keyLength + messageSize);
this.iBuf.set(this.iKeyPad, 0);
delete this.ikeyPad;
};

hmac.prototype.hexdigest = function() {
var i, v, s = "", h = this.digest();
for(i = 0; i < h.length; i++)
s += (256+h.charCodeAt(i)).toString(16).substr(-2);
return s;
/// Take a message as an arraybuffer or string, return an arraybuffer
FixedHMAC.prototype.digest = function(message) {
this.iBuf.set(message, this.keyLength);
this.oBuf.set(this.hash(this.iBuf), this.keyLength);
return this.hash(this.oBuf);
};

exports.create = function(key, message, digestmod) {
return new hmac(key, message, digestmod);
}

/**
compare_digest(a, b) -> bool
Return 'a == b'. This function uses an approach designed to prevent
timing analysis, making it appropriate for cryptography.
a and b must both be of the same type.
Note: If a and b are of different lengths, or if an error occurs,
a timing attack could theoretically reveal information about the
types and lengths of a and b--but not their values.
*/
exports.compare_digest = function(a, b) {
var match, i;

if(a.length != b.length) {
return false;
}

match = 0;
for(i = 0; i < a.length; i++) {
match |= a.charCodeAt(i) ^ b.charCodeAt(i);
}
/// Create a basic HMAC using SHA1
exports.SHA1 = function(key) {
return new exports.HMAC(key, require('crypto').SHA1, 64, 20);
};

return match == 0;
/// FixedSHA1 is faster than SHA1, but digested message must always be the same fixed length.
exports.FixedSHA1 = function(key, messageSize) {
return new FixedHMAC(key, messageSize, require('crypto').SHA1, 64, 20);
};
30 changes: 14 additions & 16 deletions modules/hmac.md
@@ -1,32 +1,30 @@
<!--- Copyright (c) 2014 Mikael Ganehag Brorsson. See the file LICENSE for copying permission. -->
<!--- Copyright (c) 2020 Gordon Williams. See the file LICENSE for copying permission. -->
HMAC Module
===========

<span style="color:red">:warning: **Please view the correctly rendered version of this page at https://www.espruino.com/hmac. Links, lists, videos, search, and other features will not work correctly when viewed on GitHub** :warning:</span>

* KEYWORDS: Module,HMAC,Hashlib,Crypto

This [[hmac.js]] module implements a HMAC for Espruino. It depends on the inclusion of hashlib to compute the checksums.
This [[hmac.js]] module implements a HMAC for Espruino.

**Note:** There was an older `hmac` module that depended on `hashlib` (a built-in module
that is no longer included in Espruino). This new `hmac` has a different API
that uses the [`crypto` library](http://www.espruino.com/Reference#crypto).

How to use the module:

```
var hmac = require("hmac");
var hashlib = require("hashlib");
var foo = hmac.create("secret", "message", hashlib.sha256);
var bar = hmac.create("secret", "another message", hashlib.sha256);
foo.digest() // raw digest
foo.hexdigest() // hex encoded digest
foo.update('more message') // used to iterate parts of a message
// This function uses an approach designed to prevent timing analysis,
// making it appropriate for cryptography.
hmac.compare_digest(foo.digest(), bar.digest())
const HMAC = require('hmac');
var hmac = HMAC.SHA1(E.toArrayBuffer('my secret key'));
console.log(hmac.digest(E.toArrayBuffer('my message')));
// FixedSHA1 is faster than SHA1, but digested message must always be the same fixed length.
var hmacf = HMAC.FixedSHA1(E.toArrayBuffer('my secret key'), 2); // 2 bytes
console.log(hmacf.digest(E.toArrayBuffer('bb')));
console.log(hmacf.digest(E.toArrayBuffer('xx')));
```

Reference
--------------

* APPEND_JSDOC: hmac.js

0 comments on commit ab0f7fa

Please sign in to comment.