Skip to content

Commit ab0f7fa

Browse files
committed
Add @coajaxial's HMAC
1 parent f408f8e commit ab0f7fa

File tree

6 files changed

+155
-107
lines changed

6 files changed

+155
-107
lines changed

modules/TOTP.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/* Copyright (c) 2020 Dominik Enzinger. See the file LICENSE for copying permission. */
2+
const HMAC = require('hmac');
3+
const base32 = require('base32');
4+
5+
/// For internal use - generates the one time password
6+
function generateOtp(hmac, digits) {
7+
"compiled";
8+
const o = hmac[hmac.byteLength - 1] & 0x0F;
9+
const dt = ((hmac[o] & 0x7f) << 24) | (hmac[o + 1] << 16) | (hmac[o + 2] << 8) | hmac[o + 3];
10+
let result = '' + dt % Math.pow(10, digits);
11+
while ( result.length < digits ) {
12+
result = '0' + result;
13+
}
14+
return result;
15+
}
16+
17+
function TOTP(secret) {
18+
this.hmac = new HMAC.FixedSHA1(base32.decode(secret), 8);
19+
this.message = new Uint8Array(8);
20+
};
21+
22+
/// Generate a TOTP with the given timestamp, period, and number of digits.
23+
/// For example totp.generate(getTime(), 6/*digits*, 30/*seconds*/)
24+
TOTP.prototype.generate = function(timestamp, digits, tokenPeriod) {
25+
const epoch = Math.floor(timestamp / tokenPeriod);
26+
this.message.set([epoch >> 24 & 0xFF, epoch >> 16 & 0xFF, epoch >> 8 & 0xFF, epoch & 0xFF], 4);
27+
const hmac = this.hmac.digest(this.message.buffer);
28+
return generateOtp(hmac, digits);
29+
};
30+
31+
/// Create a TOTP generator with a secret code in base32
32+
exports.create = function(secret) {
33+
return new TOTP(secret);
34+
};

modules/TOTP.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<!--- Copyright (c) 2020 Gordon Williams. See the file LICENSE for copying permission. -->
2+
Time-based One-time Password
3+
============================
4+
5+
<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>
6+
7+
* KEYWORDS: Module,Crypto,TOTP,Time-based Password,OTP,One time password
8+
9+
This [[TOTP.js]] module implements a Time-based One-time Password for Espruino.
10+
11+
12+
How to use the module:
13+
14+
```
15+
const TOTP = require('totp');
16+
const totp = TOTP.create('JBSWY3DPEHPK3PXP');
17+
// 6 digits, period of 30 seconds
18+
console.log(totp.generate(getTime(), 6, 30));
19+
```
20+
21+
Reference
22+
--------------
23+
24+
* APPEND_JSDOC: TOTP.js

modules/base32.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/* Copyright (c) 2020 Dominik Enzinger. See the file LICENSE for copying permission. */
2+
const BASE32_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
3+
4+
/// Decode the supplied encoded base 32 string into an ArrayBuffer
5+
exports.decode = function(encoded) {
6+
encoded = encoded.toUpperCase();
7+
const result = new Uint8Array(Math.floor(encoded.length * 5 / 8));
8+
let buffer = 0,
9+
next = 0,
10+
bitsLeft = 0,
11+
charValue = 0;
12+
for ( let i in encoded ) {
13+
charValue = BASE32_CHARS.indexOf(encoded.charAt(i));
14+
buffer = (buffer << 5) | charValue & 31;
15+
bitsLeft += 5;
16+
if (bitsLeft >= 8) {
17+
bitsLeft -= 8;
18+
result[next++] = (buffer >> bitsLeft) & 0xFF;
19+
}
20+
}
21+
return result.buffer;
22+
};

modules/base32.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<!--- Copyright (c) 2020 Gordon Williams. See the file LICENSE for copying permission. -->
2+
Base 32 Decoder
3+
=================
4+
5+
<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>
6+
7+
* KEYWORDS: Module,Base32
8+
9+
This [[base32.js]] module implements a Base 32 decoder.
10+
11+
How to use the module:
12+
13+
```
14+
var decoded = require('base32').decode('JBSWY3DPEHPK3PXP');
15+
// decoded is now an ArrayBuffer
16+
```
17+
18+
Reference
19+
--------------
20+
21+
* APPEND_JSDOC: base32.js

modules/hmac.js

Lines changed: 40 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,50 @@
1-
/* Copyright (c) 2015 Mikael Ganehag Brorsson. See the file LICENSE for copying permission. */
2-
/*
3-
Small module to add HMAC support. Depends on 'hashlib'.
4-
*/
5-
6-
function hmac(key, message, digestmod) {
7-
if (digestmod == null) {
8-
digestmod = hashlib.sha256;
9-
}
10-
11-
this.finished = false;
12-
this.inner = digestmod();
13-
this.outer = digestmod();
14-
15-
var i, pad = new Uint8Array(this.inner.block_size);
16-
17-
if (key.length > this.inner.block_size) {
18-
var h = (new digestmod()).update(k),
19-
ah = E.toBufferArray(h.digest());
20-
21-
for(i = 0; i < ah.length; i++) {
22-
pad[i] = ah[i].charCodeAt(0);
23-
}
24-
}
25-
else {
26-
for (i = 0; i < key.length; i++) {
27-
pad[i] = key[i].charCodeAt(0);
28-
}
29-
}
30-
31-
for (i = 0; i < pad.length; i++) {
32-
pad[i] ^= 0x36;
33-
}
34-
this.inner.update(String.fromCharCode.apply(null, pad));
35-
36-
for (i = 0; i < pad.length; i++) {
37-
pad[i] ^= 0x36 ^ 0x5c;
38-
}
39-
this.outer.update(String.fromCharCode.apply(null, pad));
40-
41-
for (i = 0; i < pad.length; i++) {
42-
pad[i] = 0;
43-
}
1+
/* Copyright (c) 2020 Dominik Enzinger. See the file LICENSE for copying permission. */
2+
function bitwiseOr0x36(b) {"compiled"; return b ^ 0x36; }
3+
4+
function bitwiseOr0x5c(b) {"compiled"; return b ^ 0x5c; }
5+
6+
/// Returns an MAC instance using the given hash. Eg. HMAC(key, require('crypto').SHA1, 64, 20)
7+
exports.HMAC = function(key, hash, blockSize, outputSize) {
8+
if ( key.byteLength > blockSize )
9+
key = hash(key);
10+
this.hash = hash;
11+
this.keyLength = Math.max(blockSize, key.byteLength);
12+
key = new Uint8Array(key, 0, this.keyLength);
13+
this.oBuf = new Uint8Array(this.keyLength + outputSize);
14+
this.oBuf.set(key.map(bitwiseOr0x5c).buffer, 0);
15+
this.iKeyPad = key.map(bitwiseOr0x36).buffer;
16+
};
4417

45-
if(message) {
46-
this.inner.update(message);
47-
}
48-
}
4918

50-
hmac.prototype.update = function(m) {
51-
if(m) {
52-
this.finished = false;
53-
this.inner.update(m);
54-
}
55-
return this;
19+
/// Take a message as an arraybuffer or string, return an arraybuffer
20+
exports.HMAC.prototype.digest = function(message) {
21+
const iBuf = new Uint8Array(this.keyLength + message.byteLength);
22+
iBuf.set(this.iKeyPad, 0);
23+
iBuf.set(message, this.keyLength);
24+
this.oBuf.set(this.hash(iBuf), this.keyLength);
25+
return this.hash(this.oBuf);
5626
};
5727

58-
hmac.prototype.digest = function() {
59-
if(!this.finished) {
60-
this.outer.update(this.inner.digest())
61-
this.finished = true;
62-
}
63-
return this.outer.digest();
28+
function FixedHMAC(key, messageSize, hash, blockSize, outputSize) {
29+
exports.HMAC.call(this, key, hash, blockSize, outputSize);
30+
this.iBuf = new Uint8Array(this.keyLength + messageSize);
31+
this.iBuf.set(this.iKeyPad, 0);
32+
delete this.ikeyPad;
6433
};
6534

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

73-
exports.create = function(key, message, digestmod) {
74-
return new hmac(key, message, digestmod);
75-
}
76-
77-
/**
78-
compare_digest(a, b) -> bool
79-
80-
Return 'a == b'. This function uses an approach designed to prevent
81-
timing analysis, making it appropriate for cryptography.
82-
a and b must both be of the same type.
83-
84-
Note: If a and b are of different lengths, or if an error occurs,
85-
a timing attack could theoretically reveal information about the
86-
types and lengths of a and b--but not their values.
87-
*/
88-
exports.compare_digest = function(a, b) {
89-
var match, i;
90-
91-
if(a.length != b.length) {
92-
return false;
93-
}
94-
95-
match = 0;
96-
for(i = 0; i < a.length; i++) {
97-
match |= a.charCodeAt(i) ^ b.charCodeAt(i);
98-
}
42+
/// Create a basic HMAC using SHA1
43+
exports.SHA1 = function(key) {
44+
return new exports.HMAC(key, require('crypto').SHA1, 64, 20);
45+
};
9946

100-
return match == 0;
47+
/// FixedSHA1 is faster than SHA1, but digested message must always be the same fixed length.
48+
exports.FixedSHA1 = function(key, messageSize) {
49+
return new FixedHMAC(key, messageSize, require('crypto').SHA1, 64, 20);
10150
};

modules/hmac.md

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,30 @@
1-
<!--- Copyright (c) 2014 Mikael Ganehag Brorsson. See the file LICENSE for copying permission. -->
1+
<!--- Copyright (c) 2020 Gordon Williams. See the file LICENSE for copying permission. -->
22
HMAC Module
33
===========
44

55
<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>
66

77
* KEYWORDS: Module,HMAC,Hashlib,Crypto
88

9-
This [[hmac.js]] module implements a HMAC for Espruino. It depends on the inclusion of hashlib to compute the checksums.
9+
This [[hmac.js]] module implements a HMAC for Espruino.
10+
11+
**Note:** There was an older `hmac` module that depended on `hashlib` (a built-in module
12+
that is no longer included in Espruino). This new `hmac` has a different API
13+
that uses the [`crypto` library](http://www.espruino.com/Reference#crypto).
1014

1115
How to use the module:
1216

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

2927
Reference
3028
--------------
31-
29+
3230
* APPEND_JSDOC: hmac.js

0 commit comments

Comments
 (0)