From cd2bfca5199936a3ec4a140265dfb5ce1e4615ea Mon Sep 17 00:00:00 2001 From: Daniel Huigens Date: Fri, 8 Nov 2019 19:12:58 +0100 Subject: [PATCH 1/3] Optimize iterated S2K --- src/type/s2k.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/type/s2k.js b/src/type/s2k.js index 8359547d..e7771763 100644 --- a/src/type/s2k.js +++ b/src/type/s2k.js @@ -168,13 +168,15 @@ S2K.prototype.produce_key = async function (passphrase, numBytes) { case 'iterated': { const count = s2k.get_count(); const data = util.concatUint8Array([s2k.salt, passphrase]); - const datalen = data.length; - const isp = new Uint8Array(prefix.length + count + datalen); + let datalen = data.length; + const prefixlen = prefix.length; + const isp = new Uint8Array(prefixlen + count); isp.set(prefix); - for (let pos = prefix.length; pos < count; pos += datalen) { - isp.set(data, pos); + isp.set(data, prefixlen); + for (let pos = prefixlen + datalen; pos < count; pos += datalen, datalen *= 2) { + isp.copyWithin(pos, prefixlen, pos); } - return crypto.hash.digest(algorithm, isp.subarray(0, prefix.length + count)); + return crypto.hash.digest(algorithm, isp); } case 'gnu': throw new Error("GNU s2k type not supported."); From 6ddfca5f1452bfd4c2f77b7155ce7ffa6601bc2a Mon Sep 17 00:00:00 2001 From: Daniel Huigens Date: Fri, 8 Nov 2019 19:45:57 +0100 Subject: [PATCH 2/3] Refactor S2K function --- src/type/s2k.js | 70 ++++++++++++++++++++----------------------------- 1 file changed, 28 insertions(+), 42 deletions(-) diff --git a/src/type/s2k.js b/src/type/s2k.js index e7771763..dac6d817 100644 --- a/src/type/s2k.js +++ b/src/type/s2k.js @@ -151,55 +151,41 @@ S2K.prototype.write = function () { */ S2K.prototype.produce_key = async function (passphrase, numBytes) { passphrase = util.encode_utf8(passphrase); - - async function round(prefix, s2k) { - const algorithm = enums.write(enums.hash, s2k.algorithm); - - switch (s2k.type) { - case 'simple': - return crypto.hash.digest(algorithm, util.concatUint8Array([prefix, passphrase])); - - case 'salted': - return crypto.hash.digest( - algorithm, - util.concatUint8Array([prefix, s2k.salt, passphrase]) - ); - - case 'iterated': { - const count = s2k.get_count(); - const data = util.concatUint8Array([s2k.salt, passphrase]); - let datalen = data.length; - const prefixlen = prefix.length; - const isp = new Uint8Array(prefixlen + count); - isp.set(prefix); - isp.set(data, prefixlen); - for (let pos = prefixlen + datalen; pos < count; pos += datalen, datalen *= 2) { - isp.copyWithin(pos, prefixlen, pos); - } - return crypto.hash.digest(algorithm, isp); - } - case 'gnu': - throw new Error("GNU s2k type not supported."); - - default: - throw new Error("Unknown s2k type."); - } - } + const algorithm = enums.write(enums.hash, this.algorithm); const arr = []; let rlength = 0; - const prefix = new Uint8Array(numBytes); - for (let i = 0; i < numBytes; i++) { - prefix[i] = 0; - } - - let i = 0; + let prefixlen = 0; while (rlength < numBytes) { - const result = await round(prefix.subarray(0, i), this); + let toHash; + switch (this.type) { + case 'simple': + toHash = util.concatUint8Array([new Uint8Array(prefixlen), passphrase]); + break; + case 'salted': + toHash = util.concatUint8Array([new Uint8Array(prefixlen), this.salt, passphrase]); + break; + case 'iterated': { + const count = this.get_count(); + const data = util.concatUint8Array([this.salt, passphrase]); + let datalen = data.length; + toHash = new Uint8Array(prefixlen + count); + toHash.set(data, prefixlen); + for (let pos = prefixlen + datalen; pos < count; pos += datalen, datalen *= 2) { + toHash.copyWithin(pos, prefixlen, pos); + } + break; + } + case 'gnu': + throw new Error("GNU s2k type not supported."); + default: + throw new Error("Unknown s2k type."); + } + const result = await crypto.hash.digest(algorithm, toHash); arr.push(result); rlength += result.length; - i++; + prefixlen++; } return util.concatUint8Array(arr).subarray(0, numBytes); From b0914663dd86d2a1c42fe4a61af48c7d9c56a05c Mon Sep 17 00:00:00 2001 From: Daniel Huigens Date: Fri, 8 Nov 2019 20:15:31 +0100 Subject: [PATCH 3/3] Iterated S2K: always hash the full salt+password at least once As per the spec: The one exception is that if the octet count is less than the size of the salt plus passphrase, the full salt plus passphrase will be hashed even though that is greater than the octet count. --- src/type/s2k.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/type/s2k.js b/src/type/s2k.js index dac6d817..2b45d77e 100644 --- a/src/type/s2k.js +++ b/src/type/s2k.js @@ -167,9 +167,9 @@ S2K.prototype.produce_key = async function (passphrase, numBytes) { toHash = util.concatUint8Array([new Uint8Array(prefixlen), this.salt, passphrase]); break; case 'iterated': { - const count = this.get_count(); const data = util.concatUint8Array([this.salt, passphrase]); let datalen = data.length; + const count = Math.max(this.get_count(), datalen); toHash = new Uint8Array(prefixlen + count); toHash.set(data, prefixlen); for (let pos = prefixlen + datalen; pos < count; pos += datalen, datalen *= 2) {