ElGamal and MPI use bn.js; TODO: RSA Key Generation

This commit is contained in:
Mahrud Sayrafi 2018-02-20 03:08:42 -08:00
parent 490b1dc0f0
commit e1d85ba682
No known key found for this signature in database
GPG Key ID: C24071B956C3245F
10 changed files with 145 additions and 134 deletions

View File

@ -32,7 +32,6 @@
*/ */
import BN from 'bn.js'; import BN from 'bn.js';
import { RSA_RAW } from 'asmcrypto.js';
import publicKey from './public_key'; import publicKey from './public_key';
import cipher from './cipher'; import cipher from './cipher';
import random from './random'; import random from './random';
@ -68,18 +67,19 @@ export default {
switch (algo) { switch (algo) {
case 'rsa_encrypt': case 'rsa_encrypt':
case 'rsa_encrypt_sign': { case 'rsa_encrypt_sign': {
const m = data.toUint8Array();
const n = publicParams[0].toUint8Array(); const n = publicParams[0].toUint8Array();
const e = publicParams[1].toUint8Array(); const e = publicParams[1].toUint8Array();
const m = data.toUint8Array(); const res = await publicKey.rsa.encrypt(m, n, e);
return constructParams(types, [new BN(RSA_RAW.encrypt(m, [n, e]))]); return constructParams(types, [new BN(res)]);
} }
case 'elgamal': { case 'elgamal': {
const elgamal = new publicKey.elgamal(); const m = data.toBN();
const p = publicParams[0].toBigInteger(); const p = publicParams[0].toBN();
const g = publicParams[1].toBigInteger(); const g = publicParams[1].toBN();
const y = publicParams[2].toBigInteger(); const y = publicParams[2].toBN();
const m = data.toBigInteger(); const res = await publicKey.elgamal.encrypt(m, p, g, y);
return constructParams(types, elgamal.encrypt(m, g, p, y)); return constructParams(types, [res.c1, res.c2]);
} }
case 'ecdh': { case 'ecdh': {
const oid = publicParams[0]; const oid = publicParams[0];
@ -117,18 +117,14 @@ export default {
const p = keyIntegers[3].toUint8Array(); const p = keyIntegers[3].toUint8Array();
const q = keyIntegers[4].toUint8Array(); const q = keyIntegers[4].toUint8Array();
const u = keyIntegers[5].toUint8Array(); // q^-1 mod p const u = keyIntegers[5].toUint8Array(); // q^-1 mod p
const dd = new BN(d); return publicKey.rsa.decrypt(c, n, e, d, p, q, u);
const dp = dd.mod(new BN(p).subn(1)).toArrayLike(Uint8Array); // d mod (p-1)
const dq = dd.mod(new BN(q).subn(1)).toArrayLike(Uint8Array); // d mod (q-1)
return new BN(RSA_RAW.decrypt(c, [n, e, d, q, p, dq, dp, u]).slice(1)); // FIXME remove slice
} }
case 'elgamal': { case 'elgamal': {
const elgamal = new publicKey.elgamal(); const c1 = dataIntegers[0].toBN();
const x = keyIntegers[3].toBigInteger(); const c2 = dataIntegers[1].toBN();
const c1 = dataIntegers[0].toBigInteger(); const p = keyIntegers[0].toBN();
const c2 = dataIntegers[1].toBigInteger(); const x = keyIntegers[3].toBN();
const p = keyIntegers[0].toBigInteger(); return publicKey.elgamal.decrypt(c1, c2, p, x);
return elgamal.decrypt(c1, c2, p, x);
} }
case 'ecdh': { case 'ecdh': {
const oid = keyIntegers[0]; const oid = keyIntegers[0];
@ -263,8 +259,7 @@ export default {
case 'rsa_encrypt': case 'rsa_encrypt':
case 'rsa_encrypt_sign': case 'rsa_encrypt_sign':
case 'rsa_sign': { case 'rsa_sign': {
const rsa = new publicKey.rsa(); return publicKey.rsa.generate(bits, "10001").then(function(keyObject) {
return rsa.generate(bits, "10001").then(function(keyObject) {
return constructParams( return constructParams(
types, [keyObject.n, keyObject.ee, keyObject.d, keyObject.p, keyObject.q, keyObject.u] types, [keyObject.n, keyObject.ee, keyObject.d, keyObject.p, keyObject.q, keyObject.u]
); );

View File

@ -86,7 +86,7 @@ export default {
} }
break; break;
} }
return {r: r.fromRed(), s: s.fromRed()}; return { r: r.fromRed(), s: s.fromRed() };
}, },
/* /*

View File

@ -18,39 +18,41 @@
// ElGamal implementation // ElGamal implementation
/** /**
* @requires crypto/public_key/jsbn * @requires bn.js
* @requires crypto/random * @requires crypto/random
* @requires util
* @module crypto/public_key/elgamal * @module crypto/public_key/elgamal
*/ */
import BigInteger from './jsbn.js'; import BN from 'bn.js';
import random from '../random.js'; import random from '../random';
import util from '../../util.js';
export default function Elgamal() { const one = new BN(1);
function encrypt(m, g, p, y) {
// choose k in {2,...,p-2} export default {
const pMinus2 = p.subtract(BigInteger.TWO); /*
let k = random.getRandomBigIntegerInRange(BigInteger.ONE, pMinus2); * m, p, g, y are all BN
k = k.mod(pMinus2).add(BigInteger.ONE); * returns { c1: BN, c2: BN }
const c = []; */
c[0] = g.modPow(k, p); encrypt: function(m, p, g, y) {
c[1] = y.modPow(k, p).multiply(m).mod(p); const redp = new BN.red(p);
return c; const mred = m.toRed(redp);
const gred = g.toRed(redp);
const yred = y.toRed(redp);
const k = random.getRandomBN(one, p);
return {
c1: gred.redPow(k).fromRed(),
c2: yred.redPow(k).redMul(mred).fromRed()
};
},
/*
* c1, c2, p, x are all BN
* returns BN
*/
decrypt: function(c1, c2, p, x) {
const redp = new BN.red(p);
const c1red = c1.toRed(redp);
const c2red = c2.toRed(redp);
return c1red.redPow(x).redInvm().redMul(c2red).fromRed();
} }
};
function decrypt(c1, c2, p, x) {
util.print_debug("Elgamal Decrypt:\nc1:" + util.hexstrdump(c1.toMPI()) + "\n" +
"c2:" + util.hexstrdump(c2.toMPI()) + "\n" +
"p:" + util.hexstrdump(p.toMPI()) + "\n" +
"x:" + util.hexstrdump(x.toMPI()));
return (c1.modPow(x, p).modInverse(p)).multiply(c2).mod(p);
//var c = c1.pow(x).modInverse(p); // c0^-a mod p
//return c.multiply(c2).mod(p);
}
// signing and signature verification using Elgamal is not required by OpenPGP.
this.encrypt = encrypt;
this.decrypt = decrypt;
}

View File

@ -67,7 +67,7 @@ function kdf(hash_algo, X, length, param) {
* @param {module:type/oid} oid Elliptic curve object identifier * @param {module:type/oid} oid Elliptic curve object identifier
* @param {Enums} cipher_algo Symmetric cipher to use * @param {Enums} cipher_algo Symmetric cipher to use
* @param {Enums} hash_algo Hash algorithm to use * @param {Enums} hash_algo Hash algorithm to use
* @param {Uint8Array} m Value derived from session key (RFC 6637) * @param {module:type/mpi} m Value derived from session key (RFC 6637)
* @param {Uint8Array} Q Recipient public key * @param {Uint8Array} Q Recipient public key
* @param {String} fingerprint Recipient fingerprint * @param {String} fingerprint Recipient fingerprint
* @return {{V: BN, C: BN}} Returns ephemeral key and encoded session key * @return {{V: BN, C: BN}} Returns ephemeral key and encoded session key
@ -81,7 +81,7 @@ async function encrypt(oid, cipher_algo, hash_algo, m, Q, fingerprint) {
Q = curve.keyFromPublic(Q); Q = curve.keyFromPublic(Q);
const S = v.derive(Q); const S = v.derive(Q);
const Z = kdf(hash_algo, S, cipher[cipher_algo].keySize, param); const Z = kdf(hash_algo, S, cipher[cipher_algo].keySize, param);
const C = aes_kw.wrap(Z, m.toBytes()); const C = aes_kw.wrap(Z, m.toString());
return { return {
V: new BN(v.getPublic()), V: new BN(v.getPublic()),
C: C C: C

View File

@ -18,6 +18,7 @@
// RSA implementation // RSA implementation
/** /**
* @requires bn.js
* @requires asmcrypto.js * @requires asmcrypto.js
* @requires crypto/public_key/jsbn * @requires crypto/public_key/jsbn
* @requires crypto/random * @requires crypto/random
@ -27,6 +28,7 @@
*/ */
import BN from 'bn.js';
import { RSA_RAW } from 'asmcrypto.js'; import { RSA_RAW } from 'asmcrypto.js';
import BigInteger from './jsbn'; import BigInteger from './jsbn';
import random from '../random'; import random from '../random';
@ -59,7 +61,7 @@ function unblind(t, n) {
return t.multiply(unblinder).mod(n); return t.multiply(unblinder).mod(n);
} }
export default function RSA() { export default {
/** /**
* This function uses jsbn Big Num library to decrypt RSA * This function uses jsbn Big Num library to decrypt RSA
* @param m message * @param m message
@ -71,7 +73,12 @@ export default function RSA() {
* @param u RSA u as BigInteger * @param u RSA u as BigInteger
* @return {BigInteger} The decrypted value of the message * @return {BigInteger} The decrypted value of the message
*/ */
function decrypt(m, n, e, d, p, q, u) { decrypt: function(m, n, e, d, p, q, u) {
const dd = new BN(d);
const dp = dd.mod(new BN(p).subn(1)).toArrayLike(Uint8Array); // d mod (p-1)
const dq = dd.mod(new BN(q).subn(1)).toArrayLike(Uint8Array); // d mod (q-1)
return new BN(RSA_RAW.decrypt(m, [n, e, d, q, p, dq, dp, u]).slice(1)); // FIXME remove slice
/*
if (config.rsa_blinding) { if (config.rsa_blinding) {
m = blind(m, n, e); m = blind(m, n, e);
} }
@ -89,53 +96,39 @@ export default function RSA() {
} }
t = t.multiply(p).add(xp); t = t.multiply(p).add(xp);
// var t = RSA.decrypt(m, [n, e, d, q, p, dq, dp, u]).slice(1)
if (config.rsa_blinding) { if (config.rsa_blinding) {
t = unblind(t, n); t = unblind(t, n);
} }
return t; return t;
} */
},
/** /**
* encrypt message * encrypt message
* @param m message as BigInteger * @param m message as BigInteger
* @param e public MPI part as BigInteger
* @param n public MPI part as BigInteger * @param n public MPI part as BigInteger
* @param e public MPI part as BigInteger
* @return BigInteger * @return BigInteger
*/ */
function encrypt(m, e, n) { encrypt: function(m, n, e) {
return m.modPowInt(e, n); // return m.modPowInt(e, n);
} return RSA_RAW.encrypt(m, [n, e]);
},
/* Sign and Verify */ /* Sign and Verify */
function sign(m, d, n) { sign: function(m, n, e, d) {
return m.modPow(d, n); // return m.modPow(d, n);
// return RSA_RAW.sign(m, [n, e, d]); return RSA_RAW.sign(m, [n, e, d]);
} },
function verify(x, e, n) { verify: function(x, n, e) {
return x.modPowInt(e, n); // return x.modPowInt(e, n);
// return RSA_RAW.verify(x, [n, e]); return RSA_RAW.verify(x, [n, e]);
} },
// "empty" RSA key constructor
function KeyObject() {
this.n = null;
this.e = 0;
this.ee = null;
this.d = null;
this.p = null;
this.q = null;
this.dmp1 = null;
this.dmq1 = null;
this.u = null;
}
// Generate a new random private key B bits long, using public expt E // Generate a new random private key B bits long, using public expt E
function generate(B, E) { generate: function(B, E) {
const webCrypto = util.getWebCryptoAll(); const webCrypto = util.getWebCryptoAll();
// //
@ -189,6 +182,20 @@ export default function RSA() {
}); });
} }
// "empty" RSA key constructor
function KeyObject() {
this.n = null;
this.e = 0;
this.ee = null;
this.d = null;
this.p = null;
this.q = null;
this.dmp1 = null;
this.dmq1 = null;
this.u = null;
}
function exportKey(keypair) { function exportKey(keypair) {
// export the generated keys as JsonWebKey (JWK) // export the generated keys as JsonWebKey (JWK)
// https://tools.ietf.org/html/draft-ietf-jose-json-web-key-33 // https://tools.ietf.org/html/draft-ietf-jose-json-web-key-33
@ -263,11 +270,4 @@ export default function RSA() {
resolve(key); resolve(key);
}); });
} }
};
this.encrypt = encrypt;
this.decrypt = decrypt;
this.verify = verify;
this.sign = sign;
this.generate = generate;
this.keyObject = KeyObject;
}

View File

@ -6,7 +6,7 @@
* @module crypto/signature * @module crypto/signature
*/ */
import { RSA_RAW } from 'asmcrypto.js'; // FIXME wrap rsa.js around this
import publicKey from './public_key'; import publicKey from './public_key';
import pkcs1 from './pkcs1'; import pkcs1 from './pkcs1';
import util from '../util'; import util from '../util';
@ -32,7 +32,7 @@ export default {
const m = msg_MPIs[0].toUint8Array(); const m = msg_MPIs[0].toUint8Array();
const n = publickey_MPIs[0].toUint8Array(); const n = publickey_MPIs[0].toUint8Array();
const e = publickey_MPIs[1].toUint8Array(); const e = publickey_MPIs[1].toUint8Array();
const EM = RSA_RAW.verify(m, [n, e]); const EM = publicKey.rsa.verify(m, n, e);
const EM2 = pkcs1.emsa.encode(hash_algo, util.Uint8Array2str(data), n.length); const EM2 = pkcs1.emsa.encode(hash_algo, util.Uint8Array2str(data), n.length);
return util.hexidump(EM) === EM2; return util.hexidump(EM) === EM2;
} }
@ -93,7 +93,8 @@ export default {
const m = util.hex2Uint8Array( const m = util.hex2Uint8Array(
'00'+pkcs1.emsa.encode(hash_algo, data, n.length) // FIXME remove '00' '00'+pkcs1.emsa.encode(hash_algo, data, n.length) // FIXME remove '00'
); );
return util.Uint8Array2MPI(RSA_RAW.sign(m, [n, e, d])); const signature = publicKey.rsa.sign(m, n, e, d);
return util.Uint8Array2MPI(signature);
} }
case 17: { case 17: {
// DSA (Digital Signature Algorithm) [FIPS186] [HAC] // DSA (Digital Signature Algorithm) [FIPS186] [HAC]

View File

@ -182,7 +182,7 @@ PublicKey.prototype.getFingerprint = function () {
} else if (this.version === 3) { } else if (this.version === 3) {
const paramCount = crypto.getPubKeyParamTypes(this.algorithm).length; const paramCount = crypto.getPubKeyParamTypes(this.algorithm).length;
for (let i = 0; i < paramCount; i++) { for (let i = 0; i < paramCount; i++) {
toHash += this.params[i].toBytes(); toHash += this.params[i].toString();
} }
this.fingerprint = util.Uint8Array2str(crypto.hash.md5(util.str2Uint8Array(toHash))); this.fingerprint = util.Uint8Array2str(crypto.hash.md5(util.str2Uint8Array(toHash)));
} }

View File

@ -134,7 +134,7 @@ PublicKeyEncryptedSessionKey.prototype.decrypt = async function (key) {
key.params, key.params,
this.encrypted, this.encrypted,
key.fingerprint key.fingerprint
)).toBytes(); )).toString();
let checksum; let checksum;
let decoded; let decoded;

View File

@ -44,12 +44,14 @@ import util from '../util';
*/ */
export default function MPI(data) { export default function MPI(data) {
/** An implementation dependent integer */ /** An implementation dependent integer */
if (data instanceof BigInteger) { if (data instanceof BN) {
this.fromBN(data);
} else if (data instanceof BigInteger) {
this.fromBigInteger(data); this.fromBigInteger(data);
} else if (data instanceof BN) { } else if (util.isUint8Array(data)) {
this.fromBytes(util.Uint8Array2str(data.toArrayLike(Uint8Array))); this.fromUint8Array(data);
} else if (util.isString(data)) { } else if (util.isString(data)) {
this.fromBytes(data); this.fromString(data);
} else { } else {
this.data = null; this.data = null;
} }
@ -57,8 +59,8 @@ export default function MPI(data) {
/** /**
* Parsing function for a mpi ({@link https://tools.ietf.org/html/rfc4880#section3.2|RFC 4880 3.2}). * Parsing function for a mpi ({@link https://tools.ietf.org/html/rfc4880#section3.2|RFC 4880 3.2}).
* @param {String} input Payload of mpi data * @param {Uint8Array} input Payload of mpi data
* @param {String} endian Endianness of the payload; 'be' for big-endian and 'le' for little-endian * @param {String} endian Endianness of the payload; 'be' for big-endian or 'le' for little-endian
* @return {Integer} Length of data read * @return {Integer} Length of data read
*/ */
MPI.prototype.read = function (bytes, endian='be') { MPI.prototype.read = function (bytes, endian='be') {
@ -81,7 +83,7 @@ MPI.prototype.read = function (bytes, endian='be') {
const bytelen = Math.ceil(bits / 8); const bytelen = Math.ceil(bits / 8);
let payload = bytes.subarray(2, 2 + bytelen); let payload = bytes.subarray(2, 2 + bytelen);
if (endian === 'le') { if (endian === 'le') {
payload = new Uint8Array(payload).reverse(); payload = Uint8Array.from(payload).reverse();
} }
const raw = util.Uint8Array2str(payload); const raw = util.Uint8Array2str(payload);
this.fromBytes(raw); this.fromBytes(raw);
@ -89,47 +91,58 @@ MPI.prototype.read = function (bytes, endian='be') {
return 2 + bytelen; return 2 + bytelen;
}; };
MPI.prototype.fromBytes = function (bytes) { /**
this.data = new BigInteger(util.hexstrdump(bytes), 16); * Converts the mpi object to a bytes as specified in
* {@link https://tools.ietf.org/html/rfc4880#section-3.2|RFC4880 3.2}
* @param {String} endian Endianness of the payload; 'be' for big-endian or 'le' for little-endian
* @param {Integer} length Length of the data part of the MPI
* @return {Uint8Aray} mpi Byte representation
*/
MPI.prototype.write = function (endian, length) {
return util.Uint8Array2MPI(this.data.toArrayLike(Uint8Array, endian, length));
}; };
MPI.prototype.toBytes = function () { MPI.prototype.byteLength = function () {
return util.Uint8Array2str(this.toUint8Array()); return this.write().length - 2;
}; };
MPI.prototype.toUint8Array = function () { MPI.prototype.toUint8Array = function () {
return this.write().slice(2); return this.write().slice(2);
}; };
MPI.prototype.byteLength = function () { MPI.prototype.fromUint8Array = function (bytes) {
return this.toBytes().length; this.data = new BN(bytes);
}; };
/** MPI.prototype.toString = function () {
* Converts the mpi object to a bytes as specified in {@link https://tools.ietf.org/html/rfc4880#section-3.2|RFC4880 3.2} return util.Uint8Array2str(this.toUint8Array());
* @return {Uint8Aray} mpi Byte representation
*/
MPI.prototype.write = function () {
return util.str2Uint8Array(this.data.toMPI());
}; };
MPI.prototype.toBigInteger = function () { MPI.prototype.fromString = function (str) {
this.data = new BN(util.str2Uint8Array(str));
};
// TODO remove this
MPI.prototype.fromBytes = MPI.prototype.fromString;
MPI.prototype.toBN = function () {
return this.data.clone(); return this.data.clone();
}; };
MPI.prototype.toBN = function (bn) { MPI.prototype.fromBN = function (bn) {
return new BN(this.write().slice(2));
};
MPI.prototype.fromBigInteger = function (bn) {
this.data = bn.clone(); this.data = bn.clone();
}; };
MPI.fromClone = function (clone) { MPI.prototype.toBigInteger = function () {
clone.data.copyTo = BigInteger.prototype.copyTo; return new BigInteger(util.hexidump(this.write()), 16);
const bn = new BigInteger(); };
clone.data.copyTo(bn);
const mpi = new MPI(); MPI.prototype.fromBigInteger = function (bn) {
mpi.data = bn; this.data = new BN(bn.toByteArray());
return mpi; };
MPI.fromClone = function (clone) {
const bn = new BN();
clone.data.copy(bn);
return new MPI(bn);
}; };

View File

@ -171,7 +171,7 @@ describe("Packet", function() {
}); });
it('Public key encrypted symmetric key packet', function() { it('Public key encrypted symmetric key packet', function() {
const rsa = new openpgp.crypto.publicKey.rsa(); const rsa = openpgp.crypto.publicKey.rsa;
const keySize = openpgp.util.getWebCryptoAll() ? 2048 : 512; // webkit webcrypto accepts minimum 2048 bit keys const keySize = openpgp.util.getWebCryptoAll() ? 2048 : 512; // webkit webcrypto accepts minimum 2048 bit keys
return rsa.generate(keySize, "10001").then(function(mpiGen) { return rsa.generate(keySize, "10001").then(function(mpiGen) {
@ -435,7 +435,7 @@ describe("Packet", function() {
const key = new openpgp.packet.List(); const key = new openpgp.packet.List();
key.push(new openpgp.packet.SecretKey()); key.push(new openpgp.packet.SecretKey());
const rsa = new openpgp.crypto.publicKey.rsa(); const rsa = openpgp.crypto.publicKey.rsa;
const keySize = openpgp.util.getWebCryptoAll() ? 2048 : 512; // webkit webcrypto accepts minimum 2048 bit keys const keySize = openpgp.util.getWebCryptoAll() ? 2048 : 512; // webkit webcrypto accepts minimum 2048 bit keys
return rsa.generate(keySize, "10001").then(function(mpiGen) { return rsa.generate(keySize, "10001").then(function(mpiGen) {
@ -463,7 +463,7 @@ describe("Packet", function() {
it('Writing and verification of a signature packet.', function() { it('Writing and verification of a signature packet.', function() {
const key = new openpgp.packet.SecretKey(); const key = new openpgp.packet.SecretKey();
const rsa = new openpgp.crypto.publicKey.rsa(); const rsa = openpgp.crypto.publicKey.rsa;
const keySize = openpgp.util.getWebCryptoAll() ? 2048 : 512; // webkit webcrypto accepts minimum 2048 bit keys const keySize = openpgp.util.getWebCryptoAll() ? 2048 : 512; // webkit webcrypto accepts minimum 2048 bit keys
return rsa.generate(keySize, "10001").then(function(mpiGen) { return rsa.generate(keySize, "10001").then(function(mpiGen) {