Não é possível descriptografar a mensagem RSA-OAEP criptografada com PyCryptodome usando a API SubtleCrypto Web Crypto

Aug 26 2020

No lado do servidor, estou usando o PyCryptodome para criptografar uma mensagem com RSA-OAEP (com SHA-256).
Estou tentando descriptografar a mensagem usando a API SubtleCrypto Web Crypto no lado do cliente, mas isso me dá um erro DOMException sem mais detalhes.
No SubtleCrypto consigo importar a chave privada gerada no PyCryptodome sem problemas, mas dá o erro quando estou tentando descriptografar a mensagem.

Também tentei importar a chave pública gerada no PyCryptodome no lado do cliente para criptografar a mesma mensagem com o SubtleCrypto. Nesse caso, posso descriptografá-lo sem problemas, usando o mesmo fluxo de antes.

Os algoritmos RSA-OAEP entre essas duas bibliotecas são incompatíveis? Percebi que PyCryptodome faz referência a RFC 8017(v2.2) e SubtleCrypto RFC 3447(v2.1) em suas respectivas documentações.

Editar :

  • Código do lado do servidor (pycryptodome==3.9.8):

      from Crypto.PublicKey import RSA
      from Crypto.Cipher import PKCS1_OAEP
    
      class Cipher:
    
      def rsa_encrypt(self, data, key_string):
          key = RSA.importKey(key_string)
          rsa_encryption_cipher = PKCS1_OAEP.new(key)
          ciphertext = rsa_encryption_cipher.encrypt(data)
          return base64.b64encode(ciphertext)
    
      def rsa_decrypt(self, data, key_string):
          data = base64.b64decode(data)
          key = RSA.importKey(key_string)
          rsa_decryption_cipher = PKCS1_OAEP.new(key)
          plaintext = rsa_decryption_cipher.decrypt(data)
          return plaintext
    
    ( ... ) 
    
  • Código do lado do cliente

    private decryptRSAString (encryptedText: string, privateKey: string) : Observable<ArrayBuffer> {
    
      return Observable.create ((observer: any) => {
    
        let keyBuffer: ArrayBuffer = this.str2ab(window.atob(privateKey));
        let encryptedTextBuffer: ArrayBuffer = this.str2ab(window.atob(encryptedText));
        let algorithmParams: RsaHashedImportParams = {
          name: "RSA-OAEP",
          hash: "SHA-256"
        };
        window.crypto.subtle.importKey(
          'pkcs8',
          keyBuffer,
          algorithmParams,
          true,
          ["decrypt"]
        ).then (
          (cryptoKey: CryptoKey) => {
            window.crypto.subtle.decrypt(
              {
                name: "RSA-OAEP"
              },
              cryptoKey,
              encryptedTextBuffer
            ).then (
              (decryptedMessage: ArrayBuffer) => {
                observer.next (decryptedMessage);
                observer.complete();
              },
              (error: any) => {
                observer.error (error)
              }
            )
          },
          (error: any) => {
            observer.error (error)
          }
        );
      });
    }
    

Respostas

5 Topaco Aug 26 2020 at 20:27

PyCryptodome não aplica SHA-256 como resumo padrão para OAEP, mas SHA-1, aqui . Assim, o SHA-1 deve ser usado no lado do WebCrypto :

let algorithmParams: RsaHashedImportParams = {
    name: "RSA-OAEP",
    hash: "SHA-1"
};

Claro que você também pode aplicar SHA-256 no lado do PyCryptodome , então nenhuma alteração é necessária no lado do WebCrypto .

from Crypto.Hash import SHA256
...
rsa_encryption_cipher = PKCS1_OAEP.new(key, SHA256) # default: Crypto.Hash.SHA1

Com resumos consistentes em ambos os lados, posso descriptografar com êxito um texto cifrado com seu código WebCrypto , que gerei anteriormente com seu código PyCryptodome (usando minhas próprias chaves).