hmac implementation for web crypto

This commit is contained in:
Kyle Spearrin 2018-04-17 19:02:58 -04:00
parent 719c8aa70c
commit 81f7bd7b76
4 changed files with 54 additions and 2 deletions

View File

@ -2,4 +2,5 @@ export abstract class CryptoFunctionService {
pbkdf2: (password: string | ArrayBuffer, salt: string | ArrayBuffer, algorithm: 'sha256' | 'sha512', pbkdf2: (password: string | ArrayBuffer, salt: string | ArrayBuffer, algorithm: 'sha256' | 'sha512',
iterations: number, length: number) => Promise<ArrayBuffer>; iterations: number, length: number) => Promise<ArrayBuffer>;
hash: (value: string | ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512') => Promise<ArrayBuffer>; hash: (value: string | ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512') => Promise<ArrayBuffer>;
hmac: (value: ArrayBuffer, key: ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512') => Promise<ArrayBuffer>;
} }

View File

@ -25,6 +25,10 @@ export class NodeCryptoFunctionService implements CryptoFunctionService {
return hash.digest().buffer; return hash.digest().buffer;
} }
async hmac(value: ArrayBuffer, key: ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512'): Promise<ArrayBuffer> {
return new Uint8Array([]).buffer;
}
private toNodeValue(value: string | ArrayBuffer): string | Buffer { private toNodeValue(value: string | ArrayBuffer): string | Buffer {
let nodeValue: string | Buffer; let nodeValue: string | Buffer;
if (typeof (value) === 'string') { if (typeof (value) === 'string') {

View File

@ -47,6 +47,21 @@ describe('WebCrypto Function Service', () => {
testHash(true, 'sha256', regular256Hash, utf8256Hash, unicode256Hash); testHash(true, 'sha256', regular256Hash, utf8256Hash, unicode256Hash);
testHash(true, 'sha512', regular512Hash, utf8512Hash, unicode512Hash); testHash(true, 'sha512', regular512Hash, utf8512Hash, unicode512Hash);
}); });
describe('hmac', () => {
const sha1Mac = '4d4c223f95dc577b665ec4ccbcb680b80a397038';
const sha256Mac = '6be3caa84922e12aaaaa2f16c40d44433bb081ef323db584eb616333ab4e874f';
const sha512Mac = '21910e341fa12106ca35758a2285374509326c9fbe0bd64e7b99c898f841dc948c58ce66d3504d8883c' +
'5ea7817a0b7c5d4d9b00364ccd214669131fc17fe4aca';
testHmac(false, 'sha1', sha1Mac);
testHmac(false, 'sha256', sha256Mac);
testHmac(false, 'sha512', sha512Mac);
testHmac(true, 'sha1', sha1Mac);
testHmac(true, 'sha256', sha256Mac);
testHmac(true, 'sha512', sha512Mac);
});
}); });
function testPbkdf2ValidKey(edge: boolean, algorithm: 'sha256' | 'sha512', regularKey: string, function testPbkdf2ValidKey(edge: boolean, algorithm: 'sha256' | 'sha512', regularKey: string,
@ -117,6 +132,15 @@ function testHash(edge: boolean, algorithm: 'sha1' | 'sha256' | 'sha512', regula
}); });
} }
function testHmac(edge: boolean, algorithm: 'sha1' | 'sha256' | 'sha512', mac: string) {
it('should create valid ' + algorithm + ' hmac' + (edge ? ' for edge' : ''), async () => {
const webCryptoFunctionService = getWebCryptoFunctionService(edge);
const computedMac = await webCryptoFunctionService.hmac(UtilsService.fromUtf8ToArray('SignMe!!').buffer,
UtilsService.fromUtf8ToArray('secretkey').buffer, algorithm);
expect(UtilsService.fromBufferToHex(computedMac)).toBe(mac);
});
}
function getWebCryptoFunctionService(edge = false) { function getWebCryptoFunctionService(edge = false) {
const platformUtilsService = new BrowserPlatformUtilsService(edge); const platformUtilsService = new BrowserPlatformUtilsService(edge);
return new WebCryptoFunctionService(window, platformUtilsService); return new WebCryptoFunctionService(window, platformUtilsService);

View File

@ -35,7 +35,7 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
name: 'PBKDF2', name: 'PBKDF2',
salt: saltBuf, salt: saltBuf,
iterations: iterations, iterations: iterations,
hash: { name: algorithm === 'sha256' ? 'SHA-256' : 'SHA-512' }, hash: { name: this.toWebCryptoAlgorithm(algorithm) },
}; };
const keyType: AesDerivedKeyParams = { const keyType: AesDerivedKeyParams = {
@ -65,10 +65,29 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
const valueBuf = this.toBuf(value); const valueBuf = this.toBuf(value);
return await this.subtle.digest({ return await this.subtle.digest({
name: algorithm === 'sha1' ? 'SHA-1' : algorithm === 'sha256' ? 'SHA-256' : 'SHA-512' name: this.toWebCryptoAlgorithm(algorithm)
}, valueBuf); }, valueBuf);
} }
async hmac(value: ArrayBuffer, key: ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512'): Promise<ArrayBuffer> {
if (this.isEdge) {
const valueBytes = this.toForgeBytes(value);
const keyBytes = this.toForgeBytes(key);
const hmac = (forge as any).hmac.create();
hmac.start(algorithm, keyBytes);
hmac.update(valueBytes);
return this.fromForgeBytesToBuf(hmac.digest().getBytes());
}
const signingAlgorithm = {
name: 'HMAC',
hash: { name: this.toWebCryptoAlgorithm(algorithm) },
};
const importedKey = await this.subtle.importKey('raw', key, signingAlgorithm, false, ['sign']);
return await this.subtle.sign(signingAlgorithm, importedKey, value);
}
private toBuf(value: string | ArrayBuffer): ArrayBuffer { private toBuf(value: string | ArrayBuffer): ArrayBuffer {
let buf: ArrayBuffer; let buf: ArrayBuffer;
if (typeof (value) === 'string') { if (typeof (value) === 'string') {
@ -94,4 +113,8 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
const b64 = forge.util.encode64(byteString); const b64 = forge.util.encode64(byteString);
return UtilsService.fromB64ToArray(b64).buffer; return UtilsService.fromB64ToArray(b64).buffer;
} }
private toWebCryptoAlgorithm(algorithm: 'sha1' | 'sha256' | 'sha512'): string {
return algorithm === 'sha1' ? 'SHA-1' : algorithm === 'sha256' ? 'SHA-256' : 'SHA-512';
}
} }