From eb72d4dd63447745ecadf6cd2e05fe79fa4ea77f Mon Sep 17 00:00:00 2001 From: Daniel Huigens Date: Mon, 18 Jun 2018 12:54:57 +0200 Subject: [PATCH] Don't use asmcrypto's streaming API when not necessary Optimization for Firefox --- src/crypto/hash/index.js | 12 +++++++---- src/message.js | 12 +++++------ src/openpgp.js | 8 ++++---- src/packet/signature.js | 8 ++++++-- .../sym_encrypted_integrity_protected.js | 20 ++++++++++++------- 5 files changed, 37 insertions(+), 23 deletions(-) diff --git a/src/crypto/hash/index.js b/src/crypto/hash/index.js index c897d930..92a0987a 100644 --- a/src/crypto/hash/index.js +++ b/src/crypto/hash/index.js @@ -43,10 +43,14 @@ function hashjs_hash(hash) { function asmcrypto_hash(hash) { return function(data) { - const hashInstance = new hash(); - return stream.transform(data, value => { - hashInstance.process(value); - }, () => hashInstance.finish().result); + if (util.isStream(data)) { + const hashInstance = new hash(); + return stream.transform(data, value => { + hashInstance.process(value); + }, () => hashInstance.finish().result); + } else { + return hash.bytes(data); + } }; } diff --git a/src/message.js b/src/message.js index 1516a07e..a2f212a8 100644 --- a/src/message.js +++ b/src/message.js @@ -99,7 +99,7 @@ Message.prototype.getSigningKeyIds = function() { * @returns {Promise} new message with decrypted content * @async */ -Message.prototype.decrypt = async function(privateKeys, passwords, sessionKeys) { +Message.prototype.decrypt = async function(privateKeys, passwords, sessionKeys, asStream) { const keyObjs = sessionKeys || await this.decryptSessionKeys(privateKeys, passwords); const symEncryptedPacketlist = this.packets.filterByTag( @@ -120,7 +120,7 @@ Message.prototype.decrypt = async function(privateKeys, passwords, sessionKeys) } try { - await symEncryptedPacket.decrypt(keyObjs[i].algorithm, keyObjs[i].data); + await symEncryptedPacket.decrypt(keyObjs[i].algorithm, keyObjs[i].data, asStream); break; } catch (e) { util.print_debug_error(e); @@ -260,7 +260,7 @@ Message.prototype.getText = function() { * @returns {Promise} new message with encrypted content * @async */ -Message.prototype.encrypt = async function(keys, passwords, sessionKey, wildcard=false, date=new Date(), userId={}) { +Message.prototype.encrypt = async function(keys, passwords, sessionKey, wildcard=false, date=new Date(), userId={}, asStream) { let symAlgo; let aeadAlgo; let symEncryptedPacket; @@ -300,7 +300,7 @@ Message.prototype.encrypt = async function(keys, passwords, sessionKey, wildcard } symEncryptedPacket.packets = this.packets; - await symEncryptedPacket.encrypt(symAlgo, sessionKey); + await symEncryptedPacket.encrypt(symAlgo, sessionKey, asStream); msg.packets.push(symEncryptedPacket); symEncryptedPacket.packets = new packet.List(); // remove packets after encryption @@ -536,7 +536,7 @@ export async function createSignaturePackets(literalDataPacket, privateKeys, sig * @returns {Promise>} list of signer's keyid and validity of signature * @async */ -Message.prototype.verify = async function(keys, date=new Date()) { +Message.prototype.verify = async function(keys, date=new Date(), asStream) { const msg = this.unwrapCompressed(); const literalDataList = msg.packets.filterByTag(enums.packet.literal); if (literalDataList.length !== 1) { @@ -548,7 +548,7 @@ Message.prototype.verify = async function(keys, date=new Date()) { onePassSig.signatureData = stream.fromAsync(() => new Promise(resolve => { onePassSig.signatureDataResolve = resolve; })); - onePassSig.hashed = onePassSig.hash(literalDataList[0]); + onePassSig.hashed = onePassSig.hash(literalDataList[0], undefined, asStream); }); return stream.transform(msg.packets.stream, signature => { const onePassSig = onePassSigList.pop(); diff --git a/src/openpgp.js b/src/openpgp.js index cf2afc09..42cad0a3 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -323,7 +323,7 @@ export function encrypt({ data, dataType, publicKeys, privateKeys, passwords, se } } message = message.compress(compression); - return message.encrypt(publicKeys, passwords, sessionKey, wildcard, date, toUserId); + return message.encrypt(publicKeys, passwords, sessionKey, wildcard, date, toUserId, asStream); }).then(async encrypted => { if (armor) { @@ -364,13 +364,13 @@ export function decrypt({ message, privateKeys, passwords, sessionKeys, publicKe return asyncProxy.delegate('decrypt', { message, privateKeys, passwords, sessionKeys, publicKeys, format, asStream, signature, date }); } - return message.decrypt(privateKeys, passwords, sessionKeys).then(async function(decrypted) { + return message.decrypt(privateKeys, passwords, sessionKeys, asStream).then(async function(decrypted) { if (!publicKeys) { publicKeys = []; } const result = {}; - result.signatures = signature ? await decrypted.verifyDetached(signature, publicKeys, date) : await decrypted.verify(publicKeys, date); + result.signatures = signature ? await decrypted.verifyDetached(signature, publicKeys, date) : await decrypted.verify(publicKeys, date, asStream); result.data = format === 'binary' ? decrypted.getLiteralData() : decrypted.getText(); result.data = await convertStream(result.data, asStream); result.signatures = await convertStreamArray(result.signatures, asStream); @@ -462,7 +462,7 @@ export function verify({ message, publicKeys, asStream, signature=null, date=new return Promise.resolve().then(async function() { const result = {}; - result.signatures = signature ? await message.verifyDetached(signature, publicKeys, date) : await message.verify(publicKeys, date); + result.signatures = signature ? await message.verifyDetached(signature, publicKeys, date) : await message.verify(publicKeys, date, asStream); result.data = message instanceof CleartextMessage ? message.getText() : message.getLiteralData(); result.data = await convertStream(result.data, asStream); result.signatures = await convertStreamArray(result.signatures, asStream); diff --git a/src/packet/signature.js b/src/packet/signature.js index 436cf1e6..e6aada70 100644 --- a/src/packet/signature.js +++ b/src/packet/signature.js @@ -658,9 +658,13 @@ Signature.prototype.toHash = function(data) { } }; -Signature.prototype.hash = function(data, toHash) { +Signature.prototype.hash = function(data, toHash, asStream=true) { const hashAlgorithm = enums.write(enums.hash, this.hashAlgorithm); - return crypto.hash.digest(hashAlgorithm, toHash || this.toHash(data)); + if (!toHash) toHash = this.toHash(data); + if (!asStream && util.isStream(toHash)) { + return stream.fromAsync(async () => this.hash(data, await stream.readToEnd(toHash))); + } + return crypto.hash.digest(hashAlgorithm, toHash); }; diff --git a/src/packet/sym_encrypted_integrity_protected.js b/src/packet/sym_encrypted_integrity_protected.js index 001dd0e2..a142a61a 100644 --- a/src/packet/sym_encrypted_integrity_protected.js +++ b/src/packet/sym_encrypted_integrity_protected.js @@ -24,7 +24,7 @@ * @requires util */ -import { AES_CFB_Decrypt, AES_CFB_Encrypt } from 'asmcrypto.js/src/aes/cfb/exports'; +import { AES_CFB, AES_CFB_Decrypt, AES_CFB_Encrypt } from 'asmcrypto.js/src/aes/cfb/exports'; import config from '../config'; import crypto from '../crypto'; @@ -90,8 +90,9 @@ SymEncryptedIntegrityProtected.prototype.write = function () { * @returns {Promise} * @async */ -SymEncryptedIntegrityProtected.prototype.encrypt = async function (sessionKeyAlgorithm, key) { - const bytes = this.packets.write(); +SymEncryptedIntegrityProtected.prototype.encrypt = async function (sessionKeyAlgorithm, key, asStream) { + let bytes = this.packets.write(); + if (!asStream) bytes = await stream.readToEnd(bytes); const prefixrandom = await crypto.getPrefixRandom(sessionKeyAlgorithm); const repeat = new Uint8Array([prefixrandom[prefixrandom.length - 2], prefixrandom[prefixrandom.length - 1]]); const prefix = util.concat([prefixrandom, repeat]); @@ -118,12 +119,13 @@ SymEncryptedIntegrityProtected.prototype.encrypt = async function (sessionKeyAlg * @returns {Promise} * @async */ -SymEncryptedIntegrityProtected.prototype.decrypt = async function (sessionKeyAlgorithm, key) { +SymEncryptedIntegrityProtected.prototype.decrypt = async function (sessionKeyAlgorithm, key, asStream) { + if (!asStream) this.encrypted = await stream.readToEnd(this.encrypted); const encrypted = stream.clone(this.encrypted); const encryptedClone = stream.passiveClone(encrypted); let decrypted; if (sessionKeyAlgorithm.substr(0, 3) === 'aes') { // AES optimizations. Native code for node, asmCrypto for browser. - decrypted = aesDecrypt(sessionKeyAlgorithm, encrypted, key); + decrypted = aesDecrypt(sessionKeyAlgorithm, encrypted, key, asStream); } else { decrypted = crypto.cfb.decrypt(sessionKeyAlgorithm, key, await stream.readToEnd(encrypted), false); } @@ -176,8 +178,12 @@ function aesDecrypt(algo, ct, key) { if (nodeCrypto) { // Node crypto library. pt = nodeDecrypt(algo, ct, key); } else { // asm.js fallback - const cfb = new AES_CFB_Decrypt(key); - pt = stream.transform(ct, value => cfb.process(value).result, () => cfb.finish().result); + if (util.isStream(ct)) { + const cfb = new AES_CFB_Decrypt(key); + pt = stream.transform(ct, value => cfb.process(value).result, () => cfb.finish().result); + } else { + pt = AES_CFB.decrypt(ct, key); + } } return stream.slice(pt, crypto.cipher[algo].blockSize + 2); // Remove random prefix }