javascript 使用Web Crypto API解密使用Django中的Python pycrytodome库加密的密文

jjjwad0x  于 2022-12-28  发布在  Java
关注(0)|答案(1)|浏览(187)

I am running a django app and I am trying to use a RSA 'challenge' to verify that a user has the correct private key. The keys are generated client side using the js Web Crypto API and the public key is sent to django as a jwk and the private key is stored in a client-side pem file. I want the django view to send an encrypted uuid to the client page where a user has loaded their private key file. Then the page locally decrypts the uuid and sends it back to the server for authentication. It seems like I have gotten everything to work except for the client side decryption.
Here is the relevant portion of the django/python view:

uuid = secrets.token_hex(16)
        key = json.loads(request.POST.get('key')) //key is in JWK format

        e = int.from_bytes(base64.b64decode(base64url_to_base64(key['e'])), "big")
        n = int.from_bytes(base64.b64decode(base64url_to_base64(key['n'])), "big")
        rsakey = RSA.construct((n, e), consistency_check=True)

        cipher = PKCS1_OAEP.new(rsakey, SHA256)
        challenge = cipher.encrypt(uuid.encode())
        challenge = base64.b64encode(challenge)

Here is the relevant portion of the client-side js:

fileReader.readAsText(file);
        fileReader.onload = function() {
          filekey = fileReader.result;
          filekey = filekey.substring(filekey.indexOf('-----BEGIN PRIVATE KEY-----'));

          let pem = filekey;
          const pemHeader = "-----BEGIN PRIVATE KEY-----";
          const pemFooter = "-----END PRIVATE KEY-----";
          const pemContents = pem.substring(pemHeader.length, pem.length - pemFooter.length);
          const binaryDerString = window.atob(pemContents);
          const binaryDer = str2ab(binaryDerString);

          challenge = str2ab(window.atob(challenge.substring(2, challenge.length - 1));
          console.log(challenge);

          window.crypto.subtle.importKey(
            "pkcs8",
            binaryDer,
            {
              name: "RSA-OAEP",
              hash: {name: "SHA-256"}
            },
            true,
            ["decrypt"]
          ).then((key) => {

            const decryptedMessage = window.crypto.subtle.decrypt(
              {
                name: "RSA-OAEP"
              },
              key,
              challenge
            ).then((txt) => {
              console.log(txt);
              console.log(ab2str(txt));

            });

When I tried this, I get the error Uncaught (in promise) DOMException: The operation failed for an operation-specific reason from the window.crypto.subtle.decrypt(). I think there is some issue with the format of the ciphertext as the pycryptodome Crypto.PublicKey.PKCS1_OAEP.encrypt() function takes in a byte string whereas the Web Crypto API uses arraybuffer objects. However, I do not have enough experience with js and python data types to figure it out. It could also be possible that it is an issue with constructing the key in python using the JWK.
Any help or suggestions would be greatly appreciated!

Update: As suggested by @Topaco here is some test data:
jwk public key : {"alg":"RSA-OAEP-256","e":"AQAB","ext":true,"key_ops":["encrypt"],"kty":"RSA","n":"tTW0HHD56Lv-FmDcucLOkBTCcT3ySRDtZ64MmsgFnZWGvCOAa3Q1kKYo8RAHWtjrvac_2enHF4LZlys2kS_j1kLZeyatsDWPuMDAHunRu-jscfQoSIODB1hc8YcPiG0vVLDEBY-VKozSOje6GcXWKcaYi4kFkbLmIIJgzHYoOccflAyXl_FvVHgcU5z5qYk8JjucZfqf9rzTH3HTaeCux2SMqJr6ubBmwX8-iwyC-4LBnnf27rdGL-DcMsOCq_CPWfgtx7nav9OCt51PdszsYx3JGLsbp0-iH1mSjKs4dg3ORh6KOyP2Qbq_HRALI__OKonp5FopApWSFvVDJZf3EhIHC2upnbpj-UCcjzkSSm0h3GkTr13GCqfhRz0jRvK-1Yj4PuwmXJ5kr1gxSbokqAnRL0oFicP_wTakvQOB7XpMWz2Cl3NDLDvqhocVMHZ9HwH52fD7k9IBYHXh6cVqeOwKSIy4whAyYFLmKg-57LwKB1diiSgi6MeCMG3NFafeEm3llooAmOTZZic_uD59-zfggywf6YOyBScilYBGWKxA9P-UVK76rxJIlwInDv7U1uY-8RodjPTNcGRw8RhvIyTkfgfpLRejXxAVEx0Xu-Gr4nZ2hPQgCQwP6pUL-ohN1Lz5Y0y6GWXrGcA2N1WY7GfBqrcNhWv7xXNVA1Qcei0"}

pem private key: -----BEGIN PRIVATE KEY----- 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 -----END PRIVATE KEY-----
plaintext/uuid: 060433466b8d0be7e7d435bdd43752b5
ciphertext after b64 encode in python: b'iN+uii2BAktcgSKnt2WLZ3baAJQxNYvD3AYTwumoTT+GYMKnydPVAT264XHbsHTzeLXQjIWPi1FT3Npa7PJUewDNMCKyzrZBLgyT6HOr6QIp0TJyPx83u1osPQw1clSf22ypOEeMc9JKuePDDVu1cH61gIYU4SoLMSpxf0oukVSa1Cg3Rficiscewtn36KBkBKY4M/CTde5lwiCf1x7ZtRboSsffZiUnD2dpRvZ0GcQugW7nV583mSVadtqfxdlVrXsa9hhnZhvBHuElHOByyH5GLICEKtHXZEraNTkMvnWFWpHrMz4xSX4O0c9JP2LD61t/8mnUwvHhutsxLSbULXBvcba8+e6+KYZsw14+Ol7h7235dPEYOXmLbNGU/KCQeznu9VaJMlN8CjF8MUfDPAOSIh8OcL74cKdh8U9CmhbFSOT+TjDYbvaXIFH0D3tY/nlg3C0O6uUSZmS+7qCHbNKGVvdDK0iagnWqu3+9i/hekFKP22SJtWPYw1d6d6w3vnqzwePPtFS7D8/14D4/0hjESOSpq2X1ZBhKBlatlma8HDOl9LEgxXs791fkoj4y5XevePW2r84OCuQL+6DtaUduLDWIoCLV+LnU/t8m5AuTDCEh7cpxYfgYeexggPzk7lEJtByKkrBHqC2Egwp0GCvnIZB+4oENfO+4ZqK2C4s='
Update: Was able to fix the issue, see my code blocks. The problem was that the python returns the b64 string surrounded by b'' , which don't work with atob().

yzckvree

yzckvree1#

为了解决我的问题,我首先必须在服务器端加密中使用SHA256:
包括来自pypcryptodome的SHA256:

from Crypto.Hash import SHA256

使用SHA256加密:

cipher = PKCS1_OAEP.new(rsakey, SHA256)
challenge = cipher.encrypt(uuid.encode())

接下来,我在服务器上用base64对密文进行编码:

challenge = base64.b64encode(challenge)

然后,当准备在客户端解密密文时,我使用substring删除密文开头和结尾的b'',然后在转换到数组缓冲区之前使用atob():

challenge = str2ab(atob(challenge.substring(2,challenge.length - 1)));

相关问题