使用 RSA-OAEP 在 JavaScript 中加密的 C# 中解密数据时出现 OAEP 填充错误
OAEP Padding Error When Decrypting Data in C# That Was Encrypted in JavaScript with RSA-OAEP
在我深入了解细节之前,我要完成的高级任务是在 JavaScript 中加密一些数据,将其发送到 Web 服务器,然后在 C# 中解密该加密数据。我遇到问题的部分是用 C# 解密数据。
我正在像这样加密 JavaScript 中的一些数据(我删除了无关代码):
// https://github.com/diafygi/webcrypto-examples#rsa-oaep---encrypt
window.crypto.subtle.encrypt(
{
name: "RSA-OAEP"
},
publicKey,
data
)
.then(function (encrypted) {
// ...
});
我确认可以在JavaScript中像这样解密(注意我其实并不想这样做,但我这样做是为了证明数据可以被解密):
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));
});
});
}
为简单起见,该代码示例正在解密先前加密的 "Bob" 值。这很好用。
在C#中尝试解密值时出现问题:
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;
}
表示 provider.Decrypt(encryptedData, doOaep)
的行抛出错误消息 "Error occurred while decoding OAEP padding." 堆栈跟踪是:
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)
似乎 JavaScript 加密值的方式与 C# 加密值的方式不兼容。在我完全放弃这种方法并尝试使用另一个 JavaScript 库进行加密之前,是否有解决此错误的方法?
对于其他上下文,我猜测此错误与本文中提到的内容有关:https://www.codeproject.com/Articles/11479/RSA-Interoperability-between-JavaScript-and-RSACry
它说:
Incompatible padding scheme from the JavaScript code would produce the
"bad data" exception at the server side.
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.
我没有得到确切的异常,但也许自从写了那篇文章以来,错误消息已经改变了。无论如何,那篇文章所说的似乎暗示 JavaScript 的加密方式与 C# 的解密方式不兼容(即,由于 C# 的填充要求)。
有什么我遗漏的吗?是否有一些参数或一些简单的方法可以使 JavaScript 中的加密工作和 C# 中的解密工作?也许有一些 C# 库以与 JavaScript 加密方式兼容的方式解密?
这是一个完整的示例,显示 JavaScript 正在正确解密(仅适用于某些浏览器...可能不适用于 IE):
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 < bytes.length; i++) {
result += String.fromCharCode(bytes[i]);
}
return result;
}
// Decodes a base64 encoded string into an ArrayBuffer.
//
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 < value.length; i++) {
bytes[i] = value.charCodeAt(i);
}
return bytes.buffer;
}
decryptValue();
顺便说一句,我的一些代码示例显示了我正在使用的私钥。这是为了帮助您理解代码(这是一把一次性钥匙)。事实上,这是我在 C# 中获取私钥的方式:
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
}
因为您将 OAEP 与 SHA-2-256 结合使用,所以您需要从 RSACryptoServiceProvider 迁移到 RSACng (.NET 4.6+)。请注意,除了 ctor 调用之外,我已经消除了正在使用哪个实现的知识。
private static RSA GetPrivateKey()
{
// build the RSAParams as before, then
RSA rsa = new RSACng();
rsa.ImportParameters(RSAparams);
return rsa;
}
// Setup encryption algorithm.
var provider = GetPrivateKey();
...
var decryptedData = provider.Decrypt(encryptedData, RSAEncryptionPadding.OaepSHA256);
我无法测试 答案,因为我无法访问 Windows 计算机,而且 Mono 显然没有实现 RSACng。下面是一个使用 Bouncycastle C# 库解密密文的示例。请注意 OaepPadding(...
) 对 Oaep 哈希和 MGF 哈希使用 SHA-256。这显然是与您的 javascript 代码进行互操作所需要的。另外,请注意我使用了 Encoding.UTF8.GetString()
而你使用了 Encoding.Unicode.GetString()
。编码绝对不是 Encoding.Unicode
给你的 UTF-16。
using System;
using System.Text;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Encodings;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
namespace RsaSha256OaepDecrypt
{
class MainClass
{
public static void Main(string[] args)
{
var encryptedValue = "CthOUMzRdtSwo+4twgtjCA674G3UosWypUZv5E7uxG7GqYPiIJ+E+Uq7vbElp/bahB1fJrgq1qbdMrUZnSypVqBwYnccSxwablO15OOXl9Rn1e7w9V9fuMxtUqvhn+YZezk1623Qd7f5XTYjf6POwixtrgfZtdA+qh00ktKiVBpQKNG/bxhV94fK9+hb+qnzPmXilr9QF5rSQTd4hYHmYcR2ljVCDDZMV3tCVUTecWjS5HbOA1254ve/q3ulBLoPQTE58g7FwDQUZnd7XBdRSwYnrBWTJh8nmJ0PDfn+mCTGEI86S7HtoFYsE+Hezd24Z523phGEVrdMC9Ob1LlXEA==";
var encryptedData = Convert.FromBase64String(encryptedValue);
var rsaPrivate = GetPrivateKey();
IAsymmetricBlockCipher cipher0 = new RsaBlindedEngine();
cipher0 = new OaepEncoding(cipher0, new Sha256Digest(), new Sha256Digest(), null);
BufferedAsymmetricBlockCipher cipher = new BufferedAsymmetricBlockCipher(cipher0);
cipher.Init(false, rsaPrivate);
cipher.ProcessBytes(encryptedData, 0, encryptedData.Length);
var decryptedData = cipher.DoFinal();
var decryptedText = Encoding.UTF8.GetString(decryptedData);
Console.WriteLine(decryptedText);
}
private static BigInteger makeBigInt(String b64Url)
{
var bytes = Base64UrlDecode(b64Url);
if ((sbyte)bytes[0] < 0)
{
// prepend a zero byte to make it positive.
var bytes1 = new byte[bytes.Length + 1];
bytes1[0] = 0;
bytes.CopyTo(bytes1, 1);
bytes = bytes1;
}
return new BigInteger(bytes);
}
private static AsymmetricKeyParameter GetPrivateKey()
{
//RSAParameters RSAparams = new RSAParameters();
var Modulus = makeBigInt("oQeTwOlTc6rIb2kddwIOc0Ywslc7YzJSRZd_PegW7T3nO3DqCI5kp5EJmnGP8JJ9sbyVYyAHFLZQtMP69UspZFn__fBk2LTp2QdqBSMHbObENcSiG2FH-pZSwCaj3Pvy-qvTjnkxxN-3OE6oB8EcX5ekZwCZzAxazbVXctY_hCcaTWG7ugwc_ZyvhsdE7wa3pnTfXYHWXcDDT8FTpYl62aqWsEIUAJSkgmQ9zce0RiDUjBJyJEM9P0ihp1Ab8BD88pEM22-PXfiOesRzp5yOsjzI3kdr5KPsshstneJEGHYae5GZXLUpnVMRY1TCFFLbkPwK6oVkRaVU1RvK9ssO3Q");
var Exponent = makeBigInt("AQAB");
var D = makeBigInt("E4KDwgxy7jFrqeXqKjxPTGOdbEoZ2aWj5qcZhUJcnr9Qh_jg_grkgpHVwEbQifTxsipXTiR3_ygspI4XFoeV-wDVfWqWCVR3_bHChF9PW8Ak1x_dBSS28BMs8PdthI1pDbpqPhmMcF4riHCtNo1M1v8cLdeaiqiXitNVBkaTePsDiucfwOy1rgxwBqAL1CNJhP8oRiYkxD-gfE_EapWuXY9-wF9O-lXPLSTKWgMmmVxSmhUP-Uqk7cJ24UH9C7W7hnSQU4pkfD5XHx3_2WO2GMKKZcqz39wJUrQzrIO7539SYsQ3rEe4aMJyL4U-Ib4_purzVS0DRjzGxK8chT2guQ");
var P = makeBigInt("2TTEToB4AuPIPPpg3yTyBlGb_m-f4r-TxpU96ConV2p696_4QI6jlPWwgcC9Vdma_Da43AGuyLzIptgkzF8nSjV80VwwDKQ1YkFPc6ZqB2isvExuieSP6_jLlB-fCyCLqtTxpPm2VcK16Pqm0s5T0QGH6cQjjm1r2Ww1wuaiQbk");
var Q = makeBigInt("vcpFwkZKZ3hx3FpHgy3ScuuTRSPO2ge8TE8UMJdCrEnpftAeYuVYrJqnxfzKgyl02OijAUi1eozJxj_lM5McxrKZEEAvo6e8wtzl2hnkUh-KWoBJ8ii0VJcu6U5vs4pcv-lYBPFC6fzoGnUw8LNWMxb5ejgYbLUWp10BbfkWGEU");
var DP = makeBigInt("kibhWHk1R6yBlhZbjIrNl9beAkyV5vtFsj_F0ixbIITzjSqI_td71sWjKQvJ2rR7hu5DYTZ4p3XwBeQ2jpYQV-y5uh4v7rGngh-0GHuHqMiUQnejgYGcHgng4iCM4e3aTO7QUlP8jqRfxw6xpfNTjrVbAL8LtdCG21vmqOiLkXE");
var DQ = makeBigInt("qLF9x-zKfaXlLsNgBQ1ZnaQexrnJRqrRh9JSU85fCNy5mmpKWAUbCHB-59CGAId8wMAnAyEpjcBOKNTqWSlNzp84xeUHcyPI-Dt4Yp_Y_dXjGAYntALSJs4qeF2rk55MSpiSD_KSU4DknX_E_G2rFMY7AZOSwi1D8YcNmj5okTE");
var InverseQ = makeBigInt("Mza7JYleki7BvmD3dX5CO2nkD3mBGz4_0P_aoWyHEkWu4p5XWillaRVWyLnQEubLvAduUCr-lhfNmzdUhHecpE438_LQNtKRyOq9zkvjsMOGDmbkKpZ7-aTSshax6KNlYOWdOkadjuLtRExCmwbzu5lgI4NwacxSs5MfjHMrTCo");
var rsa = new RsaPrivateCrtKeyParameters(Modulus, Exponent, D, P, Q, DP, DQ, InverseQ);
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
}
}
}
在我深入了解细节之前,我要完成的高级任务是在 JavaScript 中加密一些数据,将其发送到 Web 服务器,然后在 C# 中解密该加密数据。我遇到问题的部分是用 C# 解密数据。
我正在像这样加密 JavaScript 中的一些数据(我删除了无关代码):
// https://github.com/diafygi/webcrypto-examples#rsa-oaep---encrypt
window.crypto.subtle.encrypt(
{
name: "RSA-OAEP"
},
publicKey,
data
)
.then(function (encrypted) {
// ...
});
我确认可以在JavaScript中像这样解密(注意我其实并不想这样做,但我这样做是为了证明数据可以被解密):
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));
});
});
}
为简单起见,该代码示例正在解密先前加密的 "Bob" 值。这很好用。
在C#中尝试解密值时出现问题:
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;
}
表示 provider.Decrypt(encryptedData, doOaep)
的行抛出错误消息 "Error occurred while decoding OAEP padding." 堆栈跟踪是:
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)
似乎 JavaScript 加密值的方式与 C# 加密值的方式不兼容。在我完全放弃这种方法并尝试使用另一个 JavaScript 库进行加密之前,是否有解决此错误的方法?
对于其他上下文,我猜测此错误与本文中提到的内容有关:https://www.codeproject.com/Articles/11479/RSA-Interoperability-between-JavaScript-and-RSACry
它说:
Incompatible padding scheme from the JavaScript code would produce the "bad data" exception at the server side.
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.
我没有得到确切的异常,但也许自从写了那篇文章以来,错误消息已经改变了。无论如何,那篇文章所说的似乎暗示 JavaScript 的加密方式与 C# 的解密方式不兼容(即,由于 C# 的填充要求)。
有什么我遗漏的吗?是否有一些参数或一些简单的方法可以使 JavaScript 中的加密工作和 C# 中的解密工作?也许有一些 C# 库以与 JavaScript 加密方式兼容的方式解密?
这是一个完整的示例,显示 JavaScript 正在正确解密(仅适用于某些浏览器...可能不适用于 IE):
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 < bytes.length; i++) {
result += String.fromCharCode(bytes[i]);
}
return result;
}
// Decodes a base64 encoded string into an ArrayBuffer.
//
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 < value.length; i++) {
bytes[i] = value.charCodeAt(i);
}
return bytes.buffer;
}
decryptValue();
顺便说一句,我的一些代码示例显示了我正在使用的私钥。这是为了帮助您理解代码(这是一把一次性钥匙)。事实上,这是我在 C# 中获取私钥的方式:
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
}
因为您将 OAEP 与 SHA-2-256 结合使用,所以您需要从 RSACryptoServiceProvider 迁移到 RSACng (.NET 4.6+)。请注意,除了 ctor 调用之外,我已经消除了正在使用哪个实现的知识。
private static RSA GetPrivateKey()
{
// build the RSAParams as before, then
RSA rsa = new RSACng();
rsa.ImportParameters(RSAparams);
return rsa;
}
// Setup encryption algorithm.
var provider = GetPrivateKey();
...
var decryptedData = provider.Decrypt(encryptedData, RSAEncryptionPadding.OaepSHA256);
我无法测试 OaepPadding(...
) 对 Oaep 哈希和 MGF 哈希使用 SHA-256。这显然是与您的 javascript 代码进行互操作所需要的。另外,请注意我使用了 Encoding.UTF8.GetString()
而你使用了 Encoding.Unicode.GetString()
。编码绝对不是 Encoding.Unicode
给你的 UTF-16。
using System;
using System.Text;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Encodings;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
namespace RsaSha256OaepDecrypt
{
class MainClass
{
public static void Main(string[] args)
{
var encryptedValue = "CthOUMzRdtSwo+4twgtjCA674G3UosWypUZv5E7uxG7GqYPiIJ+E+Uq7vbElp/bahB1fJrgq1qbdMrUZnSypVqBwYnccSxwablO15OOXl9Rn1e7w9V9fuMxtUqvhn+YZezk1623Qd7f5XTYjf6POwixtrgfZtdA+qh00ktKiVBpQKNG/bxhV94fK9+hb+qnzPmXilr9QF5rSQTd4hYHmYcR2ljVCDDZMV3tCVUTecWjS5HbOA1254ve/q3ulBLoPQTE58g7FwDQUZnd7XBdRSwYnrBWTJh8nmJ0PDfn+mCTGEI86S7HtoFYsE+Hezd24Z523phGEVrdMC9Ob1LlXEA==";
var encryptedData = Convert.FromBase64String(encryptedValue);
var rsaPrivate = GetPrivateKey();
IAsymmetricBlockCipher cipher0 = new RsaBlindedEngine();
cipher0 = new OaepEncoding(cipher0, new Sha256Digest(), new Sha256Digest(), null);
BufferedAsymmetricBlockCipher cipher = new BufferedAsymmetricBlockCipher(cipher0);
cipher.Init(false, rsaPrivate);
cipher.ProcessBytes(encryptedData, 0, encryptedData.Length);
var decryptedData = cipher.DoFinal();
var decryptedText = Encoding.UTF8.GetString(decryptedData);
Console.WriteLine(decryptedText);
}
private static BigInteger makeBigInt(String b64Url)
{
var bytes = Base64UrlDecode(b64Url);
if ((sbyte)bytes[0] < 0)
{
// prepend a zero byte to make it positive.
var bytes1 = new byte[bytes.Length + 1];
bytes1[0] = 0;
bytes.CopyTo(bytes1, 1);
bytes = bytes1;
}
return new BigInteger(bytes);
}
private static AsymmetricKeyParameter GetPrivateKey()
{
//RSAParameters RSAparams = new RSAParameters();
var Modulus = makeBigInt("oQeTwOlTc6rIb2kddwIOc0Ywslc7YzJSRZd_PegW7T3nO3DqCI5kp5EJmnGP8JJ9sbyVYyAHFLZQtMP69UspZFn__fBk2LTp2QdqBSMHbObENcSiG2FH-pZSwCaj3Pvy-qvTjnkxxN-3OE6oB8EcX5ekZwCZzAxazbVXctY_hCcaTWG7ugwc_ZyvhsdE7wa3pnTfXYHWXcDDT8FTpYl62aqWsEIUAJSkgmQ9zce0RiDUjBJyJEM9P0ihp1Ab8BD88pEM22-PXfiOesRzp5yOsjzI3kdr5KPsshstneJEGHYae5GZXLUpnVMRY1TCFFLbkPwK6oVkRaVU1RvK9ssO3Q");
var Exponent = makeBigInt("AQAB");
var D = makeBigInt("E4KDwgxy7jFrqeXqKjxPTGOdbEoZ2aWj5qcZhUJcnr9Qh_jg_grkgpHVwEbQifTxsipXTiR3_ygspI4XFoeV-wDVfWqWCVR3_bHChF9PW8Ak1x_dBSS28BMs8PdthI1pDbpqPhmMcF4riHCtNo1M1v8cLdeaiqiXitNVBkaTePsDiucfwOy1rgxwBqAL1CNJhP8oRiYkxD-gfE_EapWuXY9-wF9O-lXPLSTKWgMmmVxSmhUP-Uqk7cJ24UH9C7W7hnSQU4pkfD5XHx3_2WO2GMKKZcqz39wJUrQzrIO7539SYsQ3rEe4aMJyL4U-Ib4_purzVS0DRjzGxK8chT2guQ");
var P = makeBigInt("2TTEToB4AuPIPPpg3yTyBlGb_m-f4r-TxpU96ConV2p696_4QI6jlPWwgcC9Vdma_Da43AGuyLzIptgkzF8nSjV80VwwDKQ1YkFPc6ZqB2isvExuieSP6_jLlB-fCyCLqtTxpPm2VcK16Pqm0s5T0QGH6cQjjm1r2Ww1wuaiQbk");
var Q = makeBigInt("vcpFwkZKZ3hx3FpHgy3ScuuTRSPO2ge8TE8UMJdCrEnpftAeYuVYrJqnxfzKgyl02OijAUi1eozJxj_lM5McxrKZEEAvo6e8wtzl2hnkUh-KWoBJ8ii0VJcu6U5vs4pcv-lYBPFC6fzoGnUw8LNWMxb5ejgYbLUWp10BbfkWGEU");
var DP = makeBigInt("kibhWHk1R6yBlhZbjIrNl9beAkyV5vtFsj_F0ixbIITzjSqI_td71sWjKQvJ2rR7hu5DYTZ4p3XwBeQ2jpYQV-y5uh4v7rGngh-0GHuHqMiUQnejgYGcHgng4iCM4e3aTO7QUlP8jqRfxw6xpfNTjrVbAL8LtdCG21vmqOiLkXE");
var DQ = makeBigInt("qLF9x-zKfaXlLsNgBQ1ZnaQexrnJRqrRh9JSU85fCNy5mmpKWAUbCHB-59CGAId8wMAnAyEpjcBOKNTqWSlNzp84xeUHcyPI-Dt4Yp_Y_dXjGAYntALSJs4qeF2rk55MSpiSD_KSU4DknX_E_G2rFMY7AZOSwi1D8YcNmj5okTE");
var InverseQ = makeBigInt("Mza7JYleki7BvmD3dX5CO2nkD3mBGz4_0P_aoWyHEkWu4p5XWillaRVWyLnQEubLvAduUCr-lhfNmzdUhHecpE438_LQNtKRyOq9zkvjsMOGDmbkKpZ7-aTSshax6KNlYOWdOkadjuLtRExCmwbzu5lgI4NwacxSs5MfjHMrTCo");
var rsa = new RsaPrivateCrtKeyParameters(Modulus, Exponent, D, P, Q, DP, DQ, InverseQ);
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
}
}
}