3

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>
Anum Sheraz
  • 1,400
  • 17
  • 37
  • Depending on the algorithm implementation the python and javascript maybe be a bit different, sad but true – Robert Moskal Jul 03 '17 at 19:46
  • 2
    JSencrypt uses PKCS#1 v1.5 padding whereas your Python code doesn't use any padding (textbook RSA is bad). – Artjom B. Jul 03 '17 at 19:54
  • SInce you are totally new in Cryptography are you sure you need RSA (asymmetric) encryption and not just AES (symmetric) data encryption? The issue is that with RSA the data to be encrypted must have a length less than the key length which means less than 128-bytes of data for a 1024-bit key. RSA is usually used to encrypt symmetric keys and AES to encrypt data. – zaph Jul 03 '17 at 20:13
  • I need to check user authentication by digitally signing. I've checked the length of encrypted data that is 128 bytes. is there any way to reduce the length ? – Anum Sheraz Jul 03 '17 at 20:20
  • 1
    You made a fundamental, all breaking error in applying RSA: you never transmit the private key. It always remains on the side where it was generated. – Klaus D. Jul 03 '17 at 20:42
  • 1
    The length of the encrypted data is fine, it is the data to be encrypted must have a length less than the key length. But you are signing with the private key not encryption, right? It might be a good idea to have the entire authentication scheme reviewed. – zaph Jul 03 '17 at 20:53
  • @KlausD. yes that makes sense. I will do that in the way you said. – Anum Sheraz Jul 03 '17 at 20:56
  • @zaph Yes I know I have to sign it with my private key for digital signature. That is my final goal. I am just trying to do the enc/dec at both ends for now. I am checking out the PKCS1_v1_5 package from Crypto library. That might do the trick. – Anum Sheraz Jul 03 '17 at 20:58
  • 1
    If you're using asymmetric encryption, you need to make sure that the public key or private key that is delivered to the browser can be trusted. This is not an assumption that can generally be made, because any kind of JavaScript code can be injected into a HTTP connection. You would need HTTPS (with TLS) to protect the public key from being changed, but if you have that, you don't need JavaScript encryption anymore. – Artjom B. Jul 03 '17 at 21:59

1 Answers1

9

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>
Håken Lid
  • 18,252
  • 8
  • 40
  • 58
  • That's a great solution, but I want to decrypt the data by using public key. I am generating key pair by RSA.generate and passing it on to PKCS1_v1_5 object. but JS fails to decrypt it using public key. – Anum Sheraz Jul 04 '17 at 00:09
  • 3
    @AnumSheraz It's not possible to decrypt with a public key. You probably mean digital signatures. – Artjom B. Jul 04 '17 at 05:07
  • 1
    But digital signature means that you need to have public key of the source from where data is coming from. Have a look at the picture below at this article, https://www.pubnub.com/blog/2015-07-06-chat-security-user-identification-with-digital-signature-message-verification/ – Anum Sheraz Jul 04 '17 at 13:10