From 9f23c6a8919aedeadb14f8fe7c7ea4ec9c6f8675 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Obernd=C3=B6rfer?= Date: Mon, 17 Mar 2014 11:54:40 +0100 Subject: [PATCH] OP-01-005 Side-channel leak in RSA decryption (High). Add config option for RSA blinding, default true. Update jsbn to 1.3. Remove decrypted packets after Message.decrypt(). --- src/config/config.js | 7 ++++--- src/crypto/crypto.js | 5 ++++- src/crypto/public_key/jsbn.js | 2 +- src/crypto/public_key/rsa.js | 36 ++++++++++++++++++++++++++++++++--- src/crypto/random.js | 6 +++--- src/message.js | 5 ++++- test/general/basic.js | 7 +++++++ 7 files changed, 56 insertions(+), 12 deletions(-) diff --git a/src/config/config.js b/src/config/config.js index de202569..f0d2f417 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -35,14 +35,15 @@ module.exports = { prefer_hash_algorithm: enums.hash.sha256, encryption_cipher: enums.symmetric.aes256, compression: enums.compression.zip, + integrity_protect: true, + rsa_blinding: true, + show_version: true, show_comment: true, - integrity_protect: true, - keyserver: "keyserver.linux.it", // "pgp.mit.edu:11371" - versionstring: "OpenPGP.js VERSION", commentstring: "http://openpgpjs.org", + keyserver: "keyserver.linux.it", // "pgp.mit.edu:11371" node_store: './openpgp.store', debug: false diff --git a/src/crypto/crypto.js b/src/crypto/crypto.js index 8537123e..6efc21b6 100644 --- a/src/crypto/crypto.js +++ b/src/crypto/crypto.js @@ -93,12 +93,15 @@ module.exports = { case 'rsa_encrypt': var rsa = new publicKey.rsa(); // 0 and 1 are the public key. + var n = keyIntegers[0].toBigInteger(); + var e = keyIntegers[1].toBigInteger(); + // 2 to 5 are the private key. var d = keyIntegers[2].toBigInteger(); p = keyIntegers[3].toBigInteger(); var q = keyIntegers[4].toBigInteger(); var u = keyIntegers[5].toBigInteger(); var m = dataIntegers[0].toBigInteger(); - return rsa.decrypt(m, d, p, q, u); + return rsa.decrypt(m, n, e, d, p, q, u); case 'elgamal': var elgamal = new publicKey.elgamal(); var x = keyIntegers[3].toBigInteger(); diff --git a/src/crypto/public_key/jsbn.js b/src/crypto/public_key/jsbn.js index eb7712c7..ea6e87d4 100644 --- a/src/crypto/public_key/jsbn.js +++ b/src/crypto/public_key/jsbn.js @@ -293,7 +293,7 @@ function bnCompareTo(a) { if (r != 0) return r; var i = this.t; r = i - a.t; - if (r != 0) return r; + if (r != 0) return (this.s < 0) ? -r : r; while (--i >= 0) if ((r = this[i] - a[i]) != 0) return r; return 0; } diff --git a/src/crypto/public_key/rsa.js b/src/crypto/public_key/rsa.js index 4a907f3c..eb997605 100644 --- a/src/crypto/public_key/rsa.js +++ b/src/crypto/public_key/rsa.js @@ -26,7 +26,8 @@ var BigInteger = require('./jsbn.js'), util = require('../../util.js'), - random = require('../random.js'); + random = require('../random.js'), + config = require('../../config'); function SecureRandom() { function nextBytes(byteArray) { @@ -37,11 +38,33 @@ function SecureRandom() { this.nextBytes = nextBytes; } +var blinder = BigInteger.ZERO; +var unblinder = BigInteger.ZERO; +var TWO = BigInteger.ONE.add(BigInteger.ONE); + +function blind(m, n, e) { + if (unblinder.bitLength() === n.bitLength()) { + unblinder = unblinder.square().mod(n); + } else { + unblinder = random.getRandomBigIntegerInRange(TWO, n); + } + blinder = unblinder.modInverse(n).modPow(e, n); + return m.multiply(blinder).mod(n); +} + +function unblind(t, n) { + return t.multiply(unblinder).mod(n); +} + function RSA() { /** * This function uses jsbn Big Num library to decrypt RSA * @param m * message + * @param n + * RSA public modulus n as BigInteger + * @param e + * RSA public exponent as BigInteger * @param d * RSA d as BigInteger * @param p @@ -52,7 +75,10 @@ function RSA() { * RSA u as BigInteger * @return {BigInteger} The decrypted value of the message */ - function decrypt(m, d, p, q, u) { + function decrypt(m, n, e, d, p, q, u) { + if (config.rsa_blinding) { + m = blind(m, n, e); + } var xp = m.mod(p).modPow(d.mod(p.subtract(BigInteger.ONE)), p); var xq = m.mod(q).modPow(d.mod(q.subtract(BigInteger.ONE)), q); util.print_debug("rsa.js decrypt\nxpn:" + util.hexstrdump(xp.toMPI()) + "\nxqn:" + util.hexstrdump(xq.toMPI())); @@ -65,7 +91,11 @@ function RSA() { } else { t = t.multiply(u).mod(q); } - return t.multiply(p).add(xp); + t = t.multiply(p).add(xp); + if (config.rsa_blinding) { + t = unblind(t, n); + } + return t; } /** diff --git a/src/crypto/random.js b/src/crypto/random.js index d81eba55..be38c81d 100644 --- a/src/crypto/random.js +++ b/src/crypto/random.js @@ -94,8 +94,8 @@ module.exports = { * @return {BigInteger} Resulting big integer */ getRandomBigInteger: function(bits) { - if (bits < 0) { - return null; + if (bits < 1) { + throw new Error('Illegal parameter value: bits < 1'); } var numBytes = Math.floor((bits + 7) / 8); @@ -114,7 +114,7 @@ module.exports = { getRandomBigIntegerInRange: function(min, max) { if (max.compareTo(min) <= 0) { - return; + throw new Error('Illegal parameter value: max <= min'); } var range = max.subtract(min); diff --git a/src/message.js b/src/message.js index 8813cb0c..06041788 100644 --- a/src/message.js +++ b/src/message.js @@ -107,7 +107,10 @@ Message.prototype.decrypt = function(privateKey) { if (symEncryptedPacketlist.length !== 0) { var symEncryptedPacket = symEncryptedPacketlist[0]; symEncryptedPacket.decrypt(pkESKeyPacket.sessionKeyAlgorithm, pkESKeyPacket.sessionKey); - return new Message(symEncryptedPacket.packets); + var resultMsg = new Message(symEncryptedPacket.packets); + // remove packets after decryption + symEncryptedPacket.packets = new packet.List(); + return resultMsg; } } }; diff --git a/test/general/basic.js b/test/general/basic.js index fe2d3d03..ad2e2bd2 100644 --- a/test/general/basic.js +++ b/test/general/basic.js @@ -257,6 +257,13 @@ describe('Basic', function() { expect(decrypted).to.equal(plaintext); done(); }); + + it('Decrypt message 2x', function() { + decrypted = openpgp.decryptMessage(privKey, message); + var decrypted2 = openpgp.decryptMessage(privKey, message); + expect(decrypted).to.equal(decrypted2); + }); + }); describe("Message 3DES decryption", function() {