pythonencryptionrsapycryptojsencrypt

RSA encryption in python & decrypt in JS


I am totally new in Cryptography. I want to generate RSA key pairs from server side and send it to all of the clients (browsers). But before that I am testing the scenario by simply encrypting data in python and sending in to index.html file via pubnub and trying to decrypt it in JavaScript. The problem is that when I do encryption by;

random_generator = Random.new().read
key = RSA.generate(1024, random_generator)
print key.exportKey() #<--private key
public_key = key.publickey()
print public_key.exportKey() #<--public key
msg = "hello"
enc_data = public_key.encrypt(msg, 32)
print '----ENCRYPTED DATA----'
enc = enc_data[0] 

and send the encrypted data enc, it gives me this error:

UnicodeDecodeError: 'utf8' codec can't decode byte 0xc4 in position 2: invalid continuation byte

I tried to convert it into

enc = base64.b64encode(enc_data[0])

and it is send with no error. but the JS decrypt method gets None

  var enc_from_python = $('#input').val();
  console.log("ENCRYPTED data:", enc_from_python);
  var decrypt = new JSEncrypt();
  decrypt.setPrivateKey($('#privkey').val());
  var uncrypted = decrypt.decrypt(enc_from_python);
  console.log(">>>",uncrypted);  //<-- this is None ! why ?

Both of the code do enc/dec very well on their own. I have also tried to enc/dec the data in JS with the received key pairs from python and that works well. I guess the problem is with the unicode encoding format of encoded data from Pycrypto which does not match. Could any one tell me what am I missing over here.

Full code for Python:

import time
from pubnub.pnconfiguration import PNConfiguration
from pubnub.pubnub import PubNub

from Crypto.PublicKey import RSA
from Crypto import Random
import base64

pnconfig = PNConfiguration()
pnconfig.subscribe_key = 'demo'
pnconfig.publish_key = 'demo'
channel = "my_channel" 
pubnub = PubNub(pnconfig)

def my_publish_callback(envelope, status):
    if not status.is_error():
        pass  # Message successfully published to specified channel.
    else:
        pass  # Handle message publish error. Check 'category' property to find out possible issue


time.sleep(1)
random_generator = Random.new().read
key = RSA.generate(1024, random_generator)
print key.exportKey() #<--private key
public_key = key.publickey()
print public_key.exportKey() #<--public key
msg = "hello"
enc_data = public_key.encrypt(msg, 32)
print '----ENCRYPTED DATA----'
#enc = enc_data[0]
enc = base64.b64encode(enc_data[0])

print enc        
#print type(enc_data[0])
print '----ENCRYPTED DATA----'
print  ''
print '----DECRYPTED DATA begin----'
print key.decrypt(enc_data[0])
print '----DECRYPTED DATA end----'      

pubnub.publish().channel(channel).message({"data": enc , "private": (key.exportKey()), "public" : (public_key.exportKey())}).async(my_publish_callback)         

and full JS code is;

<!doctype html>
<html>
  <head>
    <title>JavaScript RSA Encryption</title>
    <script src="http://code.jquery.com/jquery-1.8.3.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jsencrypt/2.3.1/jsencrypt.min.js"></script>
    <script src="https://cdn.pubnub.com/sdk/javascript/pubnub.4.12.0.min.js"></script>
    <script type="text/javascript">

      // Call this code when the page is done loading.
      $(function() {
        pubnub = new PubNub({
          publish_key: 'demo',
          subscribe_key: 'demo'
        });  

        pubnub.subscribe({
            channels: ['my_channel']
        }); 

    pubnub.addListener({
        message: function(message) {
            var msg = message.message;
            console.log("msg:" + JSON.stringify(msg));

             if (msg.private){
                $("#privkey").val(msg.private);
             }

             if(msg.public){
                $("#pubkey").val(msg.public);
             }

             if(msg.data){
                $("#input").val(msg.data);
             }           
        }    
    })      

        // Run a quick encryption/decryption when they click.
        $('#testme').click(function() {
          var enc_from_python = $('#input').val();
          console.log("ENCRYPTED data:", enc_from_python);
          // Decrypt with the private key...
          var decrypt = new JSEncrypt();
          decrypt.setPrivateKey($('#privkey').val());
          var uncrypted = decrypt.decrypt(enc_from_python);
          console.log(">>>",uncrypted);  //<-- this is None ! why ?
          // Now a simple check to see if the round-trip worked.
          if (uncrypted == $('#input').val()) {
            alert('It works!!!');
          }
          else {
            alert('Something went wrong....');
          }
        });
      });
    </script>
  </head>
  <body>
    <label for="privkey">Private Key</label><br/>
    <textarea id="privkey" rows="15" cols="65">   </textarea><br/>
    <label for="pubkey">Public Key</label><br/>
    <textarea id="pubkey" rows="15" cols="65">    </textarea><br/>
    <label for="input">Text to decrypt:</label><br/>
    <textarea id="input" name="input" type="text" rows=4 cols=70>This is a test!</textarea><br/>
    <input id="testme" type="button" value="Decrypt Me!!!" /><br/>
  </body>
</html>

Solution

  • If you use Crypto.Cipher.PKCS1_v1_5 it is possible.

    Here's the python code.

    I have only tested with python 3, but I believe it should work the same in python 2 with the __future__ imports.

    from __future__ import unicode_literals, print_function  # python2
    from Crypto.PublicKey import RSA
    from Crypto.Cipher import PKCS1_v1_5
    import base64
    
    private_key = """-----BEGIN RSA PRIVATE KEY-----
    MIIBOwIBAAJBANBOMQo9wX55+w1ijEaPoYRP2T4BOjoFv3ma0QWqYYQ8FH0z14Zc
    B/jb0j2PWpyNcsUUBovj+yWxQnQohCck64kCAwEAAQJBAL4s9PbNpO9MfFkfBMSS
    8zoyEDtcsYUxpDtojbandDpdXfvn5D279QaOVLb1C3DgQTTEmroYB8dbeZBc5YJC
    2AECIQDqyUn68ehRcx/EyLMUB1IuckZBWCIApgfn7phgVwSwiQIhAOMgY4bN+xrx
    UV15Ian4ZbkME1IbAvDPcWuNGHxdsaMBAiBoz0K/S44yDfp4lj+bCUmeglTqhrVn
    JLcSymgrWa02QQIhAMJFvPvcilGkYl1atCHHt3LN0mTjd+N0/OXq3SvblIsBAiAc
    8RzaV1GmjMEJxw9vM/tQwQg0kyAPlITMRXnwGA6E0A==
    -----END RSA PRIVATE KEY-----"""
    
    rsa = RSA.importKey(private_key)
    cipher = PKCS1_v1_5.new(rsa)
    
    def encrypt(msg):
        ciphertext = cipher.encrypt(msg.encode('utf8'))
        return base64.b64encode(ciphertext).decode('ascii')
    
    def decrypt(msg):
        ciphertext = base64.b64decode(msg.encode('ascii'))
        plaintext = cipher.decrypt(ciphertext, b'DECRYPTION FAILED')
        return plaintext.decode('utf8')
    
    ciphertext = encrypt('hello stackoverflow!')
    print(ciphertext)
    plaintext = decrypt(ciphertext)
    print(plaintext)
    

    Sample ciphertext output from plaintext "hello stackoverflow!":

    tZDRXXcf7ppbVr9JBHQ3+2k3geofl8BdDmLT3HRoqBGvfknY+xISbvy5hYH2alPAUDu2ae4iSYsLyRFBOnzpgw==
    

    In javascript, the original code should work. Here's a simplified version to demonstrate that this specific ciphertext and private rsa key works.

    const decrypt = () => {
      const privateKey = document.getElementById('private_key').value
      const cipherText = document.getElementById('ciphertext').value
      const decrypt = new JSEncrypt()
      decrypt.setPrivateKey(privateKey)
      const plainText = decrypt.decrypt(cipherText) || 'DECRYPTION FAILED'
      document.getElementById('plaintext').innerHTML = plainText
    }
    document.querySelector('button').addEventListener('click', decrypt)
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jsencrypt/2.3.1/jsencrypt.min.js"></script>
    <button>Decrypt</button>
    
    <p>plaintext: <em id=plaintext></em> </p>
    
    <label>ciphertext:</label><br>
    <textarea cols=64 rows=3 id=ciphertext>
    tZDRXXcf7ppbVr9JBHQ3+2k3geofl8BdDmLT3HRoqBGvfknY+xISbvy5hYH2alPAUDu2ae4iSYsLyRFBOnzpgw==
    </textarea>
    <br>
    <label>private key:</label><br>
    <textarea cols=64 rows=10 id=private_key>
    -----BEGIN RSA PRIVATE KEY-----
    MIIBOwIBAAJBANBOMQo9wX55+w1ijEaPoYRP2T4BOjoFv3ma0QWqYYQ8FH0z14Zc
    B/jb0j2PWpyNcsUUBovj+yWxQnQohCck64kCAwEAAQJBAL4s9PbNpO9MfFkfBMSS
    8zoyEDtcsYUxpDtojbandDpdXfvn5D279QaOVLb1C3DgQTTEmroYB8dbeZBc5YJC
    2AECIQDqyUn68ehRcx/EyLMUB1IuckZBWCIApgfn7phgVwSwiQIhAOMgY4bN+xrx
    UV15Ian4ZbkME1IbAvDPcWuNGHxdsaMBAiBoz0K/S44yDfp4lj+bCUmeglTqhrVn
    JLcSymgrWa02QQIhAMJFvPvcilGkYl1atCHHt3LN0mTjd+N0/OXq3SvblIsBAiAc
    8RzaV1GmjMEJxw9vM/tQwQg0kyAPlITMRXnwGA6E0A==
    -----END RSA PRIVATE KEY-----
    </textarea>