Skip to content
This repository has been archived by the owner on Jun 26, 2024. It is now read-only.

Commit

Permalink
Add back HMAC
Browse files Browse the repository at this point in the history
Need for noble-curves (secp256k1)
  • Loading branch information
larabr committed Jul 19, 2023
1 parent 46f2c70 commit c10ec2b
Show file tree
Hide file tree
Showing 4 changed files with 308 additions and 0 deletions.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@
"./esm/biginteger/*": {
"import": "./esm/biginteger/*.js"
},
"./hmac": {
"import": "./esm/hmac.js"
},
"./ripemd160": {
"import": "./esm/ripemd160.js"
},
Expand Down
81 changes: 81 additions & 0 deletions src/hmac.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import assert from './_assert.js';
import { Hash, CHash, Input, toBytes } from './utils.js';
// HMAC (RFC 2104)
export class HMAC<T extends Hash<T>> extends Hash<HMAC<T>> {
oHash: T;
iHash: T;
blockLen: number;
outputLen: number;
private finished = false;
private destroyed = false;

constructor(hash: CHash, _key: Input) {
super();
assert.hash(hash);
const key = toBytes(_key);
this.iHash = hash.create() as T;
if (typeof this.iHash.update !== 'function')
throw new Error('Expected instance of class which extends utils.Hash');
this.blockLen = this.iHash.blockLen;
this.outputLen = this.iHash.outputLen;
const blockLen = this.blockLen;
const pad = new Uint8Array(blockLen);
// blockLen can be bigger than outputLen
pad.set(key.length > blockLen ? hash.create().update(key).digest() : key);
for (let i = 0; i < pad.length; i++) pad[i] ^= 0x36;
this.iHash.update(pad);
// By doing update (processing of first block) of outer hash here we can re-use it between multiple calls via clone
this.oHash = hash.create() as T;
// Undo internal XOR && apply outer XOR
for (let i = 0; i < pad.length; i++) pad[i] ^= 0x36 ^ 0x5c;
this.oHash.update(pad);
pad.fill(0);
}
update(buf: Input) {
assert.exists(this);
this.iHash.update(buf);
return this;
}
digestInto(out: Uint8Array) {
assert.exists(this);
assert.bytes(out, this.outputLen);
this.finished = true;
this.iHash.digestInto(out);
this.oHash.update(out);
this.oHash.digestInto(out);
this.destroy();
}
digest() {
const out = new Uint8Array(this.oHash.outputLen);
this.digestInto(out);
return out;
}
_cloneInto(to?: HMAC<T>): HMAC<T> {
// Create new instance without calling constructor since key already in state and we don't know it.
to ||= Object.create(Object.getPrototypeOf(this), {});
const { oHash, iHash, finished, destroyed, blockLen, outputLen } = this;
to = to as this;
to.finished = finished;
to.destroyed = destroyed;
to.blockLen = blockLen;
to.outputLen = outputLen;
to.oHash = oHash._cloneInto(to.oHash);
to.iHash = iHash._cloneInto(to.iHash);
return to;
}
destroy() {
this.destroyed = true;
this.oHash.destroy();
this.iHash.destroy();
}
}

/**
* HMAC: RFC2104 message authentication code.
* @param hash - function that would be used e.g. sha256
* @param key - message key
* @param message - message data
*/
export const hmac = (hash: CHash, key: Input, message: Input): Uint8Array =>
new HMAC<any>(hash, key).update(message).digest();
hmac.create = (hash: CHash, key: Input) => new HMAC<any>(hash, key);
223 changes: 223 additions & 0 deletions test/hmac.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
import assert, { deepStrictEqual } from 'assert';
import { should } from 'micro-should';
import { sha256 } from '../esm/sha256.js';
import { sha512, sha384 } from '../esm/sha512.js';
import { hmac } from '../esm/hmac.js';
import {
utf8ToBytes,
hexToBytes,
bytesToHex,
truncate,
concatBytes,
TYPE_TEST,
SPACE,
EMPTY,
} from './utils.js';

// HMAC test vectors from RFC 4231
const HMAC_VECTORS = [
{
key: hexToBytes('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b'),
data: [utf8ToBytes('Hi There')],
sha256: 'b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7',
sha512:
'87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cde' +
'daa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854',
},
{
key: utf8ToBytes('Jefe'),
data: [utf8ToBytes('what do ya want '), utf8ToBytes('for nothing?')],
sha256: '5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843',
sha512:
'164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea250554' +
'9758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737',
},
{
key: hexToBytes('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'),
data: [
hexToBytes(
'dddddddddddddddddddddddddddddddddddddddddddddddddd' +
'dddddddddddddddddddddddddddddddddddddddddddddddddd'
),
],
sha256: '773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe',
sha512:
'fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39' +
'bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb',
},
{
key: hexToBytes('0102030405060708090a0b0c0d0e0f10111213141516171819'),
data: [
hexToBytes(
'cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd'
),
],
sha256: '82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b',
sha512:
'b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3db' +
'a91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd',
},
{
key: hexToBytes('0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c'),
data: [utf8ToBytes('Test With Trunca'), utf8ToBytes('tion')],
sha256: 'a3b6167473100ee06e0c796c2955552b',
sha512: '415fad6271580a531d4179bc891d87a6',
truncate: 16,
},
{
key: hexToBytes(
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaa'
),
data: [
utf8ToBytes('Test Using Large'),
utf8ToBytes('r Than Block-Siz'),
utf8ToBytes('e Key - Hash Key'),
utf8ToBytes(' First'),
],
sha256: '60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54',
sha512:
'80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f352' +
'6b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598',
},
{
key: hexToBytes(
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' +
'aaaaaa'
),
data: [
utf8ToBytes('This is a test u'),
utf8ToBytes('sing a larger th'),
utf8ToBytes('an block-size ke'),
utf8ToBytes('y and a larger t'),
utf8ToBytes('han block-size d'),
utf8ToBytes('ata. The key nee'),
utf8ToBytes('ds to be hashed '),
utf8ToBytes('before being use'),
utf8ToBytes('d by the HMAC al'),
utf8ToBytes('gorithm.'),
],
sha256: '9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2',
sha512:
'e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944' +
'b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58',
},
];

for (let i = 0; i < HMAC_VECTORS.length; i++) {
const t = HMAC_VECTORS[i];
should(`HMAC vector (${i}) sha256 full`, () => {
const h256 = hmac.create(sha256, t.key).update(concatBytes(...t.data));
assert.deepStrictEqual(truncate(h256.digest(), t.truncate), hexToBytes(t.sha256));
});
should(`HMAC vector (${i}) sha256 partial`, () => {
const h256 = hmac.create(sha256, t.key);
for (let d of t.data) h256.update(d);
assert.deepStrictEqual(truncate(h256.digest(), t.truncate), hexToBytes(t.sha256));
});
should(`HMAC vector (${i}) sha256 partial (cleanup=true)`, () => {
const h256 = hmac.create(sha256, t.key, { cleanup: true });
for (let d of t.data) h256.update(d);
assert.deepStrictEqual(truncate(h256.digest(), t.truncate), hexToBytes(t.sha256));
});
should(`HMAC vector (${i}) sha256 partial (cleanup=false)`, () => {
const h256 = hmac.create(sha256, t.key, { cleanup: false });
for (let d of t.data) h256.update(d);
assert.deepStrictEqual(truncate(h256.digest(), t.truncate), hexToBytes(t.sha256));
});
should(`HMAC vector (${i}) sha512 full`, () => {
const h512 = hmac.create(sha512, t.key).update(concatBytes(...t.data));
assert.deepStrictEqual(truncate(h512.digest(), t.truncate), hexToBytes(t.sha512));
});
should(`HMAC vector (${i}) sha512 partial`, () => {
const h512 = hmac.create(sha512, t.key);
for (let d of t.data) h512.update(d);
assert.deepStrictEqual(truncate(h512.digest(), t.truncate), hexToBytes(t.sha512));
});
should(`HMAC vector (${i}) sha512 partial (cleanup=false)`, () => {
const h512 = hmac.create(sha512, t.key, { cleanup: false });
for (let d of t.data) h512.update(d);
assert.deepStrictEqual(truncate(h512.digest(), t.truncate), hexToBytes(t.sha512));
});
should(`HMAC vector (${i}) sha512 partial (cleanup=true)`, () => {
const h512 = hmac.create(sha512, t.key, { cleanup: true });
for (let d of t.data) h512.update(d);
assert.deepStrictEqual(truncate(h512.digest(), t.truncate), hexToBytes(t.sha512));
});
}

should('HMAC types', () => {
hmac(sha256, 'key', 'msg');
hmac.create(sha256, 'key');
for (const t of TYPE_TEST.bytes) {
assert.throws(() => hmac(sha256, t, 'msg'), `hmac(key=${t})`);
assert.throws(() => hmac(sha256, 'key', t), `hmac(msg=${t})`);
assert.throws(() => hmac.create(sha256, t), `hmac.create(key=${t})`);
}
assert.throws(() => hmac(sha256, undefined, 'msg'), `hmac(key=undefined)`);
assert.throws(() => hmac(sha256, 'key'), `hmac(msg=undefined)`);
assert.throws(() => hmac.create(sha256, undefined), `hmac.create(key=undefined)`);
// for (const t of TYPE_TEST.opts) {
// assert.throws(() => hmac(sha256, 'key', 'salt', t), `hmac(opt=${t})`);
// assert.throws(() => hmac.create(sha256, 'key', t), `hmac.create(opt=${t})`);
// }
for (const t of TYPE_TEST.hash) assert.throws(() => hmac(t, 'key', 'salt'), `hmac(hash=${t})`);
assert.deepStrictEqual(
hmac(sha512, SPACE.str, SPACE.str),
hmac(sha512, SPACE.bytes, SPACE.bytes),
'hmac.SPACE'
);
assert.deepStrictEqual(
hmac(sha512, EMPTY.str, EMPTY.str),
hmac(sha512, EMPTY.bytes, EMPTY.bytes),
'hmac.EMPTY'
);
assert.deepStrictEqual(
hmac(sha512, SPACE.str, SPACE.str),
hmac.create(sha512, SPACE.str).update(SPACE.bytes).digest(),
'hmac.SPACE (full form bytes)'
);
assert.deepStrictEqual(
hmac(sha512, SPACE.str, SPACE.str),
hmac.create(sha512, SPACE.str).update(SPACE.str).digest(),
'hmac.SPACE (full form stingr)'
);
});

should('Sha512/384 issue', () => {
const h = hmac.create(
sha384,
hexToBytes(
'000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
)
);
h.update(
hexToBytes(
'010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101'
)
);
h.update(hexToBytes('00'));
h.update(
hexToBytes(
'6b9d3dad2e1b8c1c05b19875b6659f4de23c3b667bf297ba9aa47740787137d896d5724e4c70a825f872c9ea60d2edf59a9083505bc92276aec4be312696ef7bf3bf603f4bbd381196a029f340585312313bca4a9b5b890efee42c77b1ee25fe'
)
);
deepStrictEqual(
bytesToHex(h.digest()),
'a1ae63339c4fac449464e302c61e8ceb5b28c04d108e022179ce6dabb2d3e310cb3bf41cd6013b3006f33c037e6b7fa8'
);
});
1 change: 1 addition & 0 deletions test/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { should } from 'micro-should';
// // Tests generated from rust
import './hmac.test.js';
import './clone.test.js';
import './u64.test.js';
import './utils.test.js';
Expand Down

0 comments on commit c10ec2b

Please sign in to comment.