Pycryptodome incorrect decryption

Question:

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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
-----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=
Asked By: Randusr

||

Answers:

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.

Answered By: Topaco