2

I'm trying to encrypt the data twice with rsa public keys, I'm getting the following error in python,

Traceback (most recent call last):
  File "/Users/pbhat1/Documents/dev/registrationdemo/venv/lib/python3.8/site-packages/flask/app.py", line 2447, in wsgi_app
    response = self.full_dispatch_request()
  File "/Users/pbhat1/Documents/dev/registrationdemo/venv/lib/python3.8/site-packages/flask/app.py", line 1952, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/Users/pbhat1/Documents/dev/registrationdemo/venv/lib/python3.8/site-packages/flask/app.py", line 1821, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/Users/pbhat1/Documents/dev/registrationdemo/venv/lib/python3.8/site-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "/Users/pbhat1/Documents/dev/registrationdemo/venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request
    rv = self.dispatch_request()
  File "/Users/pbhat1/Documents/dev/registrationdemo/venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "app.py", line 39, in register_user
    first_encrypted = client_key_decrypter.decrypt(data['cipher'])
  File "/Users/pbhat1/Documents/dev/registrationdemo/venv/lib/python3.8/site-packages/Crypto/Cipher/PKCS1_OAEP.py", line 167, in decrypt
    raise ValueError("Ciphertext with incorrect length.")
ValueError: Ciphertext with incorrect length.

What I'm trying to do is

generate the public and private keys in flask,

generate the client public and private keys to use unique to each call,

encrypt the data client side using jsencrypt,

decrypt the data server side using pycryptodome

here's my code in app.py

from flask import Flask
from flask import render_template
from flask import request
import Crypto
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
from Crypto import Random
from base64 import *

application = Flask(__name__, static_url_path='/static')
random_generator = Random.new().read
key = RSA.generate(2048, random_generator) #generate public and private keys

@application.route("/")
def hello():
    return "<h1 style='color:blue'>Hello There!</h1>"

@application.route("/register")
def register():
    crypto_key = key.publickey().export_key()
    client_random_generator = Random.new().read
    client_key = RSA.generate(4096, client_random_generator) #generate for client-side encryption
    print(client_key)
    return render_template('register.html', 
        serverCrypto = crypto_key.decode("utf-8"),
        clientCrypto = client_key.publickey().export_key().decode("utf-8"), 
        randomName1 = client_key.export_key().decode("utf-8"), 
        randomName2 = None, 
        randomName3 = None)

@application.route("/users", methods=['POST'])
def register_user():
    error = None
    if request.method == "POST":
        data = request.get_json()
        print(data)
        client_key = RSA.importKey(data['clientPrivateKey'])
        client_key_decrypter = PKCS1_OAEP.new(client_key)
        first_encrypted = client_key_decrypter.decrypt(data['cipher'])
        server_key = PKCS1_OAEP.new(key.export_key())
        second_encrypted = server_key.decrypt(first_encrypted)
        print(second_encrypted)
    return render_template('login.html', error=error)

@application.route("/crypto", methods=['POST'])
def get_crypto():
    if request.method == "POST":
        return None

if __name__ == "__main__":
    application.run(ssl_context=('cert.pem', 'key.pem'))

my html code

<!DOCTYPE html>
<html>
<head>
    <title>Demo!</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jsencrypt/2.3.1/jsencrypt.min.js"></script>
    <script type="text/javascript" src="/static/js/register.js"></script>
    <style type="text/css">
            input { margin-right: 100% }
        </style>
</head>
<body>
<h2>Register</h2>
<form id="register">
    <label>Username: <input name="username" type="text"
                            id="username">
    </label>
    <label>Password: <input name="password" type="password"
                            id="password">
    </label>
    <input type="hidden" name="serverCrypto" id="serverCrypto" value="{{ serverCrypto }}"/>
    <input type="hidden" name="clientCrypto" id="clientCrypto" value="{{ clientCrypto }}"/>
    <input type="hidden" name="randomName1" id="randomName1" value="{{ randomName1 }}"/>
    <input type="hidden" name="randomName2" id="randomName2" value="{{ randomName2 }}"/>
    <input type="hidden" name="randomName3" id="randomName3" value="{{ randomName3 }}"/>
    <button type="register">Register</button>
</form>
</body>
</html>

and my javascript code

const apiUrl = 'https://localhost:5000';


function register(username, password, serverCrypto, clientCrypto, randomName1) {
    let data = {"username": username, "passwword": password}

    const encrypt = new JSEncrypt();
    encrypt.setPublicKey(serverCrypto);
    var encrypted = encrypt.encrypt(JSON.stringify(data));
    console.log(encrypted); // shows up as false
    encrypt.setPublicKey(clientCrypto);
    encrypted = encrypt.encrypt(encrypted);
    console.log(encrypted);
    dataToSend = {"cipher": encrypted, "clientPrivateKey": randomName1};

    fetch(apiUrl + '/users', {
        method: 'POST',
        body: JSON.stringify(dataToSend),
        headers: {
            'Content-Type': 'application/json',
        }
    })
    .then(res => {
       if (res.ok) {
         res.json().then(json => {
            console.log(json);
         });
       }
    })
    .catch(error => console.error('Error logging in: ', error));
}

window.addEventListener('load', function(e) {
    document.getElementById('register')
        .addEventListener('submit', processRegisterSubmit);
});

function processRegisterSubmit(e) {
    e.preventDefault();

    let username = document.getElementById('username').value;
    let password = document.getElementById('password').value;
    let serverCrypto = document.getElementById('serverCrypto').value;
    let clientCrypto = document.getElementById('clientCrypto').value;
    let randomName1 = document.getElementById('randomName1').value;
    let randomName2 = document.getElementById('randomName2').value;
    let randomName3 = document.getElementById('randomName3').value;
    

    register(username, password, serverCrypto, clientCrypto, randomName1);
    return false;
}

testasker
  • 29
  • 3
  • 2
    It is not possible to encrypt a message twice in succession with RSA if two keys of the same size are used. This is because on the one hand the ciphertext has the size of the key and on the other hand the data to be encrypted must be smaller than the key size. For the second encryption, the latter is not fulfilled anymore. But this does not explain the exception with the first encryption, post example data (for `serverCrypto` and for `data`). – Topaco May 29 '22 at 10:37
  • updated code to use a different key size though still getting the same error – testasker May 29 '22 at 11:58
  • 2
    Not reproducible i.e the 1st encryption works: https://jsfiddle.net/hLnkw26g/ – Topaco May 29 '22 at 12:23
  • 1
    turns out the problem was with python sending it as utf-8 so decoded it first before sending client-side then javascript started working, decrypting it on the python side didn't work though – testasker May 29 '22 at 13:29
  • Another problem are different paddings, OAEP and PKCS#1 v1.5 on the Python and JavaScript side. Since JSEncrypt only supports PKCS#1v1.5, on the Python side PKCS#1v1.5 must be used instead of OAEP (s. [here](https://pycryptodome.readthedocs.io/en/latest/src/cipher/pkcs1_v1_5.html?highlight=pkcs1%20v1.5#pkcs-1-v1-5-encryption-rsa)). – Topaco Jun 01 '22 at 21:20
  • Or, instead of JSEncrypt, use the [SubtleCrypto](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto) interface of the [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) which supports OAEP. – Besworks Jun 01 '22 at 22:04
  • If changing the library is an option, other libraries besides WebCrypto should be also considered ([here](https://gist.github.com/jo/8619441)), e.g. forge. WebCrypto has the advantage of being available in most browsers, but as low level API it's not particularly comfortable (regarding handling and functionality). – Topaco Jun 02 '22 at 06:47

0 Answers0