Merge pull request #831 from twiss/web-crypto-cfb
Web Crypto CFB encryption, revision 2
This commit is contained in:
commit
ffeb43ef04
|
@ -15,7 +15,7 @@ matrix:
|
|||
env: OPENPGP_NODE_JS='10' OPENPGPJSTEST='unit'
|
||||
- node_js: "9"
|
||||
env: BROWSER='Firefox' VERSION='26' PLATFORM='macOS 10.13' OPENPGPJSTEST='saucelabs' COMPAT=1
|
||||
- node_js: "9"
|
||||
- node_js: "10"
|
||||
env: BROWSER='Firefox' VERSION='61' PLATFORM='macOS 10.13' OPENPGPJSTEST='saucelabs'
|
||||
- node_js: "9"
|
||||
env: BROWSER='Chrome' VERSION='49' PLATFORM='macOS 10.13' OPENPGPJSTEST='saucelabs' COMPAT=1
|
||||
|
|
|
@ -18,273 +18,68 @@
|
|||
*/
|
||||
|
||||
/**
|
||||
* @requires web-stream-tools
|
||||
* @requires crypto/cipher
|
||||
* @requires util
|
||||
* @module crypto/cfb
|
||||
*/
|
||||
|
||||
import { AES_CFB } from 'asmcrypto.js/dist_es5/aes/cfb';
|
||||
|
||||
import stream from 'web-stream-tools';
|
||||
import cipher from './cipher';
|
||||
import config from '../config';
|
||||
import util from '../util';
|
||||
|
||||
const webCrypto = util.getWebCrypto();
|
||||
const nodeCrypto = util.getNodeCrypto();
|
||||
const Buffer = util.getNodeBuffer();
|
||||
|
||||
export default {
|
||||
|
||||
/**
|
||||
* This function encrypts a given plaintext with the specified prefixrandom
|
||||
* using the specified blockcipher
|
||||
* @param {Uint8Array} prefixrandom random bytes of block_size length
|
||||
* to be used in prefixing the data
|
||||
* @param {String} cipherfn the algorithm cipher class to encrypt
|
||||
* data in one block_size encryption, {@link module:crypto/cipher}.
|
||||
* @param {Uint8Array} plaintext data to be encrypted
|
||||
* @param {Uint8Array} key key to be used to encrypt the plaintext.
|
||||
* This will be passed to the cipherfn
|
||||
* @param {Boolean} resync a boolean value specifying if a resync of the
|
||||
* IV should be used or not. The encrypteddatapacket uses the
|
||||
* "old" style with a resync. Encryption within an
|
||||
* encryptedintegrityprotecteddata packet is not resyncing the IV.
|
||||
* @returns {Uint8Array} encrypted data
|
||||
*/
|
||||
encrypt: function(prefixrandom, cipherfn, plaintext, key, resync) {
|
||||
cipherfn = new cipher[cipherfn](key);
|
||||
const block_size = cipherfn.blockSize;
|
||||
|
||||
const FR = new Uint8Array(block_size);
|
||||
let FRE = new Uint8Array(block_size);
|
||||
|
||||
const new_prefix = new Uint8Array(prefixrandom.length + 2);
|
||||
new_prefix.set(prefixrandom);
|
||||
new_prefix[prefixrandom.length] = prefixrandom[block_size-2];
|
||||
new_prefix[prefixrandom.length+1] = prefixrandom[block_size-1];
|
||||
prefixrandom = new_prefix;
|
||||
|
||||
let ciphertext = new Uint8Array(plaintext.length + 2 + block_size * 2);
|
||||
let i;
|
||||
let n;
|
||||
let begin;
|
||||
const offset = resync ? 0 : 2;
|
||||
|
||||
// 1. The feedback register (FR) is set to the IV, which is all zeros.
|
||||
for (i = 0; i < block_size; i++) {
|
||||
FR[i] = 0;
|
||||
encrypt: function(algo, key, plaintext, iv) {
|
||||
if (algo.substr(0, 3) === 'aes') {
|
||||
return aesEncrypt(algo, key, plaintext, iv);
|
||||
}
|
||||
|
||||
// 2. FR is encrypted to produce FRE (FR Encrypted). This is the
|
||||
// encryption of an all-zero value.
|
||||
FRE = cipherfn.encrypt(FR);
|
||||
// 3. FRE is xored with the first BS octets of random data prefixed to
|
||||
// the plaintext to produce C[1] through C[BS], the first BS octets
|
||||
// of ciphertext.
|
||||
for (i = 0; i < block_size; i++) {
|
||||
ciphertext[i] = FRE[i] ^ prefixrandom[i];
|
||||
}
|
||||
|
||||
// 4. FR is loaded with C[1] through C[BS].
|
||||
FR.set(ciphertext.subarray(0, block_size));
|
||||
|
||||
// 5. FR is encrypted to produce FRE, the encryption of the first BS
|
||||
// octets of ciphertext.
|
||||
FRE = cipherfn.encrypt(FR);
|
||||
|
||||
// 6. The left two octets of FRE get xored with the next two octets of
|
||||
// data that were prefixed to the plaintext. This produces C[BS+1]
|
||||
// and C[BS+2], the next two octets of ciphertext.
|
||||
ciphertext[block_size] = FRE[0] ^ prefixrandom[block_size];
|
||||
ciphertext[block_size + 1] = FRE[1] ^ prefixrandom[block_size + 1];
|
||||
|
||||
if (resync) {
|
||||
// 7. (The resync step) FR is loaded with C[3] through C[BS+2].
|
||||
FR.set(ciphertext.subarray(2, block_size + 2));
|
||||
} else {
|
||||
FR.set(ciphertext.subarray(0, block_size));
|
||||
}
|
||||
// 8. FR is encrypted to produce FRE.
|
||||
FRE = cipherfn.encrypt(FR);
|
||||
|
||||
// 9. FRE is xored with the first BS octets of the given plaintext, now
|
||||
// that we have finished encrypting the BS+2 octets of prefixed
|
||||
// data. This produces C[BS+3] through C[BS+(BS+2)], the next BS
|
||||
// octets of ciphertext.
|
||||
for (i = 0; i < block_size; i++) {
|
||||
ciphertext[block_size + 2 + i] = FRE[i + offset] ^ plaintext[i];
|
||||
}
|
||||
for (n = block_size; n < plaintext.length + offset; n += block_size) {
|
||||
// 10. FR is loaded with C[BS+3] to C[BS + (BS+2)] (which is C11-C18 for
|
||||
// an 8-octet block).
|
||||
begin = n + 2 - offset;
|
||||
FR.set(ciphertext.subarray(begin, begin + block_size));
|
||||
|
||||
// 11. FR is encrypted to produce FRE.
|
||||
FRE = cipherfn.encrypt(FR);
|
||||
|
||||
// 12. FRE is xored with the next BS octets of plaintext, to produce
|
||||
// the next BS octets of ciphertext. These are loaded into FR, and
|
||||
// the process is repeated until the plaintext is used up.
|
||||
for (i = 0; i < block_size; i++) {
|
||||
ciphertext[block_size + begin + i] = FRE[i] ^ plaintext[n + i - offset];
|
||||
}
|
||||
}
|
||||
|
||||
ciphertext = ciphertext.subarray(0, plaintext.length + 2 + block_size);
|
||||
return ciphertext;
|
||||
},
|
||||
|
||||
/**
|
||||
* Decrypts the prefixed data for the Modification Detection Code (MDC) computation
|
||||
* @param {String} cipherfn.encrypt Cipher function to use,
|
||||
* @see module:crypto/cipher.
|
||||
* @param {Uint8Array} key Uint8Array representation of key to be used to check the mdc
|
||||
* This will be passed to the cipherfn
|
||||
* @param {Uint8Array} ciphertext The encrypted data
|
||||
* @returns {Uint8Array} plaintext Data of D(ciphertext) with blocksize length +2
|
||||
*/
|
||||
mdc: function(cipherfn, key, ciphertext) {
|
||||
cipherfn = new cipher[cipherfn](key);
|
||||
const block_size = cipherfn.blockSize;
|
||||
|
||||
let iblock = new Uint8Array(block_size);
|
||||
let ablock = new Uint8Array(block_size);
|
||||
let i;
|
||||
|
||||
|
||||
// initialisation vector
|
||||
for (i = 0; i < block_size; i++) {
|
||||
iblock[i] = 0;
|
||||
}
|
||||
|
||||
iblock = cipherfn.encrypt(iblock);
|
||||
for (i = 0; i < block_size; i++) {
|
||||
ablock[i] = ciphertext[i];
|
||||
iblock[i] ^= ablock[i];
|
||||
}
|
||||
|
||||
ablock = cipherfn.encrypt(ablock);
|
||||
|
||||
const result = new Uint8Array(iblock.length + 2);
|
||||
result.set(iblock);
|
||||
result[iblock.length] = ablock[0] ^ ciphertext[block_size];
|
||||
result[iblock.length + 1] = ablock[1] ^ ciphertext[block_size + 1];
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* This function decrypts a given ciphertext using the specified blockcipher
|
||||
* @param {String} cipherfn the algorithm cipher class to decrypt
|
||||
* data in one block_size encryption, {@link module:crypto/cipher}.
|
||||
* @param {Uint8Array} key Uint8Array representation of key to be used to decrypt the ciphertext.
|
||||
* This will be passed to the cipherfn
|
||||
* @param {Uint8Array} ciphertext to be decrypted
|
||||
* @param {Boolean} resync a boolean value specifying if a resync of the
|
||||
* IV should be used or not. The encrypteddatapacket uses the
|
||||
* "old" style with a resync. Decryption within an
|
||||
* encryptedintegrityprotecteddata packet is not resyncing the IV.
|
||||
* @returns {Uint8Array} the plaintext data
|
||||
*/
|
||||
decrypt: function(cipherfn, key, ciphertext, resync) {
|
||||
cipherfn = new cipher[cipherfn](key);
|
||||
const block_size = cipherfn.blockSize;
|
||||
|
||||
const iblock = new Uint8Array(block_size);
|
||||
let ablock = new Uint8Array(block_size);
|
||||
|
||||
let i;
|
||||
let j;
|
||||
let n;
|
||||
let text = new Uint8Array(ciphertext.length - block_size);
|
||||
|
||||
/* RFC4880: Tag 18 and Resync:
|
||||
* [...] Unlike the Symmetrically Encrypted Data Packet, no
|
||||
* special CFB resynchronization is done after encrypting this prefix
|
||||
* data. See "OpenPGP CFB Mode" below for more details.
|
||||
*/
|
||||
|
||||
j = 0;
|
||||
if (resync) {
|
||||
for (i = 0; i < block_size; i++) {
|
||||
iblock[i] = ciphertext[i + 2];
|
||||
}
|
||||
for (n = block_size + 2; n < ciphertext.length; n += block_size) {
|
||||
ablock = cipherfn.encrypt(iblock);
|
||||
|
||||
for (i = 0; i < block_size && i + n < ciphertext.length; i++) {
|
||||
iblock[i] = ciphertext[n + i];
|
||||
if (j < text.length) {
|
||||
text[j] = ablock[i] ^ iblock[i];
|
||||
j++;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < block_size; i++) {
|
||||
iblock[i] = ciphertext[i];
|
||||
}
|
||||
for (n = block_size; n < ciphertext.length; n += block_size) {
|
||||
ablock = cipherfn.encrypt(iblock);
|
||||
for (i = 0; i < block_size && i + n < ciphertext.length; i++) {
|
||||
iblock[i] = ciphertext[n + i];
|
||||
if (j < text.length) {
|
||||
text[j] = ablock[i] ^ iblock[i];
|
||||
j++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
n = resync ? 0 : 2;
|
||||
|
||||
text = text.subarray(n, ciphertext.length - block_size - 2 + n);
|
||||
|
||||
return text;
|
||||
},
|
||||
|
||||
normalEncrypt: function(cipherfn, key, plaintext, iv) {
|
||||
cipherfn = new cipher[cipherfn](key);
|
||||
const cipherfn = new cipher[algo](key);
|
||||
const block_size = cipherfn.blockSize;
|
||||
|
||||
let blocki = new Uint8Array(block_size);
|
||||
const blockc = new Uint8Array(block_size);
|
||||
const blockc = iv;
|
||||
let pos = 0;
|
||||
const cyphertext = new Uint8Array(plaintext.length);
|
||||
const ciphertext = new Uint8Array(plaintext.length);
|
||||
let i;
|
||||
let j = 0;
|
||||
|
||||
if (iv === null) {
|
||||
for (i = 0; i < block_size; i++) {
|
||||
blockc[i] = 0;
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < block_size; i++) {
|
||||
blockc[i] = iv[i];
|
||||
}
|
||||
}
|
||||
while (plaintext.length > block_size * pos) {
|
||||
const encblock = cipherfn.encrypt(blockc);
|
||||
blocki = plaintext.subarray((pos * block_size), (pos * block_size) + block_size);
|
||||
for (i = 0; i < blocki.length; i++) {
|
||||
blockc[i] = blocki[i] ^ encblock[i];
|
||||
cyphertext[j++] = blockc[i];
|
||||
ciphertext[j++] = blockc[i];
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
return cyphertext;
|
||||
return ciphertext;
|
||||
},
|
||||
|
||||
normalDecrypt: function(cipherfn, key, ciphertext, iv) {
|
||||
cipherfn = new cipher[cipherfn](key);
|
||||
decrypt: async function(algo, key, ciphertext, iv) {
|
||||
if (algo.substr(0, 3) === 'aes') {
|
||||
return aesDecrypt(algo, key, ciphertext, iv);
|
||||
}
|
||||
|
||||
ciphertext = await stream.readToEnd(ciphertext);
|
||||
|
||||
const cipherfn = new cipher[algo](key);
|
||||
const block_size = cipherfn.blockSize;
|
||||
|
||||
let blockp;
|
||||
let blockp = iv;
|
||||
let pos = 0;
|
||||
const plaintext = new Uint8Array(ciphertext.length);
|
||||
const offset = 0;
|
||||
let i;
|
||||
let j = 0;
|
||||
|
||||
if (iv === null) {
|
||||
blockp = new Uint8Array(block_size);
|
||||
for (i = 0; i < block_size; i++) {
|
||||
blockp[i] = 0;
|
||||
}
|
||||
} else {
|
||||
blockp = iv.subarray(0, block_size);
|
||||
}
|
||||
while (ciphertext.length > (block_size * pos)) {
|
||||
const decblock = cipherfn.encrypt(blockp);
|
||||
blockp = ciphertext.subarray((pos * (block_size)) + offset, (pos * (block_size)) + (block_size) + offset);
|
||||
|
@ -297,3 +92,60 @@ export default {
|
|||
return plaintext;
|
||||
}
|
||||
};
|
||||
|
||||
function aesEncrypt(algo, key, pt, iv) {
|
||||
if (
|
||||
util.getWebCrypto() &&
|
||||
key.length !== 24 && // Chrome doesn't support 192 bit keys, see https://www.chromium.org/blink/webcrypto#TOC-AES-support
|
||||
!util.isStream(pt) &&
|
||||
pt.length >= 3000 * config.min_bytes_for_web_crypto // Default to a 3MB minimum. Chrome is pretty slow for small messages, see: https://bugs.chromium.org/p/chromium/issues/detail?id=701188#c2
|
||||
) { // Web Crypto
|
||||
return webEncrypt(algo, key, pt, iv);
|
||||
}
|
||||
if (nodeCrypto) { // Node crypto library.
|
||||
return nodeEncrypt(algo, key, pt, iv);
|
||||
} // asm.js fallback
|
||||
const cfb = new AES_CFB(key, iv);
|
||||
return stream.transform(pt, value => cfb.AES_Encrypt_process(value), () => cfb.AES_Encrypt_finish());
|
||||
}
|
||||
|
||||
function aesDecrypt(algo, key, ct, iv) {
|
||||
if (nodeCrypto) { // Node crypto library.
|
||||
return nodeDecrypt(algo, key, ct, iv);
|
||||
}
|
||||
if (util.isStream(ct)) {
|
||||
const cfb = new AES_CFB(key, iv);
|
||||
return stream.transform(ct, value => cfb.AES_Decrypt_process(value), () => cfb.AES_Decrypt_finish());
|
||||
}
|
||||
return AES_CFB.decrypt(ct, key, iv);
|
||||
}
|
||||
|
||||
function xorMut(a, b) {
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
a[i] = a[i] ^ b[i];
|
||||
}
|
||||
}
|
||||
|
||||
async function webEncrypt(algo, key, pt, iv) {
|
||||
const ALGO = 'AES-CBC';
|
||||
const _key = await webCrypto.importKey('raw', key, { name: ALGO }, false, ['encrypt']);
|
||||
const { blockSize } = cipher[algo];
|
||||
const cbc_pt = util.concatUint8Array([new Uint8Array(blockSize), pt]);
|
||||
const ct = new Uint8Array(await webCrypto.encrypt({ name: ALGO, iv }, _key, cbc_pt)).subarray(0, pt.length);
|
||||
xorMut(ct, pt);
|
||||
return ct;
|
||||
}
|
||||
|
||||
function nodeEncrypt(algo, key, pt, iv) {
|
||||
key = new Buffer(key);
|
||||
iv = new Buffer(iv);
|
||||
const cipherObj = new nodeCrypto.createCipheriv('aes-' + algo.substr(3, 3) + '-cfb', key, iv);
|
||||
return stream.transform(pt, value => new Uint8Array(cipherObj.update(new Buffer(value))));
|
||||
}
|
||||
|
||||
function nodeDecrypt(algo, key, ct, iv) {
|
||||
key = new Buffer(key);
|
||||
iv = new Buffer(iv);
|
||||
const decipherObj = new nodeCrypto.createDecipheriv('aes-' + algo.substr(3, 3) + '-cfb', key, iv);
|
||||
return stream.transform(ct, value => new Uint8Array(decipherObj.update(new Buffer(value))));
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
* @requires type/mpi
|
||||
* @requires type/oid
|
||||
* @requires enums
|
||||
* @requires util
|
||||
* @module crypto/crypto
|
||||
*/
|
||||
|
||||
|
@ -39,6 +40,7 @@ import type_kdf_params from '../type/kdf_params';
|
|||
import type_mpi from '../type/mpi';
|
||||
import type_oid from '../type/oid';
|
||||
import enums from '../enums';
|
||||
import util from '../util';
|
||||
|
||||
function constructParams(types, data) {
|
||||
return types.map(function(type, i) {
|
||||
|
@ -291,11 +293,13 @@ export default {
|
|||
* Generates a random byte prefix for the specified algorithm
|
||||
* See {@link https://tools.ietf.org/html/rfc4880#section-9.2|RFC 4880 9.2} for algorithms.
|
||||
* @param {module:enums.symmetric} algo Symmetric encryption algorithm
|
||||
* @returns {Uint8Array} Random bytes with length equal to the block size of the cipher
|
||||
* @returns {Uint8Array} Random bytes with length equal to the block size of the cipher, plus the last two bytes repeated.
|
||||
* @async
|
||||
*/
|
||||
getPrefixRandom: function(algo) {
|
||||
return random.getRandomBytes(cipher[algo].blockSize);
|
||||
getPrefixRandom: async function(algo) {
|
||||
const prefixrandom = await random.getRandomBytes(cipher[algo].blockSize);
|
||||
const repeat = new Uint8Array([prefixrandom[prefixrandom.length - 2], prefixrandom[prefixrandom.length - 1]]);
|
||||
return util.concat([prefixrandom, repeat]);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -195,7 +195,7 @@ SecretKey.prototype.encrypt = async function (passphrase) {
|
|||
arr = [new Uint8Array([254, enums.write(enums.symmetric, symmetric)])];
|
||||
arr.push(s2k.write());
|
||||
arr.push(iv);
|
||||
arr.push(crypto.cfb.normalEncrypt(symmetric, key, util.concatUint8Array([
|
||||
arr.push(crypto.cfb.encrypt(symmetric, key, util.concatUint8Array([
|
||||
cleartext,
|
||||
await crypto.hash.sha1(cleartext)
|
||||
]), iv));
|
||||
|
@ -293,7 +293,7 @@ SecretKey.prototype.decrypt = async function (passphrase) {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
const cleartextWithHash = crypto.cfb.normalDecrypt(symmetric, key, ciphertext, iv);
|
||||
const cleartextWithHash = await crypto.cfb.decrypt(symmetric, key, ciphertext, iv);
|
||||
|
||||
let hash;
|
||||
let hashlen;
|
||||
|
|
|
@ -24,17 +24,12 @@
|
|||
* @requires util
|
||||
*/
|
||||
|
||||
import { AES_CFB } from 'asmcrypto.js/dist_es5/aes/cfb';
|
||||
|
||||
import stream from 'web-stream-tools';
|
||||
import config from '../config';
|
||||
import crypto from '../crypto';
|
||||
import enums from '../enums';
|
||||
import util from '../util';
|
||||
|
||||
const nodeCrypto = util.getNodeCrypto();
|
||||
const Buffer = util.getNodeBuffer();
|
||||
|
||||
const VERSION = 1; // A one-octet version number of the data packet.
|
||||
|
||||
/**
|
||||
|
@ -94,22 +89,14 @@ SymEncryptedIntegrityProtected.prototype.write = function () {
|
|||
SymEncryptedIntegrityProtected.prototype.encrypt = async function (sessionKeyAlgorithm, key, streaming) {
|
||||
let bytes = this.packets.write();
|
||||
if (!streaming) 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]);
|
||||
const prefix = await crypto.getPrefixRandom(sessionKeyAlgorithm);
|
||||
const mdc = new Uint8Array([0xD3, 0x14]); // modification detection code packet
|
||||
|
||||
let tohash = util.concat([bytes, mdc]);
|
||||
const hash = await crypto.hash.sha1(util.concat([prefix, stream.passiveClone(tohash)]));
|
||||
tohash = util.concat([tohash, hash]);
|
||||
const tohash = util.concat([prefix, bytes, mdc]);
|
||||
const hash = await crypto.hash.sha1(stream.passiveClone(tohash));
|
||||
const plaintext = util.concat([tohash, hash]);
|
||||
|
||||
if (sessionKeyAlgorithm.substr(0, 3) === 'aes') { // AES optimizations. Native code for node, asmCrypto for browser.
|
||||
this.encrypted = aesEncrypt(sessionKeyAlgorithm, util.concat([prefix, tohash]), key);
|
||||
} else {
|
||||
tohash = await stream.readToEnd(tohash);
|
||||
this.encrypted = crypto.cfb.encrypt(prefixrandom, sessionKeyAlgorithm, tohash, key, false);
|
||||
this.encrypted = stream.slice(this.encrypted, 0, prefix.length + tohash.length);
|
||||
}
|
||||
this.encrypted = await crypto.cfb.encrypt(sessionKeyAlgorithm, key, plaintext, new Uint8Array(crypto.cipher[sessionKeyAlgorithm].blockSize));
|
||||
return true;
|
||||
};
|
||||
|
||||
|
@ -124,23 +111,14 @@ SymEncryptedIntegrityProtected.prototype.encrypt = async function (sessionKeyAlg
|
|||
SymEncryptedIntegrityProtected.prototype.decrypt = async function (sessionKeyAlgorithm, key, streaming) {
|
||||
if (!streaming) 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, streaming);
|
||||
} else {
|
||||
decrypted = crypto.cfb.decrypt(sessionKeyAlgorithm, key, await stream.readToEnd(encrypted), false);
|
||||
}
|
||||
const decrypted = await crypto.cfb.decrypt(sessionKeyAlgorithm, key, encrypted, new Uint8Array(crypto.cipher[sessionKeyAlgorithm].blockSize));
|
||||
|
||||
// there must be a modification detection code packet as the
|
||||
// last packet and everything gets hashed except the hash itself
|
||||
const encryptedPrefix = await stream.readToEnd(stream.slice(encryptedClone, 0, crypto.cipher[sessionKeyAlgorithm].blockSize + 2));
|
||||
const prefix = crypto.cfb.mdc(sessionKeyAlgorithm, key, encryptedPrefix);
|
||||
const realHash = stream.slice(stream.passiveClone(decrypted), -20);
|
||||
const bytes = stream.slice(decrypted, 0, -20);
|
||||
const tohash = util.concat([prefix, stream.passiveClone(bytes)]);
|
||||
const tohash = stream.slice(decrypted, 0, -20);
|
||||
const verifyHash = Promise.all([
|
||||
stream.readToEnd(await crypto.hash.sha1(tohash)),
|
||||
stream.readToEnd(await crypto.hash.sha1(stream.passiveClone(tohash))),
|
||||
stream.readToEnd(realHash)
|
||||
]).then(([hash, mdc]) => {
|
||||
if (!util.equalsUint8Array(hash, mdc)) {
|
||||
|
@ -148,7 +126,8 @@ SymEncryptedIntegrityProtected.prototype.decrypt = async function (sessionKeyAlg
|
|||
}
|
||||
return new Uint8Array();
|
||||
});
|
||||
let packetbytes = stream.slice(bytes, 0, -2);
|
||||
const bytes = stream.slice(tohash, crypto.cipher[sessionKeyAlgorithm].blockSize + 2); // Remove random prefix
|
||||
let packetbytes = stream.slice(bytes, 0, -2); // Remove MDC packet
|
||||
packetbytes = stream.concat([packetbytes, stream.fromAsync(() => verifyHash)]);
|
||||
if (!util.isStream(encrypted) || !config.allow_unauthenticated_stream) {
|
||||
packetbytes = await stream.readToEnd(packetbytes);
|
||||
|
@ -158,48 +137,3 @@ SymEncryptedIntegrityProtected.prototype.decrypt = async function (sessionKeyAlg
|
|||
};
|
||||
|
||||
export default SymEncryptedIntegrityProtected;
|
||||
|
||||
|
||||
//////////////////////////
|
||||
// //
|
||||
// Helper functions //
|
||||
// //
|
||||
//////////////////////////
|
||||
|
||||
|
||||
function aesEncrypt(algo, pt, key) {
|
||||
if (nodeCrypto) { // Node crypto library.
|
||||
return nodeEncrypt(algo, pt, key);
|
||||
} // asm.js fallback
|
||||
const cfb = new AES_CFB(key);
|
||||
return stream.transform(pt, value => cfb.AES_Encrypt_process(value), () => cfb.AES_Encrypt_finish());
|
||||
}
|
||||
|
||||
function aesDecrypt(algo, ct, key) {
|
||||
let pt;
|
||||
if (nodeCrypto) { // Node crypto library.
|
||||
pt = nodeDecrypt(algo, ct, key);
|
||||
} else { // asm.js fallback
|
||||
if (util.isStream(ct)) {
|
||||
const cfb = new AES_CFB(key);
|
||||
pt = stream.transform(ct, value => cfb.AES_Decrypt_process(value), () => cfb.AES_Decrypt_finish());
|
||||
} else {
|
||||
pt = AES_CFB.decrypt(ct, key);
|
||||
}
|
||||
}
|
||||
return stream.slice(pt, crypto.cipher[algo].blockSize + 2); // Remove random prefix
|
||||
}
|
||||
|
||||
function nodeEncrypt(algo, pt, key) {
|
||||
key = new Buffer(key);
|
||||
const iv = new Buffer(new Uint8Array(crypto.cipher[algo].blockSize));
|
||||
const cipherObj = new nodeCrypto.createCipheriv('aes-' + algo.substr(3, 3) + '-cfb', key, iv);
|
||||
return stream.transform(pt, value => new Uint8Array(cipherObj.update(new Buffer(value))));
|
||||
}
|
||||
|
||||
function nodeDecrypt(algo, ct, key) {
|
||||
key = new Buffer(key);
|
||||
const iv = new Buffer(new Uint8Array(crypto.cipher[algo].blockSize));
|
||||
const decipherObj = new nodeCrypto.createDecipheriv('aes-' + algo.substr(3, 3) + '-cfb', key, iv);
|
||||
return stream.transform(ct, value => new Uint8Array(decipherObj.update(new Buffer(value))));
|
||||
}
|
||||
|
|
|
@ -145,7 +145,7 @@ SymEncryptedSessionKey.prototype.decrypt = async function(passphrase) {
|
|||
const modeInstance = await mode(algo, key);
|
||||
this.sessionKey = await modeInstance.decrypt(this.encrypted, this.iv, adata);
|
||||
} else if (this.encrypted !== null) {
|
||||
const decrypted = crypto.cfb.normalDecrypt(algo, key, this.encrypted, null);
|
||||
const decrypted = await crypto.cfb.decrypt(algo, key, this.encrypted, new Uint8Array(crypto.cipher[algo].blockSize));
|
||||
|
||||
this.sessionKeyAlgorithm = enums.read(enums.symmetric, decrypted[0]);
|
||||
this.sessionKey = decrypted.subarray(1, decrypted.length);
|
||||
|
@ -188,7 +188,7 @@ SymEncryptedSessionKey.prototype.encrypt = async function(passphrase) {
|
|||
} else {
|
||||
const algo_enum = new Uint8Array([enums.write(enums.symmetric, this.sessionKeyAlgorithm)]);
|
||||
const private_key = util.concatUint8Array([algo_enum, this.sessionKey]);
|
||||
this.encrypted = crypto.cfb.normalEncrypt(algo, key, private_key, null);
|
||||
this.encrypted = await crypto.cfb.encrypt(algo, key, private_key, new Uint8Array(crypto.cipher[algo].blockSize));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
@ -20,12 +20,14 @@
|
|||
* @requires config
|
||||
* @requires crypto
|
||||
* @requires enums
|
||||
* @requires util
|
||||
*/
|
||||
|
||||
import stream from 'web-stream-tools';
|
||||
import config from '../config';
|
||||
import crypto from '../crypto';
|
||||
import enums from '../enums';
|
||||
import util from '../util';
|
||||
|
||||
/**
|
||||
* Implementation of the Symmetrically Encrypted Data Packet (Tag 9)
|
||||
|
@ -79,7 +81,11 @@ SymmetricallyEncrypted.prototype.write = function () {
|
|||
*/
|
||||
SymmetricallyEncrypted.prototype.decrypt = async function (sessionKeyAlgorithm, key) {
|
||||
this.encrypted = await stream.readToEnd(this.encrypted);
|
||||
const decrypted = crypto.cfb.decrypt(sessionKeyAlgorithm, key, this.encrypted, true);
|
||||
const decrypted = await crypto.cfb.decrypt(sessionKeyAlgorithm, key,
|
||||
this.encrypted.subarray(crypto.cipher[sessionKeyAlgorithm].blockSize + 2),
|
||||
this.encrypted.subarray(2, crypto.cipher[sessionKeyAlgorithm].blockSize + 2)
|
||||
);
|
||||
|
||||
// If MDC errors are not being ignored, all missing MDC packets in symmetrically encrypted data should throw an error
|
||||
if (!this.ignore_mdc_error) {
|
||||
throw new Error('Decryption failed due to missing MDC.');
|
||||
|
@ -100,7 +106,10 @@ SymmetricallyEncrypted.prototype.decrypt = async function (sessionKeyAlgorithm,
|
|||
SymmetricallyEncrypted.prototype.encrypt = async function (algo, key) {
|
||||
const data = this.packets.write();
|
||||
|
||||
this.encrypted = crypto.cfb.encrypt(await crypto.getPrefixRandom(algo), algo, await stream.readToEnd(data), key, true);
|
||||
const prefix = await crypto.getPrefixRandom(algo);
|
||||
const FRE = await crypto.cfb.encrypt(algo, key, prefix, new Uint8Array(crypto.cipher[algo].blockSize));
|
||||
const ciphertext = await crypto.cfb.encrypt(algo, key, data, FRE.subarray(2));
|
||||
this.encrypted = util.concatUint8Array([FRE, ciphertext]);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
|
|
@ -277,31 +277,14 @@ describe('API functional testing', function() {
|
|||
return algo !== 'idea' && algo !== 'plaintext';
|
||||
});
|
||||
|
||||
function testCFB(plaintext, resync) {
|
||||
symmAlgos.forEach(async function(algo) {
|
||||
async function testCFB(plaintext) {
|
||||
await Promise.all(symmAlgos.map(async function(algo) {
|
||||
const symmKey = await crypto.generateSessionKey(algo);
|
||||
const symmencData = crypto.cfb.encrypt(await crypto.getPrefixRandom(algo), algo, util.str_to_Uint8Array(plaintext), symmKey, resync);
|
||||
const text = util.Uint8Array_to_str(crypto.cfb.decrypt(algo, symmKey, symmencData, resync));
|
||||
const IV = new Uint8Array(crypto.cipher[algo].blockSize);
|
||||
const symmencData = await crypto.cfb.encrypt(algo, symmKey, util.str_to_Uint8Array(plaintext), IV);
|
||||
const text = util.Uint8Array_to_str(await crypto.cfb.decrypt(algo, symmKey, symmencData, new Uint8Array(crypto.cipher[algo].blockSize)));
|
||||
expect(text).to.equal(plaintext);
|
||||
});
|
||||
}
|
||||
|
||||
function testAESCFB(plaintext) {
|
||||
symmAlgos.forEach(async function(algo) {
|
||||
if(algo.substr(0,3) === 'aes') {
|
||||
const symmKey = await crypto.generateSessionKey(algo);
|
||||
const rndm = await crypto.getPrefixRandom(algo);
|
||||
|
||||
const repeat = new Uint8Array([rndm[rndm.length - 2], rndm[rndm.length - 1]]);
|
||||
const prefix = util.concatUint8Array([rndm, repeat]);
|
||||
|
||||
const symmencData = crypto.cfb.encrypt(rndm, algo, util.str_to_Uint8Array(plaintext), symmKey, false);
|
||||
const decrypted = crypto.cfb.decrypt(algo, symmKey, symmencData, false);
|
||||
|
||||
const text = util.Uint8Array_to_str(decrypted);
|
||||
expect(text).to.equal(plaintext);
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
function testAESGCM(plaintext, nativeDecrypt) {
|
||||
|
@ -325,25 +308,11 @@ describe('API functional testing', function() {
|
|||
});
|
||||
}
|
||||
|
||||
it("Symmetric with OpenPGP CFB resync", function () {
|
||||
testCFB("hello", true);
|
||||
testCFB("1234567", true);
|
||||
testCFB("foobarfoobar1234567890", true);
|
||||
testCFB("12345678901234567890123456789012345678901234567890", true);
|
||||
});
|
||||
|
||||
it("Symmetric without OpenPGP CFB resync", function () {
|
||||
testCFB("hello", false);
|
||||
testCFB("1234567", false);
|
||||
testCFB("foobarfoobar1234567890", false);
|
||||
testCFB("12345678901234567890123456789012345678901234567890", false);
|
||||
});
|
||||
|
||||
it("asmCrypto AES without OpenPGP CFB resync", function () {
|
||||
testAESCFB("hello");
|
||||
testAESCFB("1234567");
|
||||
testAESCFB("foobarfoobar1234567890");
|
||||
testAESCFB("12345678901234567890123456789012345678901234567890");
|
||||
it("Symmetric with OpenPGP CFB", async function () {
|
||||
await testCFB("hello");
|
||||
await testCFB("1234567");
|
||||
await testCFB("foobarfoobar1234567890");
|
||||
await testCFB("12345678901234567890123456789012345678901234567890");
|
||||
});
|
||||
|
||||
describe('Symmetric AES-GCM (native)', function() {
|
||||
|
|
Loading…
Reference in New Issue
Block a user