RSA 解密异常:要解密的数据长度对于此密钥的大小无效
RSA Decryption exception: The length of the data to decrypt is not valid for the size of this key
我有一个带有 (RSA + AES) 加密连接的 Angular + Net Core 应用程序。
来自客户端的所有请求都来自 POST。 (下面会给你一个例子。
下面提供的脚本运行良好,但在 5% 的情况下会抛出异常:
The length of the data to decrypt is not valid for the size of this key
in the line:
var decryptedAesKey = Encoding.UTF8.GetString(rsaCng.Decrypt(Convert.FromBase64String(request.k), RSAEncryptionPadding.Pkcs1));
加密部分(前端)
encrypt(requestObj:any):any {
var rsaEncrypt = new JsEncryptModule.JSEncrypt();
var key = this.generateAesKey(32); //secret key
var iv = this.generateAesKey(16); //16 digit
var stringifiedRequest = CryptoJS.enc.Utf8.parse(JSON.stringify(requestObj));
var aesEncryptedRequest = CryptoJS.AES.encrypt(stringifiedRequest,
CryptoJS.enc.Utf8.parse(key),
{
keySize: 128 / 8,
iv: CryptoJS.enc.Utf8.parse(iv),
padding: CryptoJS.pad.Pkcs7,
mode: CryptoJS.mode.CBC
});
rsaEncrypt.setPrivateKey(this.publicPemKey);
var encryptedKey = rsaEncrypt.encrypt(key);
var encryptedIV = rsaEncrypt.encrypt(iv);
var encryptedRequestObj = {
k: encryptedKey,
v: encryptedIV,
r: aesEncryptedRequest.toString()
};
return encryptedRequestObj;
}
解密部分(C#后端)
var decryptedAesKey = Encoding.UTF8.GetString(rsaCng.Decrypt(Convert.FromBase64String(request.k),
RSAEncryptionPadding.Pkcs1));
var decryptedAesIV = Encoding.UTF8.GetString(rsaCng.Decrypt(Convert.FromBase64String(request.v), RSAEncryptionPadding.Pkcs1));
byte[] encryptedBytes = request.r;
AesCryptoServiceProvider aes = new AesCryptoServiceProvider()
{
Mode = CipherMode.CBC,
Padding = PaddingMode.PKCS7,
Key = Encoding.UTF8.GetBytes(decryptedAesKey),
IV = Encoding.UTF8.GetBytes(decryptedAesIV)
};
ICryptoTransform crypto = aes.CreateDecryptor(aes.Key, aes.IV);
byte[] secret = crypto.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
crypto.Dispose();
requestJson = Encoding.UTF8.GetString(secret);
示例,用户想通过id打开一个页面。
前端:
1) 使用 AES
加密请求 ID
2) 使用 RSA
加密 AES 的密钥和 iv
3) 发送到后端
后端:
1) 使用 RSA 解密 AES 的密钥和值 <--- BREAKS HERE
2) 使用 AES' key & iv
解密请求 ID
3) 像没有加密一样解密并获取 id
这个逻辑工作得很好,但有时会中断......
请求失败示例:
{ "k":"L+ikMb/JGvFJmhBpADMGTVLFlkHOe69dZUVSQ5r7yHCvWSwY2x6KMR274ByflF0lDMYdCmywo+Nfq6JUybRctDqmAp8UFHXnhwBAv49d99mF5x2yGbJr/j0cn6EZyhweNK4p97i5yMM6MQtluZTIErpsUa22Cajtj8F+xl0jJPUMXIf8cs2X+ooFr5VP/p/vlbPmnEY3K/hMCRZRdXMkEqaCWoA5EnYMTQABtRXPZWgLSQwJpr4dqEAhGCBtga1AGsKF3dQCsKO92NYyst0ngkBiKwFNfy1QDwbk4SzKAKeBckaY17SHt526NMvpEv08BGV6btBxcM+ypsmpB4o0",
"v":"LIndJOjUgKHDlXqwpg7uSmDuut3oi5z9L/GKm2KgU7P2EXmf/JIpXM0JgpTXPJL7wUTndq3F9UMlMdU70JBOV56x/4uIBRbHbyvaG2JZYxbBZblwyYgdo1ZcK1OSE4k5oesQmMEGNEk9RVu+EZO4xAme6+mlyd2/Y/709jaC90PuiOG/k/4JMTTI/2q4s7tk6IgSxLBT8ZiOtgJVGdasSaAksEBMRHyUkzAIr5tSUw1VXedwJFPfwQT2nOD5dU2cxiNJKOwtO9uAYXly0U0FDoa/nkWskca8zaU+4EiPikJ6Km7phViH9JvwZFgHhBj+8FM6Jof+AdrY3q1dcMLFlg==",
"r":"OJnA3wFoKKG+iu4FciXyJg=="
}
正确请求的示例:
{ "k":"uW8d7vIzlgkEkKTkDnHbBZeqKwdgoG+1BVZ/NUiC0pZ/LqZM9aUasQSx+qDg+X50ur30uRnEyAyIZXruYeHQb8cacx5mvr9LWLud+wueJXsOlEEdocD/4A1DfE9TDFdnTaVcMSIwhSVlLPUjO7ubJdANY9yK4S+vb0IyPbsrYpAT7ho01mDkvsH1rZsId/TmzQadmsGhThowu+mrQlz78rrdlN8nI5LnUQHXRNWMUgBvuteTpVBmyrfnIELIKoo/jI6Nj4rGPQBf7+2OOoZPs0Y1GtjXxUCTAt7madNLKSOdaPjdWjaOfGSwnymDNeEFyJQOmAwHZoOGYNd2B/UhQQ==",
"v":"IimiJFcKv5ZHWHljJixX0LUgV4I2GWAWPbk7dWHVhwmHEhTHA/hCdih/E1wiWFS+0KaL05ZobiZInyK7gCwYPHaz0aRCSQtVeBPiFg4f7L0gwfvk1GHwJ1wZjqNJZaYf0elXJzc2l5BwN+aXNWaNJDPA7M6kfK6UPkq84IV3ohCQcTuC8zPM7aMJHxpz9IudcrMmYIkeqrj9Do88CkTLv8yg5hk3EASPk9HqsUieuQixggv/8ZlHnp00iftc62LJlIuCkGn4WR3FkMdFdqpKXf6Ebj8PU1HOmokEtKtYJiOZ5JxieZO5Pnd+ez6sO7khIbdRFDhAQ20chsxKUypezw==",
"r":"2mbUgU44JFFDlWu8As2RIw=="
}
在请求失败的情况下,Base64解码后的加密AES密钥长度为255字节。对于一个 2048 位的 RSA 密钥,它实际上应该是 256 个字节,对于剩余的数据也是如此。
对于 RSA 加密,使用了 JSEncrypt,它有一个已知的错误,偶尔会导致密文太短,这可能是导致您的问题的原因,请参阅 here .此错误于 2019 年 7 月打开,目前尚未修复。
在JSEncrypt中,太短的密文被正确处理,因此不会发生错误。然而跨平台,通常情况并非如此,因为太短的密文严格来说是无效的,因此一些编程语言将它们识别为无效,例如,显然 C# 是另一个。
如果太短的密文是手动从左边补到模数的长度0x00
,密文在C#代码中应该也是可以解密的
更新:
我已经成功使用您的代码测试了建议的修复。密文可以固定在JavaScript或者C#代码中。 JavaScript 端的可能实现是例如对于密钥:
encryptedKey = btoa(atob(encryptedKey).padStart(256, "[=10=]"));
其中 encryptedKey
是 JSEncrypt#encrypt
返回的 Base64 编码密文。为确保此更正不应用于已经具有正确长度的密文,长度检查很有用:长度为 4 * Math.ceil(256 / 3)
的 Base64 编码密文不需要修复,因为它对应于正确长度为 256 字节,参见 here.
在设置 public 密钥进行加密时,在 JSEncrypt 部分应用方法 setPrivateKey
,正确的是 setPublicKey
,参见 here。然而,JSEncrypt 似乎在内部修复了这个问题,因为它也能正常工作。然而,它应该被改变,因为它具有误导性。
正如@kelalaka 的评论中已经提到的,IV 不是秘密,不需要加密。
我有一个带有 (RSA + AES) 加密连接的 Angular + Net Core 应用程序。 来自客户端的所有请求都来自 POST。 (下面会给你一个例子。
下面提供的脚本运行良好,但在 5% 的情况下会抛出异常:
The length of the data to decrypt is not valid for the size of this key in the line:
var decryptedAesKey = Encoding.UTF8.GetString(rsaCng.Decrypt(Convert.FromBase64String(request.k), RSAEncryptionPadding.Pkcs1));
加密部分(前端)
encrypt(requestObj:any):any {
var rsaEncrypt = new JsEncryptModule.JSEncrypt();
var key = this.generateAesKey(32); //secret key
var iv = this.generateAesKey(16); //16 digit
var stringifiedRequest = CryptoJS.enc.Utf8.parse(JSON.stringify(requestObj));
var aesEncryptedRequest = CryptoJS.AES.encrypt(stringifiedRequest,
CryptoJS.enc.Utf8.parse(key),
{
keySize: 128 / 8,
iv: CryptoJS.enc.Utf8.parse(iv),
padding: CryptoJS.pad.Pkcs7,
mode: CryptoJS.mode.CBC
});
rsaEncrypt.setPrivateKey(this.publicPemKey);
var encryptedKey = rsaEncrypt.encrypt(key);
var encryptedIV = rsaEncrypt.encrypt(iv);
var encryptedRequestObj = {
k: encryptedKey,
v: encryptedIV,
r: aesEncryptedRequest.toString()
};
return encryptedRequestObj;
}
解密部分(C#后端)
var decryptedAesKey = Encoding.UTF8.GetString(rsaCng.Decrypt(Convert.FromBase64String(request.k),
RSAEncryptionPadding.Pkcs1));
var decryptedAesIV = Encoding.UTF8.GetString(rsaCng.Decrypt(Convert.FromBase64String(request.v), RSAEncryptionPadding.Pkcs1));
byte[] encryptedBytes = request.r;
AesCryptoServiceProvider aes = new AesCryptoServiceProvider()
{
Mode = CipherMode.CBC,
Padding = PaddingMode.PKCS7,
Key = Encoding.UTF8.GetBytes(decryptedAesKey),
IV = Encoding.UTF8.GetBytes(decryptedAesIV)
};
ICryptoTransform crypto = aes.CreateDecryptor(aes.Key, aes.IV);
byte[] secret = crypto.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
crypto.Dispose();
requestJson = Encoding.UTF8.GetString(secret);
示例,用户想通过id打开一个页面。
前端:
1) 使用 AES
加密请求 ID
2) 使用 RSA
加密 AES 的密钥和 iv
3) 发送到后端
后端:
1) 使用 RSA 解密 AES 的密钥和值 <--- BREAKS HERE
2) 使用 AES' key & iv
解密请求 ID
3) 像没有加密一样解密并获取 id
这个逻辑工作得很好,但有时会中断......
请求失败示例:
{ "k":"L+ikMb/JGvFJmhBpADMGTVLFlkHOe69dZUVSQ5r7yHCvWSwY2x6KMR274ByflF0lDMYdCmywo+Nfq6JUybRctDqmAp8UFHXnhwBAv49d99mF5x2yGbJr/j0cn6EZyhweNK4p97i5yMM6MQtluZTIErpsUa22Cajtj8F+xl0jJPUMXIf8cs2X+ooFr5VP/p/vlbPmnEY3K/hMCRZRdXMkEqaCWoA5EnYMTQABtRXPZWgLSQwJpr4dqEAhGCBtga1AGsKF3dQCsKO92NYyst0ngkBiKwFNfy1QDwbk4SzKAKeBckaY17SHt526NMvpEv08BGV6btBxcM+ypsmpB4o0",
"v":"LIndJOjUgKHDlXqwpg7uSmDuut3oi5z9L/GKm2KgU7P2EXmf/JIpXM0JgpTXPJL7wUTndq3F9UMlMdU70JBOV56x/4uIBRbHbyvaG2JZYxbBZblwyYgdo1ZcK1OSE4k5oesQmMEGNEk9RVu+EZO4xAme6+mlyd2/Y/709jaC90PuiOG/k/4JMTTI/2q4s7tk6IgSxLBT8ZiOtgJVGdasSaAksEBMRHyUkzAIr5tSUw1VXedwJFPfwQT2nOD5dU2cxiNJKOwtO9uAYXly0U0FDoa/nkWskca8zaU+4EiPikJ6Km7phViH9JvwZFgHhBj+8FM6Jof+AdrY3q1dcMLFlg==",
"r":"OJnA3wFoKKG+iu4FciXyJg=="
}
正确请求的示例:
{ "k":"uW8d7vIzlgkEkKTkDnHbBZeqKwdgoG+1BVZ/NUiC0pZ/LqZM9aUasQSx+qDg+X50ur30uRnEyAyIZXruYeHQb8cacx5mvr9LWLud+wueJXsOlEEdocD/4A1DfE9TDFdnTaVcMSIwhSVlLPUjO7ubJdANY9yK4S+vb0IyPbsrYpAT7ho01mDkvsH1rZsId/TmzQadmsGhThowu+mrQlz78rrdlN8nI5LnUQHXRNWMUgBvuteTpVBmyrfnIELIKoo/jI6Nj4rGPQBf7+2OOoZPs0Y1GtjXxUCTAt7madNLKSOdaPjdWjaOfGSwnymDNeEFyJQOmAwHZoOGYNd2B/UhQQ==",
"v":"IimiJFcKv5ZHWHljJixX0LUgV4I2GWAWPbk7dWHVhwmHEhTHA/hCdih/E1wiWFS+0KaL05ZobiZInyK7gCwYPHaz0aRCSQtVeBPiFg4f7L0gwfvk1GHwJ1wZjqNJZaYf0elXJzc2l5BwN+aXNWaNJDPA7M6kfK6UPkq84IV3ohCQcTuC8zPM7aMJHxpz9IudcrMmYIkeqrj9Do88CkTLv8yg5hk3EASPk9HqsUieuQixggv/8ZlHnp00iftc62LJlIuCkGn4WR3FkMdFdqpKXf6Ebj8PU1HOmokEtKtYJiOZ5JxieZO5Pnd+ez6sO7khIbdRFDhAQ20chsxKUypezw==",
"r":"2mbUgU44JFFDlWu8As2RIw=="
}
在请求失败的情况下,Base64解码后的加密AES密钥长度为255字节。对于一个 2048 位的 RSA 密钥,它实际上应该是 256 个字节,对于剩余的数据也是如此。
对于 RSA 加密,使用了 JSEncrypt,它有一个已知的错误,偶尔会导致密文太短,这可能是导致您的问题的原因,请参阅 here .此错误于 2019 年 7 月打开,目前尚未修复。
在JSEncrypt中,太短的密文被正确处理,因此不会发生错误。然而跨平台,通常情况并非如此,因为太短的密文严格来说是无效的,因此一些编程语言将它们识别为无效,例如
如果太短的密文是手动从左边补到模数的长度0x00
,密文在C#代码中应该也是可以解密的
更新:
我已经成功使用您的代码测试了建议的修复。密文可以固定在JavaScript或者C#代码中。 JavaScript 端的可能实现是例如对于密钥:
encryptedKey = btoa(atob(encryptedKey).padStart(256, "[=10=]"));
其中
encryptedKey
是JSEncrypt#encrypt
返回的 Base64 编码密文。为确保此更正不应用于已经具有正确长度的密文,长度检查很有用:长度为4 * Math.ceil(256 / 3)
的 Base64 编码密文不需要修复,因为它对应于正确长度为 256 字节,参见 here.在设置 public 密钥进行加密时,在 JSEncrypt 部分应用方法
setPrivateKey
,正确的是setPublicKey
,参见 here。然而,JSEncrypt 似乎在内部修复了这个问题,因为它也能正常工作。然而,它应该被改变,因为它具有误导性。正如@kelalaka 的评论中已经提到的,IV 不是秘密,不需要加密。