将 Openssl 签名转换为 .NET6
Converting Openssl signing to .NET6
应用程序调用 openssl 进行签名使用
openssl rsautl -sign -in rasi.bin -inkey riktest.key -out allkiri.bin
如何将其转换为 .NET 6 以便不需要调用 openssl?
riktest.key 是包含
的文本文件
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAfddAQEArYjDH7msMFifeYc1AG/TkKpcz2LITI73sC0eqnlgmWi3F7PD
Bo8lWrCw32h3v/FFMrK8KuktlnBtsSLaCCz1DWuXORzHaW7EqG8O8QNzFSmhIoqp
...
这是 ASP.NET6 MVC 应用程序。 .NET 6 System.Security.Cryptography 命名空间是否包含此 OpenSsl 功能?
为什么通常无法使用本机 .NET 方法
对于 RSASignaturePadding.Pkcs1
,本机 .NET 实现 SignData()
and SignHash()
follow the RSASSA-PKCS1-v1_5 signature scheme described in RFC8017, which applies EMSA-PKCS1-v1_5 作为编码操作:对消息进行哈希处理并签署以下值(即使用私钥加密):
EM = 0x00 || 0x01 || PS || 0x00 || T
这里PS由这么多0xff值组成,EM的大小等于密钥模数的大小。 T 是 DigestInfo 值的 DER 编码,它包含摘要 OID 和散列,例如对于 SHA256:
3031300d060960864801650304020105000420 || H
其中 H 是要签名的消息 M 的 32 字节 SHA56 哈希值。
相比之下,openssl rsautl直接使用RSA算法,如NOTES部分所述,即对以下数据进行签名:
EM' = 0x00 || 0x01 || PS || 0x00 || M
这通常无法通过本机 .NET 方法实现(特殊用例除外,见下文):SignData()
散列因此失败,SignHash()
不散列但在内部(像 SignData()
) 生成 DigestInfo 值的 DER 编码。
另一种选择是 BouncyCastle,它使用 NoneWithRSA
算法签名,就像 openssl rsautl.
该算法的一个缺点是,由于缺少散列,只能对短消息进行签名,因为较长的消息无法满足长度标准(根据该标准,EM' 的大小必须对应于密钥的模数)。
密钥导入
发布的密钥是 PKCS#1 格式的 PEM 编码私钥。
.NET 支持使用 ImportFromPem()
since .NET 5, but the import of DER encoded keys has been supported since .NET Core 3.0. A private DER encoded key in PKCS#1 format can be imported with ImportRSAPrivateKey()
导入 PEM 编码密钥(private/public,PKCS#8/PKCS#1 格式)(PEM 和 DER 编码之间的转换很简单,包括删除页眉、页脚和换行符以及剩余正文的 Base64 解码)。
BouncyCastle 支持使用 PemReader
class.
导入 PEM 编码密钥
已发布的 OpenSSL 功能的可能实现与 BouncyCastle
当rasi.bin保存来自dataToSign
和[=110的数据时,以下代码生成与OpenSSL语句相同的签名=] 持有来自 privatePkcs1Pem
:
的密钥
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using System;
using System.IO;
...
// For testing purposes a 512 bits key is used.
// In practice, keys >= 2048 bits must be used for security reasons!
string privatePkcs1Pem = @"-----BEGIN RSA PRIVATE KEY-----
MIIBOwIBAAJBANoHbFSEZoOSB9Kxt7t8PoBwmauaODjECHqJgtTU3h4MW5K3857+
04Flc6x6a9xxyvCKS5RtOP2gaOlOVtrph0ECAwEAAQJBALu8LpRr2RWrdV7/tfQT
HIJd8oQnbAe9DIvuwh/fF08IwApOE/iGL+Ded49eoHHu1OXycZhpHavN/sQMnssP
FNECIQDyDIW7V5UUu16ZAeupeQ7zdV6ykVngd0bb3FEn99EchQIhAOaYe3ll211q
SIXVjKHudMn3xe6Vvguc9O7cwCB+gyqNAiEAsr3kk6/de23SMZNlf8TR8Z8eyybj
BAuQ3BMaKzWpyjECIFMR0UFNYTYIyLF12aCoH2h2mtY1GW5jj5TQ72GFUcktAiAf
WWXnts7m8kZWuKjfD0MQiW+w4iAph+51j+wiL3EMAQ==
-----END RSA PRIVATE KEY-----";
byte[] dataToSign = Convert.FromHexString("3031300d060960864801650304020105000420d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592");
// Import private PKCS#1 key, PEM encoded
PemReader pemReader = new PemReader(new StringReader(privatePkcs1Pem));
AsymmetricKeyParameter privateKeyParameter = ((AsymmetricCipherKeyPair)pemReader.ReadObject()).Private;
// Sign raw data
ISigner signer = SignerUtilities.GetSigner("NoneWithRSA");
signer.Init(true, privateKeyParameter);
signer.BlockUpdate(dataToSign, 0, dataToSign.Length);
byte[] signature = signer.GenerateSignature();
Console.WriteLine(Convert.ToHexString(signature)); // 8C83CAD897EDA249FEC9EBA231061D585DAFC99177267E3E71BB8A3FCE07CC6663BF4DF7AF2E1C1945D2A6BB42EB25F042228B591FC18CDA82D92CAAE844670C
特殊用例 - 当可以使用本机 C# 方法时
如果rasi.bin包含DigestInfo值的DER编码,则不需要BouncyCastle。
以下示例假定 rasi.bin 包含消息 DigestInfo 值的 DER 编码 The quick brown fox跳过以 SHA256 为摘要的懒狗。 IE。最后 32 个字节对应于 SHA256 哈希。
那么使用本机 .NET 方法的可能实现是:
using System;
using System.Security.Cryptography;
...
// For testing purposes a 512 bits key is used.
// In practice, keys >= 2048 bits must be used for security reasons!
string privatePkcs1Pem = @"-----BEGIN RSA PRIVATE KEY-----
MIIBOwIBAAJBANoHbFSEZoOSB9Kxt7t8PoBwmauaODjECHqJgtTU3h4MW5K3857+
04Flc6x6a9xxyvCKS5RtOP2gaOlOVtrph0ECAwEAAQJBALu8LpRr2RWrdV7/tfQT
HIJd8oQnbAe9DIvuwh/fF08IwApOE/iGL+Ded49eoHHu1OXycZhpHavN/sQMnssP
FNECIQDyDIW7V5UUu16ZAeupeQ7zdV6ykVngd0bb3FEn99EchQIhAOaYe3ll211q
SIXVjKHudMn3xe6Vvguc9O7cwCB+gyqNAiEAsr3kk6/de23SMZNlf8TR8Z8eyybj
BAuQ3BMaKzWpyjECIFMR0UFNYTYIyLF12aCoH2h2mtY1GW5jj5TQ72GFUcktAiAf
WWXnts7m8kZWuKjfD0MQiW+w4iAph+51j+wiL3EMAQ==
-----END RSA PRIVATE KEY-----";
byte[] sha256DigestInfoDer = Convert.FromHexString("3031300d060960864801650304020105000420d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592");
byte[] sha256HashToSign = new byte[32];
Buffer.BlockCopy(sha256DigestInfoDer, sha256DigestInfoDer.Length - sha256HashToSign.Length, sha256HashToSign, 0, sha256HashToSign.Length);
using (RSA rsa = RSA.Create())
{
rsa.ImportFromPem(privatePkcs1Pem);
byte[] signature = rsa.SignHash(sha256HashToSign, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); // pass the SHA256 hash, internally the DER encoding of the DigestInfo is generated (which is why the digest must be specified)
Console.WriteLine(Convert.ToHexString(signature)); // 8C83CAD897EDA249FEC9EBA231061D585DAFC99177267E3E71BB8A3FCE07CC6663BF4DF7AF2E1C1945D2A6BB42EB25F042228B591FC18CDA82D92CAAE844670C
}
给出了 same 签名,因为 rasi.bin 在这两种情况下是相同的。
但是,请记住最后一种方法只有在 rasi.bin 包含 DigestInfo 值的 DER 编码时才有效,而第一个解决方案适用于 rasi.bin 中的 arbitrary 数据(只要满足长度标准)。
应用程序调用 openssl 进行签名使用
openssl rsautl -sign -in rasi.bin -inkey riktest.key -out allkiri.bin
如何将其转换为 .NET 6 以便不需要调用 openssl?
riktest.key 是包含
的文本文件-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAfddAQEArYjDH7msMFifeYc1AG/TkKpcz2LITI73sC0eqnlgmWi3F7PD
Bo8lWrCw32h3v/FFMrK8KuktlnBtsSLaCCz1DWuXORzHaW7EqG8O8QNzFSmhIoqp
...
这是 ASP.NET6 MVC 应用程序。 .NET 6 System.Security.Cryptography 命名空间是否包含此 OpenSsl 功能?
为什么通常无法使用本机 .NET 方法
对于 RSASignaturePadding.Pkcs1
,本机 .NET 实现 SignData()
and SignHash()
follow the RSASSA-PKCS1-v1_5 signature scheme described in RFC8017, which applies EMSA-PKCS1-v1_5 作为编码操作:对消息进行哈希处理并签署以下值(即使用私钥加密):
EM = 0x00 || 0x01 || PS || 0x00 || T
这里PS由这么多0xff值组成,EM的大小等于密钥模数的大小。 T 是 DigestInfo 值的 DER 编码,它包含摘要 OID 和散列,例如对于 SHA256:
3031300d060960864801650304020105000420 || H
其中 H 是要签名的消息 M 的 32 字节 SHA56 哈希值。
相比之下,openssl rsautl直接使用RSA算法,如NOTES部分所述,即对以下数据进行签名:
EM' = 0x00 || 0x01 || PS || 0x00 || M
这通常无法通过本机 .NET 方法实现(特殊用例除外,见下文):SignData()
散列因此失败,SignHash()
不散列但在内部(像 SignData()
) 生成 DigestInfo 值的 DER 编码。
另一种选择是 BouncyCastle,它使用 NoneWithRSA
算法签名,就像 openssl rsautl.
该算法的一个缺点是,由于缺少散列,只能对短消息进行签名,因为较长的消息无法满足长度标准(根据该标准,EM' 的大小必须对应于密钥的模数)。
密钥导入
发布的密钥是 PKCS#1 格式的 PEM 编码私钥。
.NET 支持使用 ImportFromPem()
since .NET 5, but the import of DER encoded keys has been supported since .NET Core 3.0. A private DER encoded key in PKCS#1 format can be imported with ImportRSAPrivateKey()
导入 PEM 编码密钥(private/public,PKCS#8/PKCS#1 格式)(PEM 和 DER 编码之间的转换很简单,包括删除页眉、页脚和换行符以及剩余正文的 Base64 解码)。
BouncyCastle 支持使用 PemReader
class.
已发布的 OpenSSL 功能的可能实现与 BouncyCastle
当rasi.bin保存来自dataToSign
和[=110的数据时,以下代码生成与OpenSSL语句相同的签名=] 持有来自 privatePkcs1Pem
:
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using System;
using System.IO;
...
// For testing purposes a 512 bits key is used.
// In practice, keys >= 2048 bits must be used for security reasons!
string privatePkcs1Pem = @"-----BEGIN RSA PRIVATE KEY-----
MIIBOwIBAAJBANoHbFSEZoOSB9Kxt7t8PoBwmauaODjECHqJgtTU3h4MW5K3857+
04Flc6x6a9xxyvCKS5RtOP2gaOlOVtrph0ECAwEAAQJBALu8LpRr2RWrdV7/tfQT
HIJd8oQnbAe9DIvuwh/fF08IwApOE/iGL+Ded49eoHHu1OXycZhpHavN/sQMnssP
FNECIQDyDIW7V5UUu16ZAeupeQ7zdV6ykVngd0bb3FEn99EchQIhAOaYe3ll211q
SIXVjKHudMn3xe6Vvguc9O7cwCB+gyqNAiEAsr3kk6/de23SMZNlf8TR8Z8eyybj
BAuQ3BMaKzWpyjECIFMR0UFNYTYIyLF12aCoH2h2mtY1GW5jj5TQ72GFUcktAiAf
WWXnts7m8kZWuKjfD0MQiW+w4iAph+51j+wiL3EMAQ==
-----END RSA PRIVATE KEY-----";
byte[] dataToSign = Convert.FromHexString("3031300d060960864801650304020105000420d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592");
// Import private PKCS#1 key, PEM encoded
PemReader pemReader = new PemReader(new StringReader(privatePkcs1Pem));
AsymmetricKeyParameter privateKeyParameter = ((AsymmetricCipherKeyPair)pemReader.ReadObject()).Private;
// Sign raw data
ISigner signer = SignerUtilities.GetSigner("NoneWithRSA");
signer.Init(true, privateKeyParameter);
signer.BlockUpdate(dataToSign, 0, dataToSign.Length);
byte[] signature = signer.GenerateSignature();
Console.WriteLine(Convert.ToHexString(signature)); // 8C83CAD897EDA249FEC9EBA231061D585DAFC99177267E3E71BB8A3FCE07CC6663BF4DF7AF2E1C1945D2A6BB42EB25F042228B591FC18CDA82D92CAAE844670C
特殊用例 - 当可以使用本机 C# 方法时
如果rasi.bin包含DigestInfo值的DER编码,则不需要BouncyCastle。
以下示例假定 rasi.bin 包含消息 DigestInfo 值的 DER 编码 The quick brown fox跳过以 SHA256 为摘要的懒狗。 IE。最后 32 个字节对应于 SHA256 哈希。
那么使用本机 .NET 方法的可能实现是:
using System;
using System.Security.Cryptography;
...
// For testing purposes a 512 bits key is used.
// In practice, keys >= 2048 bits must be used for security reasons!
string privatePkcs1Pem = @"-----BEGIN RSA PRIVATE KEY-----
MIIBOwIBAAJBANoHbFSEZoOSB9Kxt7t8PoBwmauaODjECHqJgtTU3h4MW5K3857+
04Flc6x6a9xxyvCKS5RtOP2gaOlOVtrph0ECAwEAAQJBALu8LpRr2RWrdV7/tfQT
HIJd8oQnbAe9DIvuwh/fF08IwApOE/iGL+Ded49eoHHu1OXycZhpHavN/sQMnssP
FNECIQDyDIW7V5UUu16ZAeupeQ7zdV6ykVngd0bb3FEn99EchQIhAOaYe3ll211q
SIXVjKHudMn3xe6Vvguc9O7cwCB+gyqNAiEAsr3kk6/de23SMZNlf8TR8Z8eyybj
BAuQ3BMaKzWpyjECIFMR0UFNYTYIyLF12aCoH2h2mtY1GW5jj5TQ72GFUcktAiAf
WWXnts7m8kZWuKjfD0MQiW+w4iAph+51j+wiL3EMAQ==
-----END RSA PRIVATE KEY-----";
byte[] sha256DigestInfoDer = Convert.FromHexString("3031300d060960864801650304020105000420d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592");
byte[] sha256HashToSign = new byte[32];
Buffer.BlockCopy(sha256DigestInfoDer, sha256DigestInfoDer.Length - sha256HashToSign.Length, sha256HashToSign, 0, sha256HashToSign.Length);
using (RSA rsa = RSA.Create())
{
rsa.ImportFromPem(privatePkcs1Pem);
byte[] signature = rsa.SignHash(sha256HashToSign, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); // pass the SHA256 hash, internally the DER encoding of the DigestInfo is generated (which is why the digest must be specified)
Console.WriteLine(Convert.ToHexString(signature)); // 8C83CAD897EDA249FEC9EBA231061D585DAFC99177267E3E71BB8A3FCE07CC6663BF4DF7AF2E1C1945D2A6BB42EB25F042228B591FC18CDA82D92CAAE844670C
}
给出了 same 签名,因为 rasi.bin 在这两种情况下是相同的。
但是,请记住最后一种方法只有在 rasi.bin 包含 DigestInfo 值的 DER 编码时才有效,而第一个解决方案适用于 rasi.bin 中的 arbitrary 数据(只要满足长度标准)。