travist / jsencrypt

A zero-dependency Javascript library to perform OpenSSL RSA Encryption, Decryption, and Key Generation.
http://www.travistidwell.com/jsencrypt
Other
6.67k stars 2.01k forks source link

node.js? #56

Open thomasp001 opened 8 years ago

thomasp001 commented 8 years ago

Is there a version that works with node.js?

felipesabino commented 8 years ago

:+1:

engeshra commented 8 years ago

Is there any version ?

felipesabino commented 8 years ago

@LOVE-2-CODE @engeshra I am using node-rsa instead

loretoparisi commented 8 years ago

There are small caveats to make this to run in node. Most of them are due to the dependence by navigator, document browser objects. Question is navigator really necessary which is this platform dependency needed that node-rsa does not like here when choosing BigInteger max digits:

if(j_lm && (navigator.appName == "Microsoft Internet Explorer")) {
  BigInteger.prototype.am = am2;
  dbits = 30;
}
else if(j_lm && (navigator.appName != "Netscape")) {
  BigInteger.prototype.am = am1;
  dbits = 26;
}
else { // Mozilla/Netscape seems to prefer am3
  BigInteger.prototype.am = am3;
  dbits = 28;
}

or here:

JSX.extend(KJUR.asn1.DERAbstractString, KJUR.asn1.ASN1Object);

where JSX uses navigator.userAgent in JSX.env.ua = JSX.env.parseUA(); that it seems not used in the code actually.

Other use is made of document here

DOM = {
        tag: function (tagName, className) {
            var t = document.createElement(tagName);
            t.className = className;
            return t;
        },
        text: function (str) {
            return document.createTextNode(str);
        }
    };

and in some point of the ASN1 class like for logging purposes

ASN1.prototype.print = function (indent) {
    if (indent === undefined) indent = '';
    document.writeln(indent + this);
    if (this.sub !== null) {
        indent += '  ';
        for (var i = 0, max = this.sub.length; i < max; ++i)
            this.sub[i].print(indent);
    }
};

I wonder if those dependencies can be removed completely, since are not related to RSA algorithm.

loretoparisi commented 8 years ago

So, I did some camouflage

document = {
    createElement : function(p) { console.log("document.createElement"); return {} },
    createTextNode : function(p) { console.log("document.createTextNode"); return {} },
    writeln : function(p) { console.log("document.writeln"); },
    write : function(p) { console.log("document.write"); },
}
navigator = {
  appName : '',
  userAgent : ''
}
window = {
  crypto :  {
    getRandomValues : function() { console.log("window.crypto.getRandomValues"); }
  },
  removeEventListener : function() {console.log("window.removeEventListener");  },
  detachEvent : function() {console.log("window.detachEvent");  },
  addEventListener : function() { console.log("window.addEventListener");  },
  attachEvent : function() { console.log("window.attachEvent");  },
}

These are the function called then

window.crypto.getRandomValues
window.addEventListener

Said that window.addEventListener does not make sense at first turn (we see later why it actually makes a lot of sense...), while the window.crypto.getRandomValues it does.

Luckily, in node we have crypto:

crypto.randomBytes()
crypto.pseudoRandomBytes()

So at this point I need to get the input buffer of type Uint32Array and use it:

var nodeCrypto = require('crypto');

function getRandomValues(buf) {
  if (nodeCrypto.randomBytes) {
    if (!(buf instanceof Uint32Array)) {
      throw new TypeError('expected Uint32Array');
    }
    if (buf.length > 65536) {
      var e = new Error();
      e.code = 22;
      e.message = 'Failed to execute \'getRandomValues\' on \'Crypto\': The ' +
        'ArrayBufferView\'s byte length (' + buf.length + ') exceeds the ' +
        'number of bytes of entropy available via this API (65536).';
      e.name = 'QuotaExceededError';
      throw e;
    }
    var bytes = nodeCrypto.randomBytes(buf.length);
    buf.set(bytes);
    return buf;
  }
  else {
    throw new Error('No secure random number generator available.');
  }
}

document = {
    createElement : function(p) { console.log("document.createElement"); return {} },
    createTextNode : function(p) { console.log("document.createTextNode"); return {} },
    writeln : function(p) { console.log("document.writeln"); },
    write : function(p) { console.log("document.write"); },
}
navigator = {
  appName : '',
  userAgent : ''
}
window = {
  crypto :  {
    getRandomValues : function(z) {
      console.log("window.crypto.getRandomValues\n%s\nBuff size:%s",z, z.length );
      return getRandomValues( z );
    }
  },
  removeEventListener : function() {console.log("window.removeEventListener");  },
  detachEvent : function() {console.log("window.detachEvent");  },
  addEventListener : function() { console.log("window.addEventListener");  },
  attachEvent : function() { console.log("window.attachEvent");  },
}

This is the node script to run

//  Create RSA Private key
  //  openssl genrsa -out rsa_1024_priv.pem 1024
  var PRVTKY="MIICXQIBAAKBgQDlOJu6TyygqxfWT7eLtGDwajtNFOb9I5XRb6khyfD1Yt3YiCgQ WMNW649887VGJiGr/L5i2osbl8C9+WJTeucF+S76xFxdU6jE0NQ+Z+zEdhUTooNR aY5nZiu5PgDB0ED/ZKBUSLKL7eibMxZtMlUDHjm4gwQco1KRMDSmXSMkDwIDAQAB AoGAfY9LpnuWK5Bs50UVep5c93SJdUi82u7yMx4iHFMc/Z2hfenfYEzu+57fI4fv xTQ//5DbzRR/XKb8ulNv6+CHyPF31xk7YOBfkGI8qjLoq06V+FyBfDSwL8KbLyeH m7KUZnLNQbk8yGLzB3iYKkRHlmUanQGaNMIJziWOkN+N9dECQQD0ONYRNZeuM8zd 8XJTSdcIX4a3gy3GGCJxOzv16XHxD03GW6UNLmfPwenKu+cdrQeaqEixrCejXdAF z/7+BSMpAkEA8EaSOeP5Xr3ZrbiKzi6TGMwHMvC7HdJxaBJbVRfApFrE0/mPwmP5 rN7QwjrMY+0+AbXcm8mRQyQ1+IGEembsdwJBAN6az8Rv7QnD/YBvi52POIlRSSIM V7SwWvSK4WSMnGb1ZBbhgdg57DXaspcwHsFV7hByQ5BvMtIduHcT14ECfcECQATe aTgjFnqE/lQ22Rk0eGaYO80cc643BXVGafNfd9fcvwBMnk0iGX0XRsOozVt5Azil psLBYuApa66NcVHJpCECQQDTjI2AQhFc1yRnCU/YgDnSpJVm1nASoRUnU8Jfm3Oz uku7JUXcVpt08DFSceCEX9unCuMcT72rAQlLpdZir876";

  //  Create RSA Public key
  //  openssl rsa -pubout -in rsa_1024_priv.pem -out rsa_1024_pub.pem

  var PBLCKEY="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDlOJu6TyygqxfWT7eLtGDwajtN FOb9I5XRb6khyfD1Yt3YiCgQWMNW649887VGJiGr/L5i2osbl8C9+WJTeucF+S76 xFxdU6jE0NQ+Z+zEdhUTooNRaY5nZiu5PgDB0ED/ZKBUSLKL7eibMxZtMlUDHjm4 gwQco1KRMDSmXSMkDwIDAQAB";

  // test str to be encrypted / decrypted
  var INPTSTR="Hello RSA in JavaScript";

  // Encrypt with the public key...
  var encrypt = new JSEncrypt();
  encrypt.setPublicKey( PBLCKEY );
  var encrypted = encrypt.encrypt( INPTSTR );

  // Decrypt with the private key...
  var decrypt = new JSEncrypt();
  decrypt.setPrivateKey( PRVTKY );
  var uncrypted = decrypt.decrypt(encrypted);

  // Now a simple check to see if the round-trip worked.
  if (uncrypted == INPTSTR ) {
    console.log('Input [%s]\nEncrypted [%s]\nUncrypted [%s]', INPTSTR, encrypted, uncrypted );
  }
  else {
    console.log('ERROR\nInput [%s]\nEncrypted [%s]\nUncrypted [%s]', INPTSTR, encrypted, uncrypted );
  }

While it is working in the browser, not yet in node, I guess this is due to the entropy methodology applied to the mouse event onmousemove:

// Use mouse events for entropy, if we do not have enough entropy by the time
  // we need it, entropy will be generated by Math.random.
  var onMouseMoveListener = function(ev) {
    this.count = this.count || 0;
    if (this.count >= 256 || rng_pptr >= rng_psize) {
      if (window.removeEventListener)
        window.removeEventListener("mousemove", onMouseMoveListener, false);
      else if (window.detachEvent)
        window.detachEvent("onmousemove", onMouseMoveListener);
      return;
    }
    this.count += 1;
    var mouseCoordinates = ev.x + ev.y;
    rng_pool[rng_pptr++] = mouseCoordinates & 255;
  };
  if (window.addEventListener)
    window.addEventListener("mousemove", onMouseMoveListener, false);
  else if (window.attachEvent)
    window.attachEvent("onmousemove", onMouseMoveListener);

By the way, if I prevent to add further entropy with mouse movements, no luck to make it working:

window.crypto.getRandomValues
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Buff size:256
window.addEventListener
[Function]
ERROR
Input [Hello RSA in JavaScript]
Encrypted [false]
Uncrypted [false]

So waiting for help by @travist at this point.

zoloft commented 8 years ago

honestly @loretoparisi that looks like a lot of effort for something that maybe is not necessary. Why using an obviously slow javascript implementation of RSA when you have full access to RSA?

loretoparisi commented 8 years ago

@zoloft thanks, please define how then.

AlexanderKozhevin commented 8 years ago

👍

loretoparisi commented 8 years ago

@zoloft adding that at some point you need code that work both in node and in the browser with browserify, webpack or similar, and if you start from the browser it's always possible. The opposite (from node to the browser) it's not always true due to compiled node modules ( node-gyp, node native addons).

zoloft commented 8 years ago

@loretoparisi maybe I'm a bit old fashioned but I think that wherever possible you should use the native implementation rather than resorting to incomplete (at best) or incorrect (at worst) implementations. Since you are in a node environment, I repeat, why not using the crypto module directly?

gengfire commented 7 years ago

when use node-rsa on server-side, must set encryptionScheme, eg: node-rsa encrypt: const rsakey = new nodeRSA('PUBLIC KEY HERE'); rsakey.setOptions({ encryptionScheme: 'pkcs1' }); // Must Set It When Frontend Use jsencrypt return rsakey.encrypt('YOUR TEXT', 'base64');

node-rsas decrypt: const rsakey = new nodeRSA('PRIVATE KEY HERE'); rsakey.setOptions({ encryptionScheme: 'pkcs1' }); // Must Set It When Frontend Use jsencrypt return rsakey.decrypt('ENCRYPTED STRING', 'utf8');

petershaw commented 6 years ago

@gengfire how to do this the other way around? i encrypt with this module:

var crypt = new JSEncrypt();
crypt.setKey(this.get('kPublic'));
var plain = "some text";
return crypt.encrypt(plain);

and on the server-side with node-rsa:

var key = new NodeRSA(data.kPrivate);
var _check = key.decrypt(input, 'buffer');

but this fails with:

{
    "message": "Error during decryption (probably incorrect key). Original error: Error:     error:040A1079:rsa routines:RSA_padding_check_PKCS1_OAEP_mgf1:oaep decoding error"
}

But i am pretty sure to use the right key. Some hints for me?

+++ update +++ solved! i used an other encryptionScheme. The same way you show will work also the other way around. Thanks a lot.

compulim commented 6 years ago

Tested on Node.js 10.

Given encrypted is the ciphertext output by JSEncrypt (i.e. Base64 format), you can pass it into Node.js crypto module and get the plain text out.

const decryptedAsBuffer = crypto.privateDecrypt({
  key: '-----BEGIN RSA PRIVATE KEY-----...',
  padding: crypto.constants.RSA_PKCS1_PADDING
}, new Buffer(encryptedAsBase64, 'base64'));
shirani182 commented 5 years ago

@petershaw which encryptionScheme did you used? i'm getting the error: Error during decryption (probably incorrect key). Original error: Error: Incorrect data or key and I used 'pkcs1'