mirror of
https://github.com/openpgpjs/openpgpjs.git
synced 2025-06-24 07:02:30 +00:00
Drop asmcrypto.js for AES fallbacks in favor of noble-ciphers (#1785)
Asm.js has now been deprecated for many years, and no performance gain is recorded for AES compared to vanilla JS. The relevant AES fallback code is primarily used if the WebCrypto (resp. NodeCrypto) implementation is not available.
This commit is contained in:
parent
79014f00f0
commit
5fd7ef370f
29
package-lock.json
generated
29
package-lock.json
generated
@ -9,9 +9,9 @@
|
||||
"version": "6.0.0-beta.2",
|
||||
"license": "LGPL-3.0+",
|
||||
"devDependencies": {
|
||||
"@noble/ciphers": "^0.6.0",
|
||||
"@noble/curves": "^1.4.0",
|
||||
"@noble/hashes": "^1.4.0",
|
||||
"@openpgp/asmcrypto.js": "^3.1.0",
|
||||
"@openpgp/jsdoc": "^3.6.11",
|
||||
"@openpgp/seek-bzip": "^1.0.5-git",
|
||||
"@openpgp/tweetnacl": "^1.0.4-1",
|
||||
@ -801,6 +801,15 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/ciphers": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.6.0.tgz",
|
||||
"integrity": "sha512-mIbq/R9QXk5/cTfESb1OKtyFnk7oc1Om/8onA1158K9/OZUQFDEVy55jVTato+xmp3XX6F6Qh0zz0Nc1AxAlRQ==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/curves": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.0.tgz",
|
||||
@ -860,12 +869,6 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@openpgp/asmcrypto.js": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@openpgp/asmcrypto.js/-/asmcrypto.js-3.1.0.tgz",
|
||||
"integrity": "sha512-LlQZE/Vtkx/KFnJxg7BB0iwD7oYKDeC8eRECHxKLhYyL2Ad0+xT137VZwv8SZTJB2euPqpx7xkj04ieV0Q665w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@openpgp/jsdoc": {
|
||||
"version": "3.6.11",
|
||||
"resolved": "https://registry.npmjs.org/@openpgp/jsdoc/-/jsdoc-3.6.11.tgz",
|
||||
@ -9041,6 +9044,12 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"@noble/ciphers": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.6.0.tgz",
|
||||
"integrity": "sha512-mIbq/R9QXk5/cTfESb1OKtyFnk7oc1Om/8onA1158K9/OZUQFDEVy55jVTato+xmp3XX6F6Qh0zz0Nc1AxAlRQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@noble/curves": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.0.tgz",
|
||||
@ -9082,12 +9091,6 @@
|
||||
"fastq": "^1.6.0"
|
||||
}
|
||||
},
|
||||
"@openpgp/asmcrypto.js": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@openpgp/asmcrypto.js/-/asmcrypto.js-3.1.0.tgz",
|
||||
"integrity": "sha512-LlQZE/Vtkx/KFnJxg7BB0iwD7oYKDeC8eRECHxKLhYyL2Ad0+xT137VZwv8SZTJB2euPqpx7xkj04ieV0Q665w==",
|
||||
"dev": true
|
||||
},
|
||||
"@openpgp/jsdoc": {
|
||||
"version": "3.6.11",
|
||||
"resolved": "https://registry.npmjs.org/@openpgp/jsdoc/-/jsdoc-3.6.11.tgz",
|
||||
|
@ -62,9 +62,9 @@
|
||||
"postversion": "git push && git push --tags && npm publish"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@noble/ciphers": "^0.6.0",
|
||||
"@noble/curves": "^1.4.0",
|
||||
"@noble/hashes": "^1.4.0",
|
||||
"@openpgp/asmcrypto.js": "^3.1.0",
|
||||
"@openpgp/jsdoc": "^3.6.11",
|
||||
"@openpgp/seek-bzip": "^1.0.5-git",
|
||||
"@openpgp/tweetnacl": "^1.0.4-1",
|
||||
|
@ -21,7 +21,7 @@
|
||||
* @module crypto/aes_kw
|
||||
*/
|
||||
|
||||
import { AES_CBC } from '@openpgp/asmcrypto.js/aes/cbc.js';
|
||||
import { aeskw as nobleAesKW } from '@noble/ciphers/aes';
|
||||
import { getCipherParams } from './cipher';
|
||||
import util from '../util';
|
||||
|
||||
@ -55,7 +55,7 @@ export async function wrap(algo, key, dataToWrap) {
|
||||
util.printDebugError('Browser did not support operation: ' + err.message);
|
||||
}
|
||||
|
||||
return asmcryptoWrap(algo, key, dataToWrap);
|
||||
return nobleAesKW(key).encrypt(dataToWrap);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -82,7 +82,7 @@ export async function unwrap(algo, key, wrappedData) {
|
||||
throw err;
|
||||
}
|
||||
util.printDebugError('Browser did not support operation: ' + err.message);
|
||||
return asmcryptoUnwrap(algo, key, wrappedData);
|
||||
return nobleAesKW(key).decrypt(wrappedData);
|
||||
}
|
||||
|
||||
try {
|
||||
@ -95,95 +95,3 @@ export async function unwrap(algo, key, wrappedData) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function asmcryptoWrap(aesAlgo, key, data) {
|
||||
const aesInstance = new AES_CBC(key, new Uint8Array(16), false);
|
||||
const IV = new Uint32Array([0xA6A6A6A6, 0xA6A6A6A6]);
|
||||
const P = unpack(data);
|
||||
let A = IV;
|
||||
const R = P;
|
||||
const n = P.length / 2;
|
||||
const t = new Uint32Array([0, 0]);
|
||||
let B = new Uint32Array(4);
|
||||
for (let j = 0; j <= 5; ++j) {
|
||||
for (let i = 0; i < n; ++i) {
|
||||
t[1] = n * j + (1 + i);
|
||||
// B = A
|
||||
B[0] = A[0];
|
||||
B[1] = A[1];
|
||||
// B = A || R[i]
|
||||
B[2] = R[2 * i];
|
||||
B[3] = R[2 * i + 1];
|
||||
// B = AES(K, B)
|
||||
B = unpack(aesInstance.encrypt(pack(B)));
|
||||
// A = MSB(64, B) ^ t
|
||||
A = B.subarray(0, 2);
|
||||
A[0] ^= t[0];
|
||||
A[1] ^= t[1];
|
||||
// R[i] = LSB(64, B)
|
||||
R[2 * i] = B[2];
|
||||
R[2 * i + 1] = B[3];
|
||||
}
|
||||
}
|
||||
return pack(A, R);
|
||||
}
|
||||
|
||||
function asmcryptoUnwrap(aesAlgo, key, data) {
|
||||
const aesInstance = new AES_CBC(key, new Uint8Array(16), false);
|
||||
const IV = new Uint32Array([0xA6A6A6A6, 0xA6A6A6A6]);
|
||||
const C = unpack(data);
|
||||
let A = C.subarray(0, 2);
|
||||
const R = C.subarray(2);
|
||||
const n = C.length / 2 - 1;
|
||||
const t = new Uint32Array([0, 0]);
|
||||
let B = new Uint32Array(4);
|
||||
for (let j = 5; j >= 0; --j) {
|
||||
for (let i = n - 1; i >= 0; --i) {
|
||||
t[1] = n * j + (i + 1);
|
||||
// B = A ^ t
|
||||
B[0] = A[0] ^ t[0];
|
||||
B[1] = A[1] ^ t[1];
|
||||
// B = (A ^ t) || R[i]
|
||||
B[2] = R[2 * i];
|
||||
B[3] = R[2 * i + 1];
|
||||
// B = AES-1(B)
|
||||
B = unpack(aesInstance.decrypt(pack(B)));
|
||||
// A = MSB(64, B)
|
||||
A = B.subarray(0, 2);
|
||||
// R[i] = LSB(64, B)
|
||||
R[2 * i] = B[2];
|
||||
R[2 * i + 1] = B[3];
|
||||
}
|
||||
}
|
||||
if (A[0] === IV[0] && A[1] === IV[1]) {
|
||||
return pack(R);
|
||||
}
|
||||
throw new Error('Key Data Integrity failed');
|
||||
}
|
||||
|
||||
function unpack(data) {
|
||||
const buffer = data.buffer;
|
||||
const view = new DataView(buffer);
|
||||
const arr = new Uint32Array(data.length / 4);
|
||||
for (let i = 0; i < data.length / 4; ++i) {
|
||||
arr[i] = view.getUint32(4 * i);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
function pack() {
|
||||
let length = 0;
|
||||
for (let k = 0; k < arguments.length; ++k) {
|
||||
length += 4 * arguments[k].length;
|
||||
}
|
||||
const buffer = new ArrayBuffer(length);
|
||||
const view = new DataView(buffer);
|
||||
let offset = 0;
|
||||
for (let i = 0; i < arguments.length; ++i) {
|
||||
for (let j = 0; j < arguments[i].length; ++j) {
|
||||
view.setUint32(offset + 4 * j, arguments[i][j]);
|
||||
}
|
||||
offset += 4 * arguments[i].length;
|
||||
}
|
||||
return new Uint8Array(buffer);
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
* @module crypto/cmac
|
||||
*/
|
||||
|
||||
import { AES_CBC } from '@openpgp/asmcrypto.js/aes/cbc.js';
|
||||
import { cbc as nobleAesCbc } from '@noble/ciphers/aes';
|
||||
import util from '../util';
|
||||
|
||||
const webCrypto = util.getWebCrypto();
|
||||
@ -97,8 +97,7 @@ async function CBC(key) {
|
||||
}
|
||||
}
|
||||
|
||||
// asm.js fallback
|
||||
return async function(pt) {
|
||||
return AES_CBC.encrypt(pt, key, false, zeroBlock);
|
||||
return nobleAesCbc(key, zeroBlock, { disablePadding: true }).encrypt(pt);
|
||||
};
|
||||
}
|
||||
|
@ -21,7 +21,8 @@
|
||||
* @module crypto/mode/cfb
|
||||
*/
|
||||
|
||||
import { AES_CFB } from '@openpgp/asmcrypto.js/aes/cfb.js';
|
||||
import { cfb as nobleAesCfb, unsafe as nobleAesHelpers } from '@noble/ciphers/aes';
|
||||
|
||||
import * as stream from '@openpgp/web-stream-tools';
|
||||
import util from '../../util';
|
||||
import enums from '../../enums';
|
||||
@ -174,17 +175,17 @@ class WebCryptoEncryptor {
|
||||
|
||||
const encryptedBlocks = await this._runCBC(toEncrypt);
|
||||
xorMut(encryptedBlocks, plaintext);
|
||||
this.prevBlock = encryptedBlocks.subarray(-this.blockSize).slice();
|
||||
this.prevBlock = encryptedBlocks.slice(-this.blockSize);
|
||||
|
||||
// take care of leftover data
|
||||
if (leftover > 0) this.nextBlock.set(value.subarray(-leftover).slice());
|
||||
if (leftover > 0) this.nextBlock.set(value.subarray(-leftover));
|
||||
this.i = leftover;
|
||||
|
||||
return encryptedBlocks;
|
||||
}
|
||||
|
||||
this.i += added.length;
|
||||
let encryptedBlock = new Uint8Array();
|
||||
let encryptedBlock;
|
||||
if (this.i === this.nextBlock.length) { // block ready to be encrypted
|
||||
const curBlock = this.nextBlock;
|
||||
encryptedBlock = await this._runCBC(this.prevBlock);
|
||||
@ -195,6 +196,8 @@ class WebCryptoEncryptor {
|
||||
const remaining = value.subarray(added.length);
|
||||
this.nextBlock.set(remaining, this.i);
|
||||
this.i += remaining.length;
|
||||
} else {
|
||||
encryptedBlock = new Uint8Array();
|
||||
}
|
||||
|
||||
return encryptedBlock;
|
||||
@ -237,22 +240,111 @@ class WebCryptoEncryptor {
|
||||
}
|
||||
}
|
||||
|
||||
class NobleStreamProcessor {
|
||||
constructor(forEncryption, algo, key, iv) {
|
||||
this.forEncryption = forEncryption;
|
||||
const { blockSize } = getCipherParams(algo);
|
||||
this.key = nobleAesHelpers.expandKeyLE(key);
|
||||
|
||||
if (iv.byteOffset % 4 !== 0) iv = iv.slice(); // aligned arrays required by noble-ciphers
|
||||
this.prevBlock = getUint32Array(iv);
|
||||
this.nextBlock = new Uint8Array(blockSize);
|
||||
this.i = 0; // pointer inside next block
|
||||
this.blockSize = blockSize;
|
||||
}
|
||||
|
||||
_runCFB(src) {
|
||||
const src32 = getUint32Array(src);
|
||||
const dst = new Uint8Array(src.length);
|
||||
const dst32 = getUint32Array(dst);
|
||||
for (let i = 0; i + 4 <= dst32.length; i += 4) {
|
||||
const { s0: e0, s1: e1, s2: e2, s3: e3 } = nobleAesHelpers.encrypt(this.key, this.prevBlock[0], this.prevBlock[1], this.prevBlock[2], this.prevBlock[3]);
|
||||
dst32[i + 0] = src32[i + 0] ^ e0;
|
||||
dst32[i + 1] = src32[i + 1] ^ e1;
|
||||
dst32[i + 2] = src32[i + 2] ^ e2;
|
||||
dst32[i + 3] = src32[i + 3] ^ e3;
|
||||
this.prevBlock = (this.forEncryption ? dst32 : src32).slice(i, i + 4);
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
async processChunk(value) {
|
||||
const missing = this.nextBlock.length - this.i;
|
||||
const added = value.subarray(0, missing);
|
||||
this.nextBlock.set(added, this.i);
|
||||
|
||||
if ((this.i + value.length) >= (2 * this.blockSize)) {
|
||||
const leftover = (value.length - missing) % this.blockSize;
|
||||
const toProcess = util.concatUint8Array([
|
||||
this.nextBlock,
|
||||
value.subarray(missing, value.length - leftover)
|
||||
]);
|
||||
|
||||
const processedBlocks = this._runCFB(toProcess);
|
||||
|
||||
// take care of leftover data
|
||||
if (leftover > 0) this.nextBlock.set(value.subarray(-leftover));
|
||||
this.i = leftover;
|
||||
|
||||
return processedBlocks;
|
||||
}
|
||||
|
||||
this.i += added.length;
|
||||
|
||||
let processedBlock;
|
||||
if (this.i === this.nextBlock.length) { // block ready to be encrypted
|
||||
processedBlock = this._runCFB(this.nextBlock);
|
||||
this.i = 0;
|
||||
|
||||
const remaining = value.subarray(added.length);
|
||||
this.nextBlock.set(remaining, this.i);
|
||||
this.i += remaining.length;
|
||||
} else {
|
||||
processedBlock = new Uint8Array();
|
||||
}
|
||||
|
||||
return processedBlock;
|
||||
}
|
||||
|
||||
async finish() {
|
||||
let result;
|
||||
if (this.i === 0) { // nothing more to encrypt
|
||||
result = new Uint8Array();
|
||||
} else {
|
||||
const processedBlock = this._runCFB(this.nextBlock);
|
||||
|
||||
result = processedBlock.subarray(0, this.i);
|
||||
}
|
||||
|
||||
this.clearSensitiveData();
|
||||
return result;
|
||||
}
|
||||
|
||||
clearSensitiveData() {
|
||||
this.nextBlock.fill(0);
|
||||
this.prevBlock.fill(0);
|
||||
this.key.fill(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function aesEncrypt(algo, key, pt, iv) {
|
||||
if (webCrypto && await WebCryptoEncryptor.isSupported(algo)) { // Chromium does not implement AES with 192-bit keys
|
||||
const cfb = new WebCryptoEncryptor(algo, key, iv);
|
||||
return util.isStream(pt) ? stream.transform(pt, value => cfb.encryptChunk(value), () => cfb.finish()) : cfb.encrypt(pt);
|
||||
} else {
|
||||
const cfb = new AES_CFB(key, iv);
|
||||
return stream.transform(pt, value => cfb.aes.AES_Encrypt_process(value), () => cfb.aes.AES_Encrypt_finish());
|
||||
} else if (util.isStream(pt)) { // async callbacks are not accepted by stream.transform unless the input is a stream
|
||||
const cfb = new NobleStreamProcessor(true, algo, key, iv);
|
||||
return stream.transform(pt, value => cfb.processChunk(value), () => cfb.finish());
|
||||
}
|
||||
return nobleAesCfb(key, iv).encrypt(pt);
|
||||
}
|
||||
|
||||
function aesDecrypt(algo, key, ct, iv) {
|
||||
async function aesDecrypt(algo, key, ct, iv) {
|
||||
if (util.isStream(ct)) {
|
||||
const cfb = new AES_CFB(key, iv);
|
||||
return stream.transform(ct, value => cfb.aes.AES_Decrypt_process(value), () => cfb.aes.AES_Decrypt_finish());
|
||||
const cfb = new NobleStreamProcessor(false, algo, key, iv);
|
||||
return stream.transform(ct, value => cfb.processChunk(value), () => cfb.finish());
|
||||
}
|
||||
return AES_CFB.decrypt(ct, key, iv);
|
||||
return nobleAesCfb(key, iv).decrypt(ct);
|
||||
}
|
||||
|
||||
function xorMut(a, b) {
|
||||
@ -262,6 +354,8 @@ function xorMut(a, b) {
|
||||
}
|
||||
}
|
||||
|
||||
const getUint32Array = arr => new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
|
||||
|
||||
function nodeEncrypt(algo, key, pt, iv) {
|
||||
const algoName = enums.read(enums.symmetric, algo);
|
||||
const cipherObj = new nodeCrypto.createCipheriv(nodeAlgos[algoName], key, iv);
|
||||
|
@ -21,7 +21,7 @@
|
||||
* @module crypto/mode/eax
|
||||
*/
|
||||
|
||||
import { AES_CTR } from '@openpgp/asmcrypto.js/aes/ctr.js';
|
||||
import { ctr as nobleAesCtr } from '@noble/ciphers/aes';
|
||||
import CMAC from '../cmac';
|
||||
import util from '../../util';
|
||||
import enums from '../../enums';
|
||||
@ -72,9 +72,8 @@ async function CTR(key) {
|
||||
}
|
||||
}
|
||||
|
||||
// asm.js fallback
|
||||
return async function(pt, iv) {
|
||||
return AES_CTR.encrypt(pt, key, iv);
|
||||
return nobleAesCtr(key, iv).encrypt(pt);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@
|
||||
* @module crypto/mode/gcm
|
||||
*/
|
||||
|
||||
import { AES_GCM } from '@openpgp/asmcrypto.js/aes/gcm.js';
|
||||
import { gcm as nobleAesGcm } from '@noble/ciphers/aes';
|
||||
import util from '../../util';
|
||||
import enums from '../../enums';
|
||||
|
||||
@ -74,7 +74,7 @@ async function GCM(cipher, key) {
|
||||
return {
|
||||
encrypt: async function(pt, iv, adata = new Uint8Array()) {
|
||||
if (webcryptoEmptyMessagesUnsupported && !pt.length) {
|
||||
return AES_GCM.encrypt(pt, key, iv, adata);
|
||||
return nobleAesGcm(key, iv, adata).encrypt(pt);
|
||||
}
|
||||
const ct = await webCrypto.encrypt({ name: ALGO, iv, additionalData: adata, tagLength: tagLength * 8 }, _key, pt);
|
||||
return new Uint8Array(ct);
|
||||
@ -82,7 +82,7 @@ async function GCM(cipher, key) {
|
||||
|
||||
decrypt: async function(ct, iv, adata = new Uint8Array()) {
|
||||
if (webcryptoEmptyMessagesUnsupported && ct.length === tagLength) {
|
||||
return AES_GCM.decrypt(ct, key, iv, adata);
|
||||
return nobleAesGcm(key, iv, adata).decrypt(ct);
|
||||
}
|
||||
try {
|
||||
const pt = await webCrypto.decrypt({ name: ALGO, iv, additionalData: adata, tagLength: tagLength * 8 }, _key, ct);
|
||||
@ -106,11 +106,11 @@ async function GCM(cipher, key) {
|
||||
|
||||
return {
|
||||
encrypt: async function(pt, iv, adata) {
|
||||
return AES_GCM.encrypt(pt, key, iv, adata);
|
||||
return nobleAesGcm(key, iv, adata).encrypt(pt);
|
||||
},
|
||||
|
||||
decrypt: async function(ct, iv, adata) {
|
||||
return AES_GCM.decrypt(ct, key, iv, adata);
|
||||
return nobleAesGcm(key, iv, adata).decrypt(ct);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -20,7 +20,7 @@
|
||||
* @module crypto/mode/ocb
|
||||
*/
|
||||
|
||||
import { AES_CBC } from '@openpgp/asmcrypto.js/aes/cbc.js';
|
||||
import { cbc as nobleAesCbc } from '@noble/ciphers/aes';
|
||||
import { getCipherParams } from '../cipher';
|
||||
import util from '../../util';
|
||||
|
||||
@ -73,8 +73,9 @@ async function OCB(cipher, key) {
|
||||
// `encipher` and `decipher` cannot be async, since `crypt` shares state across calls,
|
||||
// hence its execution cannot be broken up.
|
||||
// As a result, WebCrypto cannot currently be used for `encipher`.
|
||||
const encipher = block => AES_CBC.encrypt(block, key, false);
|
||||
const decipher = block => AES_CBC.decrypt(block, key, false);
|
||||
const aes = nobleAesCbc(key, zeroBlock, { disablePadding: true });
|
||||
const encipher = block => aes.encrypt(block);
|
||||
const decipher = block => aes.decrypt(block);
|
||||
let mask;
|
||||
|
||||
constructKeyVariables(cipher, key);
|
||||
|
Loading…
x
Reference in New Issue
Block a user