6

I am using rsa.js v1.0 from http://www-cs-students.stanford.edu/~tjw/jsbn/ to encrypt an ASCII string in a browser. The string is actually a 16 byte array that contains a double-length TripleDes key. With rsa v1.0 this works. The byte array is correctly decrypted on the server (using Bouncy Castle or a Thales HSM) as a 16 byte array.

e.g.

var zpk = hex2a("E0F8AD4092F81FC401E60ECB7F5B8F1A");
var rsa = new RSAKey();
rsa.setPublic(modulus, exponent);
var res = rsa.encrypt(zpk);
if (res) {
    document.rsatest.zpkrsa.value = hex2b64(res);
}

When moving the rsa.js v1.4 this not longer works. Bouncy castle decrypts the data, but instead of a 16 byte array, it is now a 25 byte array.

The key difference I can see in the rsa.js library is in the v1.1 release notes:

Added support for utf-8 encoding of non-ASCII characters when PKCS1 encoding and decoding JavaScript strings.

The PKCS#1 padding in v1.0 is:

// PKCS#1 (type 2, random) pad input string s to n bytes, and return a bigint
function pkcs1pad2(s, n) {
    if (n < s.length + 11) {
        alert("Message too long for RSA");
        return null;
    }
    var ba = new Array();
    var i = s.length - 1;
    while (i >= 0 && n > 0) ba[--n] = s.charCodeAt(i--);
    ba[--n] = 0;
    var rng = new SecureRandom();
    ...
    return new BigInteger(ba);
}

The PKCS#1 padding function in v1.1 and later is:

// PKCS#1 (type 2, random) pad input string s to n bytes, and return a bigint
function pkcs1pad2(s,n) {
  if(n < s.length + 11) { // TODO: fix for utf-8
    console.error("Message too long for RSA");
    return null;
  }
  var ba = new Array();
  var i = s.length - 1;
  while(i >= 0 && n > 0) {
    var c = s.charCodeAt(i--);
    if(c < 128) { // encode using utf-8
      ba[--n] = c;
    }
    else if((c > 127) && (c < 2048)) {
      ba[--n] = (c & 63) | 128;
      ba[--n] = (c >> 6) | 192;
    }
    else {
      ba[--n] = (c & 63) | 128;
      ba[--n] = ((c >> 6) & 63) | 128;
      ba[--n] = (c >> 12) | 224;
    }
  }
  ba[--n] = 0;
  ...
  return new BigInteger(ba);
}

rsa.js v1.0 treated each character as a 1 byte character. Since v1.1 characters are tested to see if they are multi-byte utf-8 or not.

It seems my only options are to either:

  1. Stick with rsa.js v1.0
  2. Create a modified version of rsa.js (and rsa2.js) that allow me to disable to utf-8 character detection.
  3. (Edited) Alter code to use defensivejs.com which supports PKCS#1 v2 (oaep).

Ideas?

andyvan
  • 183
  • 2
  • 7
  • Interesting, could be differences in the version of PKCS implemented, which could cause a lot of headaches. I would actually strongly recommend trying to use the [Stanford JavaScript Crypto Library](https://github.com/bitwiseshiftleft/sjcl) instead of this library by Wu, just because I know SJCL is more widely used and tested. – TheGreatContini May 12 '16 at 05:33
  • Yep, pretty sure Wu's library is implementing [PCKS #1 v1.5](https://tools.ietf.org/html/rfc2313) which might be vulnerable to Bleichenbacher attack. As a cryptographer, I recommend against using this code! Use SJCL instead! – TheGreatContini May 12 '16 at 05:38

1 Answers1

3
  1. This code is implementing PKCS #1 v1.5 padding in both cases, the only difference is the utf-8 support. For it to work with a recipient library, that library would need to decode the content the same way he encodes it. Good luck on that, I don't think you will find anything that does that.

  2. PKCS #1 v1.5 padding is insecure due to an attack illustrated by Daniel Bleichenbacher around 1999. Nowadays it is recommended to use PKCS #1 v2.x. Wu's code does not support this.

  3. If you really wanted to use this library (I recommend against it), probably the cleanest approach is to send in the key hex encoded before you encrypt it ("E0F8AD4092F81FC401E60ECB7F5B8F1A") and make sure recipient hex decodes it after decrypt: that would work around Wu's UTF-8 adjustments. You could also use base64 encoding/decoding.

  4. SJCL is a much better JavaScript crypto library, and you will unlikely run into problems like this. To my knowledge, Wu's code was designed as a PoC of his wonderful authentication protocol, whereas SJCL is designed for more general use and is being maintained by a community.

Community
  • 1
  • 1
TheGreatContini
  • 6,429
  • 2
  • 27
  • 37
  • Unfortunately is looks like the SJCL library doesn't support RSA. RSA is a requirement as the decrypting device is a Thales HSM Payshield 9000 (https://en.wikipedia.org/wiki/Hardware_security_module). The HSM does support PKCS#1 v2 (OEAP) – andyvan May 12 '16 at 10:56
  • The current implementation only uses the public/private key pair once. Once the server receives a request, the information is processed (success or failure) and then the keys are erased and not reused. Based on this use case, I would speculate that the Bleichenbacher's CCA attack is not applicable e.g. there is no opportunity for an attacker to retry requests with random data. – andyvan May 12 '16 at 11:04
  • @andyvan holy poop batman, you're right! Disappointing! You could also consider a library by Google, but not sure how difficult it is to work with or how mature it is, see: https://github.com/google/end-to-end/blob/master/src/javascript/crypto/e2e/asymmetric/rsa.js . If keys are erased upon failure, the. You are right that the attack would not apply. – TheGreatContini May 12 '16 at 11:06
  • It is interesting that there doesn't seem to be many (any?) js RSA libraries that are not based on Tom Wu's work. I haven't found any that support PKCS#1 v2 OAEP. The spec is here for any volunteers : ftp://ftp.rsasecurity.com/pub/pkcs/ascii/pkcs-1v2.asc – andyvan May 12 '16 at 11:24
  • @andyvan: Interesting. Maybe this is my purpose in life (I'm half serious about that). In the mean time, I modify my recommendation above for point 3: instead of using base64, just pass in the hex encoding ("E0F8AD4092F81FC401E60ECB7F5B8F1A") in the format that Wu's library requires (I think an array?). This will be easy to handle at the other end, especially the HSM: turning such hex encoding back into a byte array is trivial, and you do not need an external library to do it. – TheGreatContini May 12 '16 at 21:48
  • Another js crypto library that implements RSA, but only with pkcs #1 v1.5: http://www.ohdave.com/rsa/ . Alternatively, here's a library that has OAEP padding: http://www.defensivejs.com/ . The author claims that it is based upon Wu's code, but when I look at the source code, I see that exponentiation is done via a function called expMod, which I do not see in Wu's code. Both are worth experimenting with if you want to work around Wu's UTF8 support. – TheGreatContini May 12 '16 at 22:29
  • Passing the key as hex is not an option. The HSM decrypts RSA data and re-encrypts it under the LMK in one operation. The beauty is that key is never in the clear. However, it also means that the key must be a 16 byte key. Translation from hex to bytes is not possible. However, defensivejs.com looks promising. I will investigate this further. Thanks – andyvan May 13 '16 at 00:56