title: OAEP Padding Error When Decrypting Data in C# That Was Encrypted in JavaScript with RSA-OAEP
body: <p>Before I get too much into the details, the high level thing I'm trying to accomplish is encrypting some data in JavaScript, sending that to a web server, then decrypting that encrypted data in C#. The part I'm having trouble with is decrypting the data in C#.</p>
  
  <p>I'm encrypting some data in JavaScript like this (I removed the extraneous code):</p>
  
  <pre><code>// https://github.com/diafygi/webcrypto-examples#rsa-oaep---encrypt
  window.crypto.subtle.encrypt(
      {
          name: "RSA-OAEP"
      },
      publicKey,
      data
  )
  .then(function (encrypted) {
      // ...
  });
  </code></pre>
  
  <p>I confirmed that I can decrypt it in JavaScript like so (note that I don't actually want to do this, but I did it to prove that the data could be decrypted):</p>
  
  <pre><code>function decryptValue () {
  
      // Base64 decode the encrypted data for the value "Bob".
      var data = base64Decode("CthOUMzRdtSwo+4twgtjCA674G3UosWypUZv5E7uxG7GqYPiIJ+E+Uq7vbElp/bahB1fJrgq1qbdMrUZnSypVqBwYnccSxwablO15OOXl9Rn1e7w9V9fuMxtUqvhn+YZezk1623Qd7f5XTYjf6POwixtrgfZtdA+qh00ktKiVBpQKNG/bxhV94fK9+hb+qnzPmXilr9QF5rSQTd4hYHmYcR2ljVCDDZMV3tCVUTecWjS5HbOA1254ve/q3ulBLoPQTE58g7FwDQUZnd7XBdRSwYnrBWTJh8nmJ0PDfn+mCTGEI86S7HtoFYsE+Hezd24Z523phGEVrdMC9Ob1LlXEA==");
  
      // Get private key.
      var keyPromise = importPrivateKey();
      return keyPromise.then(function (privateKey) {
  
          // Decrypt the value.
          return window.crypto.subtle.decrypt(
              {
                  name: "RSA-OAEP"
              },
              privateKey,
              data
          )
          .then(function (decrypted) {
  
              // Log the decrypted value to the console.
              console.log(arrayBufferToString(decrypted));
  
          });
  
      });
  
  }
  </code></pre>
  
  <p>For simplicity, that code sample is decrypting a previously encrypted value of "Bob". This works fine.</p>
  
  <p>The problem occurs when I try to decrypt the value in C#:</p>
  
  <pre><code>public static string Decrypt()
  {
  
      // The encrypted and base64 encoded value for "Bob".
      var encryptedValue = "CthOUMzRdtSwo+4twgtjCA674G3UosWypUZv5E7uxG7GqYPiIJ+E+Uq7vbElp/bahB1fJrgq1qbdMrUZnSypVqBwYnccSxwablO15OOXl9Rn1e7w9V9fuMxtUqvhn+YZezk1623Qd7f5XTYjf6POwixtrgfZtdA+qh00ktKiVBpQKNG/bxhV94fK9+hb+qnzPmXilr9QF5rSQTd4hYHmYcR2ljVCDDZMV3tCVUTecWjS5HbOA1254ve/q3ulBLoPQTE58g7FwDQUZnd7XBdRSwYnrBWTJh8nmJ0PDfn+mCTGEI86S7HtoFYsE+Hezd24Z523phGEVrdMC9Ob1LlXEA==";
  
      // Assuming RSA-OAEP.
      var doOaep = true;
  
      // Setup encryption algorithm.
      var provider = GetPrivateKey();
  
      // Decrypt value.
      var encryptedData = Convert.FromBase64String(encryptedValue);
      // This line throws an error: "Error occurred while decoding OAEP padding."
      var decryptedData = provider.Decrypt(encryptedData, doOaep);
      var decryptedText = Encoding.Unicode.GetString(decryptedData);
  
      // Return decrypted text.
      return decryptedText;
  
  }
  </code></pre>
  
  <p>The line that says <code>provider.Decrypt(encryptedData, doOaep)</code> throws an error with a message of "Error occurred while decoding OAEP padding." The stack trace is:</p>
  
  <pre><code>Error occurred while decoding OAEP padding.
      at System.Security.Cryptography.RSACryptoServiceProvider.DecryptKey(SafeKeyHandle pKeyContext, Byte[] pbEncryptedKey, Int32 cbEncryptedKey, Boolean fOAEP, ObjectHandleOnStack ohRetDecryptedKey)
      at System.Security.Cryptography.RSACryptoServiceProvider.Decrypt(Byte[] rgb, Boolean fOAEP)
  </code></pre>
  
  <p>It seems like maybe the way the JavaScript is encrypting the value is not compatible with the way the C# is encrypting the value. Before I completely abandon this approach and try another JavaScript library for encryption, is there some way around this error?</p>
  
  <p>For additional context, I am guessing this error is related to something mentioned in this article: <a href="https://www.codeproject.com/Articles/11479/RSA-Interoperability-between-JavaScript-and-RSACry" rel="noreferrer">https://www.codeproject.com/Articles/11479/RSA-Interoperability-between-JavaScript-and-RSACry</a></p>
  
  <p>It says:</p>
  
  <blockquote>
    <p>Incompatible padding scheme from the JavaScript code would produce the
    "bad data" exception at the server side.</p>
    
    <p>The JavaScript code therefore needs to implement one of two padding
    schemes used in the .NET RSA implementation, the first is PKCS#1 v1.5
    padding and another is OAEP (PKCS#1 v2) padding.</p>
  </blockquote>
  
  <p>I'm not getting that exact exception, but maybe since that article was written the error message has changed. In any event, what that article says seems to imply that the way the JavaScript is encrypting isn't compatible with the way the C# is decrypting (namely, due to C#'s requirement for padding).</p>
  
  <p><strong>Is there something I'm missing? Is there some parameter or some easy way to get encryption working in JavaScript and decryption working in C#? Perhaps there is some C# library that decrypts in a way that is compatible with the way the JavaScript is encrypting?</strong></p>
  
  <p>Here's a full example that shows the JavaScript is decrypting properly (only works on some browsers... probably not going to work on IE):</p>
  
  <p><div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false">
  <div class="snippet-code">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>function decryptValue () {
  
      // Base64 decode the encrypted data for the value "Bob".
      var data = base64Decode("CthOUMzRdtSwo+4twgtjCA674G3UosWypUZv5E7uxG7GqYPiIJ+E+Uq7vbElp/bahB1fJrgq1qbdMrUZnSypVqBwYnccSxwablO15OOXl9Rn1e7w9V9fuMxtUqvhn+YZezk1623Qd7f5XTYjf6POwixtrgfZtdA+qh00ktKiVBpQKNG/bxhV94fK9+hb+qnzPmXilr9QF5rSQTd4hYHmYcR2ljVCDDZMV3tCVUTecWjS5HbOA1254ve/q3ulBLoPQTE58g7FwDQUZnd7XBdRSwYnrBWTJh8nmJ0PDfn+mCTGEI86S7HtoFYsE+Hezd24Z523phGEVrdMC9Ob1LlXEA==");
  
      // Get private key.
      var keyPromise = importPrivateKey();
      return keyPromise.then(function (privateKey) {
  
          // Decrypt the value.
          return window.crypto.subtle.decrypt(
              {
                  name: "RSA-OAEP"
              },
              privateKey,
              data
          )
          .then(function (decrypted) {
  
              // Log the decrypted value to the console.
              console.log("Decrypted value: " + arrayBufferToString(decrypted));
  
          });
  
      });
  
  }
  
  function importPrivateKey() {
      var rawKey = {
          "alg": "RSA-OAEP-256",
          "d": "E4KDwgxy7jFrqeXqKjxPTGOdbEoZ2aWj5qcZhUJcnr9Qh_jg_grkgpHVwEbQifTxsipXTiR3_ygspI4XFoeV-wDVfWqWCVR3_bHChF9PW8Ak1x_dBSS28BMs8PdthI1pDbpqPhmMcF4riHCtNo1M1v8cLdeaiqiXitNVBkaTePsDiucfwOy1rgxwBqAL1CNJhP8oRiYkxD-gfE_EapWuXY9-wF9O-lXPLSTKWgMmmVxSmhUP-Uqk7cJ24UH9C7W7hnSQU4pkfD5XHx3_2WO2GMKKZcqz39wJUrQzrIO7539SYsQ3rEe4aMJyL4U-Ib4_purzVS0DRjzGxK8chT2guQ",
          "dp": "kibhWHk1R6yBlhZbjIrNl9beAkyV5vtFsj_F0ixbIITzjSqI_td71sWjKQvJ2rR7hu5DYTZ4p3XwBeQ2jpYQV-y5uh4v7rGngh-0GHuHqMiUQnejgYGcHgng4iCM4e3aTO7QUlP8jqRfxw6xpfNTjrVbAL8LtdCG21vmqOiLkXE",
          "dq": "qLF9x-zKfaXlLsNgBQ1ZnaQexrnJRqrRh9JSU85fCNy5mmpKWAUbCHB-59CGAId8wMAnAyEpjcBOKNTqWSlNzp84xeUHcyPI-Dt4Yp_Y_dXjGAYntALSJs4qeF2rk55MSpiSD_KSU4DknX_E_G2rFMY7AZOSwi1D8YcNmj5okTE",
          "e": "AQAB",
          "ext": true,
          "key_ops": [
              "decrypt"
          ],
          "kty": "RSA",
          "n": "oQeTwOlTc6rIb2kddwIOc0Ywslc7YzJSRZd_PegW7T3nO3DqCI5kp5EJmnGP8JJ9sbyVYyAHFLZQtMP69UspZFn__fBk2LTp2QdqBSMHbObENcSiG2FH-pZSwCaj3Pvy-qvTjnkxxN-3OE6oB8EcX5ekZwCZzAxazbVXctY_hCcaTWG7ugwc_ZyvhsdE7wa3pnTfXYHWXcDDT8FTpYl62aqWsEIUAJSkgmQ9zce0RiDUjBJyJEM9P0ihp1Ab8BD88pEM22-PXfiOesRzp5yOsjzI3kdr5KPsshstneJEGHYae5GZXLUpnVMRY1TCFFLbkPwK6oVkRaVU1RvK9ssO3Q",
          "p": "2TTEToB4AuPIPPpg3yTyBlGb_m-f4r-TxpU96ConV2p696_4QI6jlPWwgcC9Vdma_Da43AGuyLzIptgkzF8nSjV80VwwDKQ1YkFPc6ZqB2isvExuieSP6_jLlB-fCyCLqtTxpPm2VcK16Pqm0s5T0QGH6cQjjm1r2Ww1wuaiQbk",
          "q": "vcpFwkZKZ3hx3FpHgy3ScuuTRSPO2ge8TE8UMJdCrEnpftAeYuVYrJqnxfzKgyl02OijAUi1eozJxj_lM5McxrKZEEAvo6e8wtzl2hnkUh-KWoBJ8ii0VJcu6U5vs4pcv-lYBPFC6fzoGnUw8LNWMxb5ejgYbLUWp10BbfkWGEU",
          "qi": "Mza7JYleki7BvmD3dX5CO2nkD3mBGz4_0P_aoWyHEkWu4p5XWillaRVWyLnQEubLvAduUCr-lhfNmzdUhHecpE438_LQNtKRyOq9zkvjsMOGDmbkKpZ7-aTSshax6KNlYOWdOkadjuLtRExCmwbzu5lgI4NwacxSs5MfjHMrTCo"
      };
      return window.crypto.subtle.importKey(
          "jwk",
          rawKey,
          {
              name: "RSA-OAEP",
              hash: { name: "SHA-256" }
          },
          true,
          ["decrypt"]
      );
  }
  
  function arrayBufferToString(buffer) {
      var result = '';
      var bytes = new Uint8Array(buffer);
      for (var i = 0; i &lt; bytes.length; i++) {
          result += String.fromCharCode(bytes[i]);
      }
      return result;
  }
  
  // Decodes a base64 encoded string into an ArrayBuffer.
  // https://stackoverflow.com/a/36378903/2052963
  function base64Decode(base64) {
      var binary_string = window.atob(base64);
      return stringToArrayBuffer(binary_string);
  }
  
  // Converts a string to an ArrayBuffer.
  function stringToArrayBuffer(value) {
      var bytes = new Uint8Array(value.length);
      for (var i = 0; i &lt; value.length; i++) {
          bytes[i] = value.charCodeAt(i);
      }
      return bytes.buffer;
  }
  
  decryptValue();</code></pre>
  </div>
  </div>
  </p>
  
  <p>BTW, some of my code samples show the private key I'm using. That's intentional to help you understand the code (it's a throw away key). In fact, here's how I am getting the private key in C#:</p>
  
  <pre><code>private static RSACryptoServiceProvider GetPrivateKey()
  {
      RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
      RSAParameters RSAparams = new RSAParameters();
      RSAparams.Modulus = Base64UrlDecode("oQeTwOlTc6rIb2kddwIOc0Ywslc7YzJSRZd_PegW7T3nO3DqCI5kp5EJmnGP8JJ9sbyVYyAHFLZQtMP69UspZFn__fBk2LTp2QdqBSMHbObENcSiG2FH-pZSwCaj3Pvy-qvTjnkxxN-3OE6oB8EcX5ekZwCZzAxazbVXctY_hCcaTWG7ugwc_ZyvhsdE7wa3pnTfXYHWXcDDT8FTpYl62aqWsEIUAJSkgmQ9zce0RiDUjBJyJEM9P0ihp1Ab8BD88pEM22-PXfiOesRzp5yOsjzI3kdr5KPsshstneJEGHYae5GZXLUpnVMRY1TCFFLbkPwK6oVkRaVU1RvK9ssO3Q");
      RSAparams.Exponent = Base64UrlDecode("AQAB");
      RSAparams.D = Base64UrlDecode("E4KDwgxy7jFrqeXqKjxPTGOdbEoZ2aWj5qcZhUJcnr9Qh_jg_grkgpHVwEbQifTxsipXTiR3_ygspI4XFoeV-wDVfWqWCVR3_bHChF9PW8Ak1x_dBSS28BMs8PdthI1pDbpqPhmMcF4riHCtNo1M1v8cLdeaiqiXitNVBkaTePsDiucfwOy1rgxwBqAL1CNJhP8oRiYkxD-gfE_EapWuXY9-wF9O-lXPLSTKWgMmmVxSmhUP-Uqk7cJ24UH9C7W7hnSQU4pkfD5XHx3_2WO2GMKKZcqz39wJUrQzrIO7539SYsQ3rEe4aMJyL4U-Ib4_purzVS0DRjzGxK8chT2guQ");
      RSAparams.P = Base64UrlDecode("2TTEToB4AuPIPPpg3yTyBlGb_m-f4r-TxpU96ConV2p696_4QI6jlPWwgcC9Vdma_Da43AGuyLzIptgkzF8nSjV80VwwDKQ1YkFPc6ZqB2isvExuieSP6_jLlB-fCyCLqtTxpPm2VcK16Pqm0s5T0QGH6cQjjm1r2Ww1wuaiQbk");
      RSAparams.Q = Base64UrlDecode("vcpFwkZKZ3hx3FpHgy3ScuuTRSPO2ge8TE8UMJdCrEnpftAeYuVYrJqnxfzKgyl02OijAUi1eozJxj_lM5McxrKZEEAvo6e8wtzl2hnkUh-KWoBJ8ii0VJcu6U5vs4pcv-lYBPFC6fzoGnUw8LNWMxb5ejgYbLUWp10BbfkWGEU");
      RSAparams.DP = Base64UrlDecode("kibhWHk1R6yBlhZbjIrNl9beAkyV5vtFsj_F0ixbIITzjSqI_td71sWjKQvJ2rR7hu5DYTZ4p3XwBeQ2jpYQV-y5uh4v7rGngh-0GHuHqMiUQnejgYGcHgng4iCM4e3aTO7QUlP8jqRfxw6xpfNTjrVbAL8LtdCG21vmqOiLkXE");
      RSAparams.DQ = Base64UrlDecode("qLF9x-zKfaXlLsNgBQ1ZnaQexrnJRqrRh9JSU85fCNy5mmpKWAUbCHB-59CGAId8wMAnAyEpjcBOKNTqWSlNzp84xeUHcyPI-Dt4Yp_Y_dXjGAYntALSJs4qeF2rk55MSpiSD_KSU4DknX_E_G2rFMY7AZOSwi1D8YcNmj5okTE");
      RSAparams.InverseQ = Base64UrlDecode("Mza7JYleki7BvmD3dX5CO2nkD3mBGz4_0P_aoWyHEkWu4p5XWillaRVWyLnQEubLvAduUCr-lhfNmzdUhHecpE438_LQNtKRyOq9zkvjsMOGDmbkKpZ7-aTSshax6KNlYOWdOkadjuLtRExCmwbzu5lgI4NwacxSs5MfjHMrTCo");
      RSA.ImportParameters(RSAparams);
      return RSA;
  }
  
  // From the PDF here: https://www.rfc-editor.org/info/rfc7515
  // Also see: https://auth0.com/docs/jwks
  public static byte[] Base64UrlDecode(string arg)
  {
      string s = arg;
      s = s.Replace('-', '+'); // 62nd char of encoding
      s = s.Replace('_', '/'); // 63rd char of encoding
      switch (s.Length % 4) // Pad with trailing '='s
      {
          case 0: break; // No pad chars in this case
          case 2: s += "=="; break; // Two pad chars
          case 3: s += "="; break; // One pad char
          default:
              throw new System.Exception(
          "Illegal base64url string!");
      }
      return Convert.FromBase64String(s); // Standard base64 decoder
  }
  </code></pre>