/**
* @license
* Copyright The Closure Library Authors.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Implementation of PBKDF2 in JavaScript.
* @see http://en.wikipedia.org/wiki/PBKDF2
*
* Currently we only support HMAC-SHA1 as the underlying hash function. To add a
* new hash function, add a static method similar to deriveKeyFromPasswordSha1()
* and implement the specific computeBlockCallback() using the hash function.
*
* Usage:
* var key = pbkdf2.deriveKeySha1(
* stringToByteArray('password'), stringToByteArray('salt'), 1000, 128);
*/
goog.provide('goog.crypt.pbkdf2');
goog.require('goog.asserts');
goog.require('goog.crypt');
goog.require('goog.crypt.Hmac');
goog.require('goog.crypt.Sha1');
/**
* Derives key from password using PBKDF2-SHA1
* @param {!Array<number>} password Byte array representation of the password
* from which the key is derived.
* @param {!Array<number>} initialSalt Byte array representation of the salt.
* @param {number} iterations Number of interations when computing the key.
* @param {number} keyLength Length of the output key in bits.
* Must be multiple of 8.
* @return {!Array<number>} Byte array representation of the output key.
*/
goog.crypt.pbkdf2.deriveKeySha1 = function(
password, initialSalt, iterations, keyLength) {
'use strict';
// Length of the HMAC-SHA1 output in bits.
var HASH_LENGTH = 160;
/**
* Compute each block of the key using HMAC-SHA1.
* @param {!Array<number>} index Byte array representation of the index of
* the block to be computed.
* @return {!Array<number>} Byte array representation of the output block.
*/
var computeBlock = function(index) {
'use strict';
// Initialize the result to be array of 0 such that its xor with the first
// block would be the first block.
var result = (new Array(HASH_LENGTH / 8)).fill(0);
// Initialize the salt of the first iteration to initialSalt || i.
var salt = initialSalt.concat(index);
var hmac = new goog.crypt.Hmac(new goog.crypt.Sha1(), password, 64);
// Compute and XOR each iteration.
for (var i = 0; i < iterations; i++) {
// The salt of the next iteration is the result of the current iteration.
salt = hmac.getHmac(salt);
result = goog.crypt.xorByteArray(result, salt);
}
return result;
};
return goog.crypt.pbkdf2.deriveKeyFromPassword_(
computeBlock, HASH_LENGTH, keyLength);
};
/**
* Compute each block of the key using PBKDF2.
* @param {Function} computeBlock Function to compute each block of the output
* key.
* @param {number} hashLength Length of each block in bits. This is determined
* by the specific hash function used. Must be multiple of 8.
* @param {number} keyLength Length of the output key in bits.
* Must be multiple of 8.
* @return {!Array<number>} Byte array representation of the output key.
* @private
*/
goog.crypt.pbkdf2.deriveKeyFromPassword_ = function(
computeBlock, hashLength, keyLength) {
'use strict';
goog.asserts.assert(keyLength % 8 == 0, 'invalid output key length');
// Compute and concactate each block of the output key.
var numBlocks = Math.ceil(keyLength / hashLength);
goog.asserts.assert(numBlocks >= 1, 'invalid number of blocks');
var result = [];
for (var i = 1; i <= numBlocks; i++) {
var indexBytes = goog.crypt.pbkdf2.integerToByteArray_(i);
result = result.concat(computeBlock(indexBytes));
}
// Trim the last block if needed.
var lastBlockSize = keyLength % hashLength;
if (lastBlockSize != 0) {
var desiredBytes = ((numBlocks - 1) * hashLength + lastBlockSize) / 8;
result.splice(desiredBytes, (hashLength - lastBlockSize) / 8);
}
return result;
};
/**
* Converts an integer number to a 32-bit big endian byte array.
* @param {number} n Integer number to be converted.
* @return {!Array<number>} Byte Array representation of the 32-bit big endian
* encoding of n.
* @private
*/
goog.crypt.pbkdf2.integerToByteArray_ = function(n) {
'use strict';
var result = new Array(4);
result[0] = n >> 24 & 0xFF;
result[1] = n >> 16 & 0xFF;
result[2] = n >> 8 & 0xFF;
result[3] = n & 0xFF;
return result;
};