Search code examples
javascriptpythonencryptionwebcrypto-apipycryptodome

Pycryptodome incorrect decryption


I am generating a RSA key pair in a django view, storing the keys in db, sending the public key to the client template, encrypting a message, then sending the message to a different view where I am retrieving the private key from the db and trying to decrypt the message. When I do the decryption, I get ValueError: Incorrect decryption..

Server: Generate keys, store in db, send public key to client:

def generateKeys(request):
    store = Keys.objects.filter(pk=request.GET.get('pk'))
    serverkeypair = Crypto.PublicKey.RSA.generate(4096)
    spk = serverkeypair.publickey().exportKey(format='PEM', passphrase=None, pkcs=8, protection=None, randfunc=None)
    sprk = serverkeypair.exportKey(format='PEM', passphrase=None, pkcs=8, protection=None, randfunc=None)
    spk = spk.decode()
    store.update(spk = spk)
    store.update(sprk = sprk.decode())
    return JsonResponse(['success', spk], safe=False)

Client: Import public key, encrypt message:

fetch('../api/endpoint1/?pk='+pk, {
    method: 'GET',
    headers: { 'Content-Type': 'application/json' },
}).then(response => response.json())
  .then(async data => {
        var pbk = data[1];
        pbk = pbk.substring(27, pbk.length - 25);
        var plaintext = window.crypto.getRandomValues(new Uint8Array(64));
        console.log(btoa(ab2str(plaintext)));
        
        window.crypto.subtle.importKey(
          "spki",
          str2ab(atob(pbk)),
          {
            name: "RSA-OAEP",
            hash: "SHA-256"
          },
          true,
          ["encrypt"]
         ).then((imp_key) => {
             window.crypto.subtle.encrypt({name: 'RSA-OAEP'}, imp_key, plaintext).then((ciphertext) => {
                 ciphertext = btoa(ab2str(ciphertext));
                 console.log(ciphertext);
                 ciphertext = b64_to_b64url(ciphertext);
                 fetch('/api/endpoint2/?pk='+pk+'&ciph='+ciphertext);
             });
          });
  });


/*
Convert  an ArrayBuffer into a string
from https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
*/
function ab2str(buf) {
  return String.fromCharCode.apply(null, new Uint8Array(buf));
}


/*
Convert a string into an ArrayBuffer
from https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
*/
function str2ab(str) {
 const buf = new ArrayBuffer(str.length);
 const bufView = new Uint8Array(buf);
 for (let i = 0, strLen = str.length; i < strLen; i++) {
   bufView[i] = str.charCodeAt(i);
 }
 return buf;
}

function b64_to_b64url(s) {
    return s.replace(/\+/g, '-').replace(/\//g, '_');
}

Server: Retrieve key, decrypt message:

def decrypt(request):
    store = Keys.objects.get(pk=request.GET.get('pk'))
    sprk = getattr(store, 'sprk')
    key = RSA.importKey(sprk.encode())
    cipher = PKCS1_OAEP.new(key)
    ciphertext = request.GET.get('ciph')
    ciphertext = base64.b64decode(b64url_to_b64(ciphertext))
    plaintext = cipher.decrypt(ciphertext)
    print(plaintext)
    return JsonResponse(['success'], safe=False)

def b64url_to_b64(str):
    return str.replace('-', '+').replace('_', '/')

Test data:

-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoxvH93Qpcbg75fMMkBxp
gWNaIamVo4dvwc7xi8o2JQTYOU+wzCYE5Hs7qN2YoPMJdDQHNqvgIfHpfpLwcIaA
F2qrojRLA8YvJoKpamlg/ui0xyu8UNtYgkfhatwihHOGl/eYkzqgeu1JQ6maF/PO
MfVv6ulioTDPEZaREx4Gj6iVgLRhW7eRu0oYG4tGoTmzmze1sIbxZvTkZWDdp9Y2
S6dsUHllsROUFUH2d/RxKJXoG7jEMjki8l+CaxkqNxjffQGBkg060Pj+Os6QbICZ
Ql4KYp+KNsRhQduaJkYsYuA5k/bl+BIWXh9E5o2eFNPQ0d6qm72Zl+ryxBwno2Sj
JYlYN9foRYxJB+2E759eAnLASK3amZaeQRrzMdbsWLzRDJDTSBzvqqmK2b5tFe7W
GM7JyVg/sHwLlY+hVoIQ8+WbuV/knxm3IV6XXUhg8VtFtlFYdcY4yVwO1K7yFzQN
h7mZjC8AhOGsw7oqzzExEasMdIsJ8DdkdtRUYJjcQaQiPU9luyVNvMFIZ4wkUTvm
FHyB5kHhwT7uWkFfkj9HXle9CQxhdlQIM2kfc3H7XBH+l4qf90pjg0cP7iG1semc
jyvv0WcFmatsAEBQ+XbIxqw76XtWh8UNGgypkI0PlVVRjKauywH6YofsjTATsE7H
asL5I39I2/XxJ9gg+0rqD3kCAwEAAQ==
-----END PUBLIC KEY-----

-----BEGIN PRIVATE KEY-----
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\nh78PtsliPE9KF/6EGS6Ysagxa2HuOQ==
-----END PRIVATE KEY-----


Plaintext (converted to string, then base64 encoded):
2w+c4ONLd50j1uipIXJ4yJlMnD1UtYuYRK6fTchr2RM4XMTz6vpB21Qu4qFIjJJGavub/OhV29c/8PB82L4www==

Ciphertext:
fIi24T8TXlYWyl+SfTVIkmF/aBc1nYPbsdtaMGQwtrOxDWjwEW7rK4b5i1dgGq3dpH2nxTxx0HwunFsD51I5I+XiGjeODGubSbM4+YNplFnu+cQ6YcN9M4jNVKkYrkDvEQUtyrM/G3oYIjZSVSh30Yb2ZyR5Tw5Tx+PAzXadUi/aUpyk0Yo92saBFGPRZMFxKuE4mozQJuNYug/qmk8+VTUd4djsWx+MFgEb+99/dTvupHu25mXA/NOFqw6t3SF4J3PW8nwlrztMnyrMKaLVJAWjNRYRDbroM3PkRG2dPbNVtg0d5e+GeVQvGFoaV2xukkP+QS2EqlQcWE1UdMvSHOx9jKp01EFrYoBjPgu/LlU1gpcqoA8yHIjiNaZ1mEpck0znKPeZmxPMh3n3XBGTO4U6t1/aPgBmn5FD8r7tPw2HJQmDEDpoGRsD+9T9CjrgGoB8Cpt2VQBqA1BPBfhrIKUdY/HHj5zCF5RHYujdMNn/qmLleN2fH/Ee3kFZ83s27fGpkwJIZD6Z9NMvIGfwJ9JKLM8xnm26KcXBKgml0Qw6py/9tE0WasEhWlv4KQOdfVfdjP6yp+wzNH8SgjBAMy6ANhCLLiY6MzgenDKPJBPZK/z+QiVakSOJ/boSpckjxDxnxo4LA+yZkWYwsR9UjWH9IC/Uj3ud23yiDawqfdk=

Solution

  • Encryption and decryption apply RSA with OAEP as padding. However, in the WebCrypto code SHA-256 is explicitly specified for content and MGF1 digests, while in the PyCryptodome code the default value SHA-1 is used for both digests when instantiating the cipher object, see PKCS1_OAEP and also this post of yours.

    To fix the issue, pass SHA256 as second parameter in the PyCryptodome code when instantiating the cipher object:

    from Crypto.Cipher import PKCS1_OAEP
    from Crypto.Hash import SHA256
    ...
    cipher = PKCS1_OAEP.new(key, SHA256) 
    

    then decryption is successful.