在 C# 中通过 JS 发布解密 RSA public-key 加密数据
Issue decrypting RSA public-key encrypted data by JS in C#
在 C# 中解密 RSA 加密字符串时出现错误 The data to be decrypted exceeds the maximum for this modulus of 256 bytes
。
我想要实现的目标:
- 在 C# (RSA) 中生成 public/private 密钥对
- 将私钥保存在 session/temp 存储中以供解密时使用
- Send/return public client/JS 的密钥用于加密
- 使用 public 密钥在 JS 中加密字符串(最多 20 个字符)并发送到服务器
- 使用步骤 2 中保存的私钥解密服务器中的加密字符串
什么有效:
- 生成public/private个密钥对
- 使用库jsencrypt
在JS中加密
到目前为止编写的代码:
C#
///Source:
public static void GenerateKeys()
{
//CSP with a new 2048 bit rsa key pair
var csp = new RSACryptoServiceProvider(2048);
//Private key
var privKey = csp.ExportParameters(true);
//Public key
var pubKey = csp.ExportParameters(false);
string privKeyString = string.Empty;
var sw = new System.IO.StringWriter();
var xs = new System.Xml.Serialization.XmlSerializer(typeof(RSAParameters));
xs.Serialize(sw, privKey);
privKeyString = sw.ToString();
//This will give public key in the following format which is required by the JS library
//-----BEGIN PUBLIC KEY-----
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
//-----END PUBLIC KEY-----
string publicKeyBase64 = ConvertPublicKeyForJS(pubKey);
//Will be saved in sesssion for later use during decryption
string privateKeyBase64 = Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(privKeyString));
//Save in session/temp location
//Return publicKeyBase64 to JS
}
/// <summary>
/// Source:
/// </summary>
/// <param name="publicKey"></param>
/// <returns></returns>
public static string ConvertPublicKeyForJS(RSAParameters publicKey)
{
string output = string.Empty;
using (var stream = new MemoryStream())
{
var writer = new BinaryWriter(stream);
writer.Write((byte)0x30); // SEQUENCE
using (var innerStream = new MemoryStream())
{
var innerWriter = new BinaryWriter(innerStream);
innerWriter.Write((byte)0x30); // SEQUENCE
EncodeLength(innerWriter, 13);
innerWriter.Write((byte)0x06); // OBJECT IDENTIFIER
var rsaEncryptionOid = new byte[] { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01 };
EncodeLength(innerWriter, rsaEncryptionOid.Length);
innerWriter.Write(rsaEncryptionOid);
innerWriter.Write((byte)0x05); // NULL
EncodeLength(innerWriter, 0);
innerWriter.Write((byte)0x03); // BIT STRING
using (var bitStringStream = new MemoryStream())
{
var bitStringWriter = new BinaryWriter(bitStringStream);
bitStringWriter.Write((byte)0x00); // # of unused bits
bitStringWriter.Write((byte)0x30); // SEQUENCE
using (var paramsStream = new MemoryStream())
{
var paramsWriter = new BinaryWriter(paramsStream);
EncodeIntegerBigEndian(paramsWriter, publicKey.Modulus); // Modulus
EncodeIntegerBigEndian(paramsWriter, publicKey.Exponent); // Exponent
var paramsLength = (int)paramsStream.Length;
EncodeLength(bitStringWriter, paramsLength);
bitStringWriter.Write(paramsStream.GetBuffer(), 0, paramsLength);
}
var bitStringLength = (int)bitStringStream.Length;
EncodeLength(innerWriter, bitStringLength);
innerWriter.Write(bitStringStream.GetBuffer(), 0, bitStringLength);
}
var length = (int)innerStream.Length;
EncodeLength(writer, length);
writer.Write(innerStream.GetBuffer(), 0, length);
}
var base64 = Convert.ToBase64String(stream.GetBuffer(), 0, (int)stream.Length);
StringBuilder sb = new StringBuilder();
sb.AppendLine("-----BEGIN PUBLIC KEY-----");
sb.AppendLine(base64);
sb.AppendLine("-----END PUBLIC KEY-----");
output = sb.ToString();
}
return output;
}
private static void EncodeLength(BinaryWriter stream, int length)
{
if (length < 0) throw new ArgumentOutOfRangeException("length", "Length must be non-negative");
if (length < 0x80)
{
// Short form
stream.Write((byte)length);
}
else
{
// Long form
var temp = length;
var bytesRequired = 0;
while (temp > 0)
{
temp >>= 8;
bytesRequired++;
}
stream.Write((byte)(bytesRequired | 0x80));
for (var i = bytesRequired - 1; i >= 0; i--)
{
stream.Write((byte)(length >> (8 * i) & 0xff));
}
}
}
private static void EncodeIntegerBigEndian(BinaryWriter stream, byte[] value, bool forceUnsigned = true)
{
stream.Write((byte)0x02); // INTEGER
var prefixZeros = 0;
for (var i = 0; i < value.Length; i++)
{
if (value[i] != 0) break;
prefixZeros++;
}
if (value.Length - prefixZeros == 0)
{
EncodeLength(stream, 1);
stream.Write((byte)0);
}
else
{
if (forceUnsigned && value[prefixZeros] > 0x7f)
{
// Add a prefix zero to force unsigned if the MSB is 1
EncodeLength(stream, value.Length - prefixZeros + 1);
stream.Write((byte)0);
}
else
{
EncodeLength(stream, value.Length - prefixZeros);
}
for (var i = prefixZeros; i < value.Length; i++)
{
stream.Write(value[i]);
}
}
}
public static string DecryptValue(string cypherText)
{
string plainTextData = string.Empty;
string privKeyBase64 = ""; //get value from session/temp storage
string privKeyString = System.Text.Encoding.ASCII.GetString(Convert.FromBase64String(privKeyBase64));
var sr = new System.IO.StringReader(privKeyString);
var xs = new System.Xml.Serialization.XmlSerializer(typeof(RSAParameters));
var privKey = (RSAParameters)xs.Deserialize(sr);
var csp = new RSACryptoServiceProvider();
csp.ImportParameters(privKey);
var bytesCypherText = Convert.FromBase64String(cypherText);
//Problematic line
var bytesPlainTextData = csp.Decrypt(bytesCypherText, false);
plainTextData = System.Text.Encoding.Unicode.GetString(bytesPlainTextData);
return plainTextData;
}
JS
function encrypt(msg, publicKey) {
var jsEncrypt = new JSEncrypt();
jsEncrypt.setPublicKey(publicKey);
var encrypted = jsEncrypt.encrypt(msg);
var base64result = btoa(encrypted);
return base64result;
}
Update/Variation:
如果我将密钥大小设置为 1024 而不是 2048,我得到的错误是 The data to be decrypted exceeds the maximum for this modulus of 128 bytes.
。根据我在调试器中看到的内容,加密字符串的总字节数为 172。
更新 |解决方案:
- 删除 JS 中的这一行
var base64result = btoa(encrypted);
。直接return加密后的值即可。
- 在 C# 解密方法中,将此行
var bytesPlainTextData = csp.Decrypt(bytesCypherText, false);
更改为 var bytesPlainTextData = csp.Decrypt(bytesCypherText, RSAEncryptionPadding.Pkcs1);
。
备注:
- 请忽略不正确的方法 return 类型,这仍然是一个 POC,很多值正在手动填充。
- 我不是安全方面的专家,所以很多代码都是从不同来源挑选出来的。
您面临非对称加密的限制。对于大块数据来说速度非常慢,并且加密字符串的大小受您使用的 RSA 密钥大小的限制。
RSA 通常用于交换对称密钥和处理大部分数据。如果您必须对大量数据使用非对称负载,那么您需要将有效负载分解为较小的负载并在另一侧进行重建。
RSA is only able to encrypt data to a maximum amount of your key size (2048 bits = 256 bytes) minus padding / header data (11 bytes for PKCS#1 v1.5 padding).
如果如您所说,您只发送 20 个字符,那么请确实使用断点检查您的解密函数是否确实获得了足够小的密文。如果不是,您需要回溯并检查您发送错误内容的位置。
也有可能是JSEncrypt中RSA的标准和C#中的RSA不一样可以看出
在 C# 中解密 RSA 加密字符串时出现错误 The data to be decrypted exceeds the maximum for this modulus of 256 bytes
。
我想要实现的目标:
- 在 C# (RSA) 中生成 public/private 密钥对
- 将私钥保存在 session/temp 存储中以供解密时使用
- Send/return public client/JS 的密钥用于加密
- 使用 public 密钥在 JS 中加密字符串(最多 20 个字符)并发送到服务器
- 使用步骤 2 中保存的私钥解密服务器中的加密字符串
什么有效:
- 生成public/private个密钥对
- 使用库jsencrypt 在JS中加密
到目前为止编写的代码:
C#
///Source:
public static void GenerateKeys()
{
//CSP with a new 2048 bit rsa key pair
var csp = new RSACryptoServiceProvider(2048);
//Private key
var privKey = csp.ExportParameters(true);
//Public key
var pubKey = csp.ExportParameters(false);
string privKeyString = string.Empty;
var sw = new System.IO.StringWriter();
var xs = new System.Xml.Serialization.XmlSerializer(typeof(RSAParameters));
xs.Serialize(sw, privKey);
privKeyString = sw.ToString();
//This will give public key in the following format which is required by the JS library
//-----BEGIN PUBLIC KEY-----
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
//-----END PUBLIC KEY-----
string publicKeyBase64 = ConvertPublicKeyForJS(pubKey);
//Will be saved in sesssion for later use during decryption
string privateKeyBase64 = Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(privKeyString));
//Save in session/temp location
//Return publicKeyBase64 to JS
}
/// <summary>
/// Source:
/// </summary>
/// <param name="publicKey"></param>
/// <returns></returns>
public static string ConvertPublicKeyForJS(RSAParameters publicKey)
{
string output = string.Empty;
using (var stream = new MemoryStream())
{
var writer = new BinaryWriter(stream);
writer.Write((byte)0x30); // SEQUENCE
using (var innerStream = new MemoryStream())
{
var innerWriter = new BinaryWriter(innerStream);
innerWriter.Write((byte)0x30); // SEQUENCE
EncodeLength(innerWriter, 13);
innerWriter.Write((byte)0x06); // OBJECT IDENTIFIER
var rsaEncryptionOid = new byte[] { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01 };
EncodeLength(innerWriter, rsaEncryptionOid.Length);
innerWriter.Write(rsaEncryptionOid);
innerWriter.Write((byte)0x05); // NULL
EncodeLength(innerWriter, 0);
innerWriter.Write((byte)0x03); // BIT STRING
using (var bitStringStream = new MemoryStream())
{
var bitStringWriter = new BinaryWriter(bitStringStream);
bitStringWriter.Write((byte)0x00); // # of unused bits
bitStringWriter.Write((byte)0x30); // SEQUENCE
using (var paramsStream = new MemoryStream())
{
var paramsWriter = new BinaryWriter(paramsStream);
EncodeIntegerBigEndian(paramsWriter, publicKey.Modulus); // Modulus
EncodeIntegerBigEndian(paramsWriter, publicKey.Exponent); // Exponent
var paramsLength = (int)paramsStream.Length;
EncodeLength(bitStringWriter, paramsLength);
bitStringWriter.Write(paramsStream.GetBuffer(), 0, paramsLength);
}
var bitStringLength = (int)bitStringStream.Length;
EncodeLength(innerWriter, bitStringLength);
innerWriter.Write(bitStringStream.GetBuffer(), 0, bitStringLength);
}
var length = (int)innerStream.Length;
EncodeLength(writer, length);
writer.Write(innerStream.GetBuffer(), 0, length);
}
var base64 = Convert.ToBase64String(stream.GetBuffer(), 0, (int)stream.Length);
StringBuilder sb = new StringBuilder();
sb.AppendLine("-----BEGIN PUBLIC KEY-----");
sb.AppendLine(base64);
sb.AppendLine("-----END PUBLIC KEY-----");
output = sb.ToString();
}
return output;
}
private static void EncodeLength(BinaryWriter stream, int length)
{
if (length < 0) throw new ArgumentOutOfRangeException("length", "Length must be non-negative");
if (length < 0x80)
{
// Short form
stream.Write((byte)length);
}
else
{
// Long form
var temp = length;
var bytesRequired = 0;
while (temp > 0)
{
temp >>= 8;
bytesRequired++;
}
stream.Write((byte)(bytesRequired | 0x80));
for (var i = bytesRequired - 1; i >= 0; i--)
{
stream.Write((byte)(length >> (8 * i) & 0xff));
}
}
}
private static void EncodeIntegerBigEndian(BinaryWriter stream, byte[] value, bool forceUnsigned = true)
{
stream.Write((byte)0x02); // INTEGER
var prefixZeros = 0;
for (var i = 0; i < value.Length; i++)
{
if (value[i] != 0) break;
prefixZeros++;
}
if (value.Length - prefixZeros == 0)
{
EncodeLength(stream, 1);
stream.Write((byte)0);
}
else
{
if (forceUnsigned && value[prefixZeros] > 0x7f)
{
// Add a prefix zero to force unsigned if the MSB is 1
EncodeLength(stream, value.Length - prefixZeros + 1);
stream.Write((byte)0);
}
else
{
EncodeLength(stream, value.Length - prefixZeros);
}
for (var i = prefixZeros; i < value.Length; i++)
{
stream.Write(value[i]);
}
}
}
public static string DecryptValue(string cypherText)
{
string plainTextData = string.Empty;
string privKeyBase64 = ""; //get value from session/temp storage
string privKeyString = System.Text.Encoding.ASCII.GetString(Convert.FromBase64String(privKeyBase64));
var sr = new System.IO.StringReader(privKeyString);
var xs = new System.Xml.Serialization.XmlSerializer(typeof(RSAParameters));
var privKey = (RSAParameters)xs.Deserialize(sr);
var csp = new RSACryptoServiceProvider();
csp.ImportParameters(privKey);
var bytesCypherText = Convert.FromBase64String(cypherText);
//Problematic line
var bytesPlainTextData = csp.Decrypt(bytesCypherText, false);
plainTextData = System.Text.Encoding.Unicode.GetString(bytesPlainTextData);
return plainTextData;
}
JS
function encrypt(msg, publicKey) {
var jsEncrypt = new JSEncrypt();
jsEncrypt.setPublicKey(publicKey);
var encrypted = jsEncrypt.encrypt(msg);
var base64result = btoa(encrypted);
return base64result;
}
Update/Variation:
如果我将密钥大小设置为 1024 而不是 2048,我得到的错误是 The data to be decrypted exceeds the maximum for this modulus of 128 bytes.
。根据我在调试器中看到的内容,加密字符串的总字节数为 172。
更新 |解决方案:
- 删除 JS 中的这一行
var base64result = btoa(encrypted);
。直接return加密后的值即可。 - 在 C# 解密方法中,将此行
var bytesPlainTextData = csp.Decrypt(bytesCypherText, false);
更改为var bytesPlainTextData = csp.Decrypt(bytesCypherText, RSAEncryptionPadding.Pkcs1);
。
备注:
- 请忽略不正确的方法 return 类型,这仍然是一个 POC,很多值正在手动填充。
- 我不是安全方面的专家,所以很多代码都是从不同来源挑选出来的。
您面临非对称加密的限制。对于大块数据来说速度非常慢,并且加密字符串的大小受您使用的 RSA 密钥大小的限制。
RSA 通常用于交换对称密钥和处理大部分数据。如果您必须对大量数据使用非对称负载,那么您需要将有效负载分解为较小的负载并在另一侧进行重建。
RSA is only able to encrypt data to a maximum amount of your key size (2048 bits = 256 bytes) minus padding / header data (11 bytes for PKCS#1 v1.5 padding).
如果如您所说,您只发送 20 个字符,那么请确实使用断点检查您的解密函数是否确实获得了足够小的密文。如果不是,您需要回溯并检查您发送错误内容的位置。
也有可能是JSEncrypt中RSA的标准和C#中的RSA不一样可以看出