Use native Node crypto for RSA encryption (#1006)
This commit is contained in:
parent
495fe1091c
commit
45c2e67624
|
@ -41,6 +41,8 @@ import type_mpi from '../type/mpi';
|
|||
import type_oid from '../type/oid';
|
||||
import enums from '../enums';
|
||||
import util from '../util';
|
||||
import pkcs1 from './pkcs1';
|
||||
import pkcs5 from './pkcs5';
|
||||
|
||||
function constructParams(types, data) {
|
||||
return types.map(function(type, i) {
|
||||
|
@ -59,7 +61,7 @@ export default {
|
|||
* @param {Array<module:type/mpi|
|
||||
module:type/oid|
|
||||
module:type/kdf_params>} pub_params Algorithm-specific public key parameters
|
||||
* @param {module:type/mpi} data Data to be encrypted as MPI
|
||||
* @param {String} data Data to be encrypted
|
||||
* @param {String} fingerprint Recipient fingerprint
|
||||
* @returns {Array<module:type/mpi|
|
||||
* module:type/ecdh_symkey>} encrypted session key parameters
|
||||
|
@ -70,13 +72,14 @@ export default {
|
|||
switch (algo) {
|
||||
case enums.publicKey.rsa_encrypt:
|
||||
case enums.publicKey.rsa_encrypt_sign: {
|
||||
const m = data.toBN();
|
||||
const n = pub_params[0].toBN();
|
||||
const e = pub_params[1].toBN();
|
||||
const res = await publicKey.rsa.encrypt(m, n, e);
|
||||
data = util.str_to_Uint8Array(data);
|
||||
const n = pub_params[0].toUint8Array();
|
||||
const e = pub_params[1].toUint8Array();
|
||||
const res = await publicKey.rsa.encrypt(data, n, e);
|
||||
return constructParams(types, [res]);
|
||||
}
|
||||
case enums.publicKey.elgamal: {
|
||||
data = new type_mpi(await pkcs1.eme.encode(data, pub_params[0].byteLength()));
|
||||
const m = data.toBN();
|
||||
const p = pub_params[0].toBN();
|
||||
const g = pub_params[1].toBN();
|
||||
|
@ -85,6 +88,7 @@ export default {
|
|||
return constructParams(types, [res.c1, res.c2]);
|
||||
}
|
||||
case enums.publicKey.ecdh: {
|
||||
data = new type_mpi(pkcs5.encode(data));
|
||||
const oid = pub_params[0];
|
||||
const Q = pub_params[1].toUint8Array();
|
||||
const kdf_params = pub_params[2];
|
||||
|
@ -108,20 +112,20 @@ export default {
|
|||
module:type/ecdh_symkey>}
|
||||
data_params encrypted session key parameters
|
||||
* @param {String} fingerprint Recipient fingerprint
|
||||
* @returns {BN} A BN containing the decrypted data
|
||||
* @returns {String} String containing the decrypted data
|
||||
* @async
|
||||
*/
|
||||
publicKeyDecrypt: async function(algo, key_params, data_params, fingerprint) {
|
||||
switch (algo) {
|
||||
case enums.publicKey.rsa_encrypt_sign:
|
||||
case enums.publicKey.rsa_encrypt: {
|
||||
const c = data_params[0].toBN();
|
||||
const n = key_params[0].toBN(); // n = pq
|
||||
const e = key_params[1].toBN();
|
||||
const d = key_params[2].toBN(); // de = 1 mod (p-1)(q-1)
|
||||
const p = key_params[3].toBN();
|
||||
const q = key_params[4].toBN();
|
||||
const u = key_params[5].toBN(); // p^-1 mod q
|
||||
const c = data_params[0].toUint8Array();
|
||||
const n = key_params[0].toUint8Array(); // n = pq
|
||||
const e = key_params[1].toUint8Array();
|
||||
const d = key_params[2].toUint8Array(); // de = 1 mod (p-1)(q-1)
|
||||
const p = key_params[3].toUint8Array();
|
||||
const q = key_params[4].toUint8Array();
|
||||
const u = key_params[5].toUint8Array(); // p^-1 mod q
|
||||
return publicKey.rsa.decrypt(c, n, e, d, p, q, u);
|
||||
}
|
||||
case enums.publicKey.elgamal: {
|
||||
|
@ -129,7 +133,8 @@ export default {
|
|||
const c2 = data_params[1].toBN();
|
||||
const p = key_params[0].toBN();
|
||||
const x = key_params[3].toBN();
|
||||
return publicKey.elgamal.decrypt(c1, c2, p, x);
|
||||
const result = new type_mpi(await publicKey.elgamal.decrypt(c1, c2, p, x));
|
||||
return pkcs1.eme.decode(result.toString());
|
||||
}
|
||||
case enums.publicKey.ecdh: {
|
||||
const oid = key_params[0];
|
||||
|
@ -138,8 +143,9 @@ export default {
|
|||
const C = data_params[1].data;
|
||||
const Q = key_params[1].toUint8Array();
|
||||
const d = key_params[3].toUint8Array();
|
||||
return publicKey.elliptic.ecdh.decrypt(
|
||||
oid, kdf_params.cipher, kdf_params.hash, V, C, Q, d, fingerprint);
|
||||
const result = new type_mpi(await publicKey.elliptic.ecdh.decrypt(
|
||||
oid, kdf_params.cipher, kdf_params.hash, V, C, Q, d, fingerprint));
|
||||
return pkcs5.decode(result.toString());
|
||||
}
|
||||
default:
|
||||
throw new Error('Invalid public key encryption algorithm.');
|
||||
|
|
|
@ -32,6 +32,7 @@ import config from '../../config';
|
|||
import util from '../../util';
|
||||
import pkcs1 from '../pkcs1';
|
||||
import enums from '../../enums';
|
||||
import type_mpi from '../../type/mpi';
|
||||
|
||||
const webCrypto = util.getWebCrypto();
|
||||
const nodeCrypto = util.getNodeCrypto();
|
||||
|
@ -132,62 +133,36 @@ export default {
|
|||
|
||||
/**
|
||||
* Encrypt message
|
||||
* @param {BN} m message
|
||||
* @param {BN} n RSA public modulus
|
||||
* @param {BN} e RSA public exponent
|
||||
* @returns {BN} RSA Ciphertext
|
||||
* @param {Uint8Array} data message
|
||||
* @param {Uint8Array} n RSA public modulus
|
||||
* @param {Uint8Array} e RSA public exponent
|
||||
* @returns {Uint8Array} RSA Ciphertext
|
||||
* @async
|
||||
*/
|
||||
encrypt: async function(m, n, e) {
|
||||
if (n.cmp(m) <= 0) {
|
||||
throw new Error('Message size cannot exceed modulus size');
|
||||
encrypt: async function(data, n, e) {
|
||||
if (nodeCrypto) {
|
||||
return this.nodeEncrypt(data, n, e);
|
||||
}
|
||||
const nred = new BN.red(n);
|
||||
return m.toRed(nred).redPow(e).toArrayLike(Uint8Array, 'be', n.byteLength());
|
||||
return this.bnEncrypt(data, n, e);
|
||||
},
|
||||
|
||||
/**
|
||||
* Decrypt RSA message
|
||||
* @param {BN} m message
|
||||
* @param {BN} n RSA public modulus
|
||||
* @param {BN} e RSA public exponent
|
||||
* @param {BN} d RSA private exponent
|
||||
* @param {BN} p RSA private prime p
|
||||
* @param {BN} q RSA private prime q
|
||||
* @param {BN} u RSA private coefficient
|
||||
* @returns {BN} RSA Plaintext
|
||||
* @param {Uint8Array} m message
|
||||
* @param {Uint8Array} n RSA public modulus
|
||||
* @param {Uint8Array} e RSA public exponent
|
||||
* @param {Uint8Array} d RSA private exponent
|
||||
* @param {Uint8Array} p RSA private prime p
|
||||
* @param {Uint8Array} q RSA private prime q
|
||||
* @param {Uint8Array} u RSA private coefficient
|
||||
* @returns {String} RSA Plaintext
|
||||
* @async
|
||||
*/
|
||||
decrypt: async function(m, n, e, d, p, q, u) {
|
||||
if (n.cmp(m) <= 0) {
|
||||
throw new Error('Data too large.');
|
||||
decrypt: async function(data, n, e, d, p, q, u) {
|
||||
if (nodeCrypto) {
|
||||
return this.nodeDecrypt(data, n, e, d, p, q, u);
|
||||
}
|
||||
const dq = d.mod(q.subn(1)); // d mod (q-1)
|
||||
const dp = d.mod(p.subn(1)); // d mod (p-1)
|
||||
const pred = new BN.red(p);
|
||||
const qred = new BN.red(q);
|
||||
const nred = new BN.red(n);
|
||||
|
||||
let blinder;
|
||||
let unblinder;
|
||||
if (config.rsa_blinding) {
|
||||
unblinder = (await random.getRandomBN(new BN(2), n)).toRed(nred);
|
||||
blinder = unblinder.redInvm().redPow(e);
|
||||
m = m.toRed(nred).redMul(blinder).fromRed();
|
||||
}
|
||||
|
||||
const mp = m.toRed(pred).redPow(dp);
|
||||
const mq = m.toRed(qred).redPow(dq);
|
||||
const t = mq.redSub(mp.fromRed().toRed(qred));
|
||||
const h = u.toRed(qred).redMul(t).fromRed();
|
||||
|
||||
let result = h.mul(p).add(mp).toRed(nred);
|
||||
|
||||
if (config.rsa_blinding) {
|
||||
result = result.redMul(unblinder);
|
||||
}
|
||||
|
||||
return result.toArrayLike(Uint8Array, 'be', n.byteLength());
|
||||
return this.bnDecrypt(data, n, e, d, p, q, u);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -323,11 +298,12 @@ export default {
|
|||
},
|
||||
|
||||
webSign: async function (hash_name, data, n, e, d, p, q, u) {
|
||||
// OpenPGP keys require that p < q, and Safari Web Crypto requires that p > q.
|
||||
// We swap them in privateToJwk, so it usually works out, but nevertheless,
|
||||
// not all OpenPGP keys are compatible with this requirement.
|
||||
// OpenPGP.js used to generate RSA keys the wrong way around (p > q), and still
|
||||
// does if the underlying Web Crypto does so (e.g. old MS Edge 50% of the time).
|
||||
/** OpenPGP keys require that p < q, and Safari Web Crypto requires that p > q.
|
||||
* We swap them in privateToJwk, so it usually works out, but nevertheless,
|
||||
* not all OpenPGP keys are compatible with this requirement.
|
||||
* OpenPGP.js used to generate RSA keys the wrong way around (p > q), and still
|
||||
* does if the underlying Web Crypto does so (e.g. old MS Edge 50% of the time).
|
||||
*/
|
||||
const jwk = privateToJwk(n, e, d, p, q, u);
|
||||
const algo = {
|
||||
name: "RSASSA-PKCS1-v1_5",
|
||||
|
@ -417,6 +393,107 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
nodeEncrypt: async function (data, n, e) {
|
||||
const keyObject = {
|
||||
modulus: new BN(n),
|
||||
publicExponent: new BN(e)
|
||||
};
|
||||
let key;
|
||||
if (typeof nodeCrypto.createPrivateKey !== 'undefined') {
|
||||
const der = RSAPublicKey.encode(keyObject, 'der');
|
||||
key = { key: der, format: 'der', type: 'pkcs1', padding: nodeCrypto.constants.RSA_PKCS1_PADDING };
|
||||
} else {
|
||||
const pem = RSAPublicKey.encode(keyObject, 'pem', {
|
||||
label: 'RSA PUBLIC KEY'
|
||||
});
|
||||
key = { key: pem, padding: nodeCrypto.constants.RSA_PKCS1_PADDING };
|
||||
}
|
||||
return new Uint8Array(nodeCrypto.publicEncrypt(key, data));
|
||||
},
|
||||
|
||||
bnEncrypt: async function (data, n, e) {
|
||||
n = new BN(n);
|
||||
data = new type_mpi(await pkcs1.eme.encode(util.Uint8Array_to_str(data), n.byteLength()));
|
||||
data = data.toBN();
|
||||
e = new BN(e);
|
||||
if (n.cmp(data) <= 0) {
|
||||
throw new Error('Message size cannot exceed modulus size');
|
||||
}
|
||||
const nred = new BN.red(n);
|
||||
return data.toRed(nred).redPow(e).toArrayLike(Uint8Array, 'be', n.byteLength());
|
||||
},
|
||||
|
||||
nodeDecrypt: function (data, n, e, d, p, q, u) {
|
||||
const pBNum = new BN(p);
|
||||
const qBNum = new BN(q);
|
||||
const dBNum = new BN(d);
|
||||
const dq = dBNum.mod(qBNum.subn(1)); // d mod (q-1)
|
||||
const dp = dBNum.mod(pBNum.subn(1)); // d mod (p-1)
|
||||
const keyObject = {
|
||||
version: 0,
|
||||
modulus: new BN(n),
|
||||
publicExponent: new BN(e),
|
||||
privateExponent: new BN(d),
|
||||
// switch p and q
|
||||
prime1: new BN(q),
|
||||
prime2: new BN(p),
|
||||
// switch dp and dq
|
||||
exponent1: dq,
|
||||
exponent2: dp,
|
||||
coefficient: new BN(u)
|
||||
};
|
||||
let key;
|
||||
if (typeof nodeCrypto.createPrivateKey !== 'undefined') {
|
||||
const der = RSAPrivateKey.encode(keyObject, 'der');
|
||||
key = { key: der, format: 'der' , type: 'pkcs1', padding: nodeCrypto.constants.RSA_PKCS1_PADDING };
|
||||
} else {
|
||||
const pem = RSAPrivateKey.encode(keyObject, 'pem', {
|
||||
label: 'RSA PRIVATE KEY'
|
||||
});
|
||||
key = { key: pem, padding: nodeCrypto.constants.RSA_PKCS1_PADDING };
|
||||
}
|
||||
return util.Uint8Array_to_str(nodeCrypto.privateDecrypt(key, data));
|
||||
},
|
||||
|
||||
bnDecrypt: async function(data, n, e, d, p, q, u) {
|
||||
data = new BN(data);
|
||||
n = new BN(n);
|
||||
e = new BN(e);
|
||||
d = new BN(d);
|
||||
p = new BN(p);
|
||||
q = new BN(q);
|
||||
u = new BN(u);
|
||||
if (n.cmp(data) <= 0) {
|
||||
throw new Error('Data too large.');
|
||||
}
|
||||
const dq = d.mod(q.subn(1)); // d mod (q-1)
|
||||
const dp = d.mod(p.subn(1)); // d mod (p-1)
|
||||
const pred = new BN.red(p);
|
||||
const qred = new BN.red(q);
|
||||
const nred = new BN.red(n);
|
||||
|
||||
let blinder;
|
||||
let unblinder;
|
||||
if (config.rsa_blinding) {
|
||||
unblinder = (await random.getRandomBN(new BN(2), n)).toRed(nred);
|
||||
blinder = unblinder.redInvm().redPow(e);
|
||||
data = data.toRed(nred).redMul(blinder).fromRed();
|
||||
}
|
||||
|
||||
const mp = data.toRed(pred).redPow(dp);
|
||||
const mq = data.toRed(qred).redPow(dq);
|
||||
const t = mq.redSub(mp.fromRed().toRed(qred));
|
||||
const h = u.toRed(qred).redMul(t).fromRed();
|
||||
|
||||
let result = h.mul(p).add(mp).toRed(nred);
|
||||
|
||||
if (config.rsa_blinding) {
|
||||
result = result.redMul(unblinder);
|
||||
}
|
||||
|
||||
return pkcs1.eme.decode((new type_mpi(result)).toString());
|
||||
},
|
||||
|
||||
prime: prime
|
||||
};
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
*/
|
||||
|
||||
import type_keyid from '../type/keyid';
|
||||
import type_mpi from '../type/mpi';
|
||||
import crypto from '../crypto';
|
||||
import enums from '../enums';
|
||||
import util from '../util';
|
||||
|
@ -112,17 +111,9 @@ PublicKeyEncryptedSessionKey.prototype.encrypt = async function (key) {
|
|||
|
||||
data += util.Uint8Array_to_str(this.sessionKey);
|
||||
data += util.Uint8Array_to_str(util.write_checksum(this.sessionKey));
|
||||
|
||||
let toEncrypt;
|
||||
const algo = enums.write(enums.publicKey, this.publicKeyAlgorithm);
|
||||
if (algo === enums.publicKey.ecdh) {
|
||||
toEncrypt = new type_mpi(crypto.pkcs5.encode(data));
|
||||
} else {
|
||||
toEncrypt = new type_mpi(await crypto.pkcs1.eme.encode(data, key.params[0].byteLength()));
|
||||
}
|
||||
|
||||
this.encrypted = await crypto.publicKeyEncrypt(
|
||||
algo, key.params, toEncrypt, key.getFingerprintBytes());
|
||||
algo, key.params, data, key.getFingerprintBytes());
|
||||
return true;
|
||||
};
|
||||
|
||||
|
@ -137,19 +128,8 @@ PublicKeyEncryptedSessionKey.prototype.encrypt = async function (key) {
|
|||
*/
|
||||
PublicKeyEncryptedSessionKey.prototype.decrypt = async function (key) {
|
||||
const algo = enums.write(enums.publicKey, this.publicKeyAlgorithm);
|
||||
const result = new type_mpi(await crypto.publicKeyDecrypt(
|
||||
algo, key.params, this.encrypted, key.getFingerprintBytes()));
|
||||
|
||||
let checksum;
|
||||
let decoded;
|
||||
if (algo === enums.publicKey.ecdh) {
|
||||
decoded = crypto.pkcs5.decode(result.toString());
|
||||
checksum = util.str_to_Uint8Array(decoded.substr(decoded.length - 2));
|
||||
} else {
|
||||
decoded = crypto.pkcs1.eme.decode(result.toString());
|
||||
checksum = result.toUint8Array().slice(result.byteLength() - 2);
|
||||
}
|
||||
|
||||
const decoded = await crypto.publicKeyDecrypt(algo, key.params, this.encrypted, key.getFingerprintBytes());
|
||||
const checksum = util.str_to_Uint8Array(decoded.substr(decoded.length - 2));
|
||||
key = util.str_to_Uint8Array(decoded.substring(1, decoded.length - 2));
|
||||
|
||||
if (!util.equalsUint8Array(checksum, util.write_checksum(key))) {
|
||||
|
|
|
@ -356,38 +356,26 @@ describe('API functional testing', function() {
|
|||
|
||||
it('Asymmetric using RSA with eme_pkcs1 padding', function () {
|
||||
const symmKey = util.Uint8Array_to_str(crypto.generateSessionKey('aes256'));
|
||||
return crypto.pkcs1.eme.encode(symmKey, RSApubMPIs[0].byteLength()).then(RSAUnencryptedData => {
|
||||
const RSAUnencryptedMPI = new openpgp.MPI(RSAUnencryptedData);
|
||||
return crypto.publicKeyEncrypt(1, RSApubMPIs, RSAUnencryptedMPI);
|
||||
}).then(RSAEncryptedData => {
|
||||
|
||||
crypto.publicKeyEncrypt(1, RSApubMPIs, symmKey).then(RSAEncryptedData => {
|
||||
return crypto.publicKeyDecrypt(
|
||||
1, RSApubMPIs.concat(RSAsecMPIs), RSAEncryptedData
|
||||
).then(data => {
|
||||
data = new openpgp.MPI(data).write();
|
||||
data = util.Uint8Array_to_str(data.subarray(2, data.length));
|
||||
|
||||
const result = crypto.pkcs1.eme.decode(data, RSApubMPIs[0].byteLength());
|
||||
expect(result).to.equal(symmKey);
|
||||
expect(data).to.equal(symmKey);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Asymmetric using Elgamal with eme_pkcs1 padding', function () {
|
||||
const symmKey = util.Uint8Array_to_str(crypto.generateSessionKey('aes256'));
|
||||
return crypto.pkcs1.eme.encode(symmKey, ElgamalpubMPIs[0].byteLength()).then(ElgamalUnencryptedData => {
|
||||
const ElgamalUnencryptedMPI = new openpgp.MPI(ElgamalUnencryptedData);
|
||||
return crypto.publicKeyEncrypt(16, ElgamalpubMPIs, ElgamalUnencryptedMPI);
|
||||
}).then(ElgamalEncryptedData => {
|
||||
|
||||
crypto.publicKeyEncrypt(16, ElgamalpubMPIs, symmKey).then(ElgamalEncryptedData => {
|
||||
return crypto.publicKeyDecrypt(
|
||||
16, ElgamalpubMPIs.concat(ElgamalsecMPIs), ElgamalEncryptedData
|
||||
).then(data => {
|
||||
data = new openpgp.MPI(data).write();
|
||||
data = util.Uint8Array_to_str(data.subarray(2, data.length));
|
||||
|
||||
const result = crypto.pkcs1.eme.decode(data, ElgamalpubMPIs[0].byteLength());
|
||||
expect(result).to.equal(symmKey);
|
||||
expect(data).to.equal(symmKey);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -38,6 +38,45 @@ const native = openpgp.util.getWebCrypto() || openpgp.util.getNodeCrypto();
|
|||
expect(verify).to.be.true;
|
||||
});
|
||||
|
||||
it('encrypt and decrypt using generated key params', async function() {
|
||||
const bits = openpgp.util.getWebCryptoAll() ? 2048 : 1024;
|
||||
const keyParams = await openpgp.crypto.generateParams(openpgp.enums.publicKey.rsa_sign, bits);
|
||||
const n = keyParams[0].toUint8Array();
|
||||
const e = keyParams[1].toUint8Array();
|
||||
const d = keyParams[2].toUint8Array();
|
||||
const p = keyParams[3].toUint8Array();
|
||||
const q = keyParams[4].toUint8Array();
|
||||
const u = keyParams[5].toUint8Array();
|
||||
const message = openpgp.util.Uint8Array_to_str(await openpgp.crypto.generateSessionKey('aes256'));
|
||||
const encrypted = await openpgp.crypto.publicKey.rsa.encrypt(openpgp.util.str_to_Uint8Array(message), n, e);
|
||||
const result = new openpgp.MPI(encrypted);
|
||||
const decrypted = await openpgp.crypto.publicKey.rsa.decrypt(result.toUint8Array(), n, e, d, p, q, u);
|
||||
expect(decrypted).to.be.equal(message);
|
||||
});
|
||||
|
||||
it('decrypt nodeCrypto by bnCrypto and vice versa', async function() {
|
||||
if (!openpgp.util.getNodeCrypto()) {
|
||||
this.skip();
|
||||
}
|
||||
const bits = 1024;
|
||||
const keyParams = await openpgp.crypto.generateParams(openpgp.enums.publicKey.rsa_sign, bits);
|
||||
const n = keyParams[0].toUint8Array();
|
||||
const e = keyParams[1].toUint8Array();
|
||||
const d = keyParams[2].toUint8Array();
|
||||
const p = keyParams[3].toUint8Array();
|
||||
const q = keyParams[4].toUint8Array();
|
||||
const u = keyParams[5].toUint8Array();
|
||||
const message = openpgp.util.Uint8Array_to_str(await openpgp.crypto.generateSessionKey('aes256'));
|
||||
const encryptedBn = await openpgp.crypto.publicKey.rsa.bnEncrypt(openpgp.util.str_to_Uint8Array(message), n, e);
|
||||
const resultBN = new openpgp.MPI(encryptedBn);
|
||||
const decrypted1 = await openpgp.crypto.publicKey.rsa.nodeDecrypt(resultBN.toUint8Array(), n, e, d, p, q, u);
|
||||
expect(decrypted1).to.be.equal(message);
|
||||
const encryptedNode = await openpgp.crypto.publicKey.rsa.nodeEncrypt(openpgp.util.str_to_Uint8Array(message), n, e);
|
||||
const resultNode = new openpgp.MPI(encryptedNode);
|
||||
const decrypted2 = await openpgp.crypto.publicKey.rsa.bnDecrypt(resultNode.toUint8Array(), n, e, d, p, q, u);
|
||||
expect(decrypted2).to.be.equal(message);
|
||||
});
|
||||
|
||||
it('compare webCrypto and bn math sign', async function() {
|
||||
if (!openpgp.util.getWebCrypto()) {
|
||||
this.skip();
|
||||
|
|
Loading…
Reference in New Issue
Block a user