将公钥从其他地方导入CngKey?

我正在寻找一种跨平台的方式来共享ECDSA签名的公钥。 从CngKey和标准.NET加密库的性能角度来看,我有一个很棒的事情,但后来我无法弄清楚33(或65)字节的公钥(使用secp256r1 / P256)是如何变成104字节的由MS .. Ergo,我无法支持跨平台签名和validation..

我现在正在使用BouncyCastle,但神圣的handgranade它很慢!

因此,寻找以下要求的建议:

  1. 跨平台/语言(服务器是.NET,但这是通过JSON / Web.API接口提供的)
    • JavaScript,Ruby,Python,C ++等。
  2. 在服务器上并不疯狂
  3. 不那么痛苦的慢人们不能在客户端上使用它。

客户端必须能够对消息进行签名,服务器必须能够使用在注册到服务时交换的公钥来validation签名。

无论如何,想法会很棒…谢谢

所以我已经找到了在ECCPublicKeyBlob和ECCPrivateKeyBlob中导出的CngKey的格式。 这应该允许其他人在其他密钥格式和CngKey之间进行互操作以进行Elliptcal Curve签名等。

ECCPrivateKeyBlob格式化(对于P256)如下

  • [密钥类型(4字节)] [密钥长度(4字节)] [公共密钥(64字节)] [私钥(32字节)]
  • HEX中的KEY TYPE为45-43-53-32
  • HEX的关键长度是20-00-00-00
  • PUBLIC KEY是未压缩格式减去前导字节(在其他库中始终为04表示未压缩的密钥)

ECCPublicKeyBlob格式化(对于P256)如下

  • [密钥类型(4字节)] [密钥长度(4字节)] [公共密钥(64字节)]
  • HEX中的KEY TYPE为45-43-53-31
  • HEX的关键长度是20-00-00-00
  • PUBLIC KEY是未压缩格式减去前导字节(在其他库中始终为04表示未压缩的密钥)

因此,如果使用另一种语言的hex语言中的未压缩公钥,则可以修剪第一个字节,将这8个字节添加到前面并使用

CngKey.Import(key,CngKeyBlobFormat.EccPrivateBlob); 

注意 :密钥blob格式由Microsoft记录。

KEY TYPE和KEY LENGTH在BCRYPT_ECCKEY_BLOB结构中定义为:

 { ulong Magic; ulong cbKey; } 

ECC公钥存储格式:

 BCRYPT_ECCKEY_BLOB BYTE X[cbKey] // Big-endian. BYTE Y[cbKey] // Big-endian. 

ECC私钥内存格式:

 BCRYPT_ECCKEY_BLOB BYTE X[cbKey] // Big-endian. BYTE Y[cbKey] // Big-endian. BYTE d[cbKey] // Big-endian. 

.NET中可用的MAGIC值在Microsoft的官方GitHub dotnet / corefx BCrypt / Interop.Blobs中 。

 internal enum KeyBlobMagicNumber : int { BCRYPT_ECDH_PUBLIC_P256_MAGIC = 0x314B4345, BCRYPT_ECDH_PRIVATE_P256_MAGIC = 0x324B4345, BCRYPT_ECDH_PUBLIC_P384_MAGIC = 0x334B4345, BCRYPT_ECDH_PRIVATE_P384_MAGIC = 0x344B4345, BCRYPT_ECDH_PUBLIC_P521_MAGIC = 0x354B4345, BCRYPT_ECDH_PRIVATE_P521_MAGIC = 0x364B4345, BCRYPT_ECDSA_PUBLIC_P256_MAGIC = 0x31534345, BCRYPT_ECDSA_PRIVATE_P256_MAGIC = 0x32534345, BCRYPT_ECDSA_PUBLIC_P384_MAGIC = 0x33534345, BCRYPT_ECDSA_PRIVATE_P384_MAGIC = 0x34534345 BCRYPT_ECDSA_PUBLIC_P521_MAGIC = 0x35534345, BCRYPT_ECDSA_PRIVATE_P521_MAGIC = 0x36534345, ... ... } 

多亏了你,我能够使用以下代码从证书导入ECDSA_P256公钥:

  private static CngKey ImportCngKeyFromCertificate(X509Certificate2 cert) { var keyType = new byte[] {0x45, 0x43, 0x53, 0x31}; var keyLength = new byte[] {0x20, 0x00, 0x00, 0x00}; var key = cert.PublicKey.EncodedKeyValue.RawData.Skip(1); var keyImport = keyType.Concat(keyLength).Concat(key).ToArray(); var cngKey = CngKey.Import(keyImport, CngKeyBlobFormat.EccPublicBlob); return cngKey; } 

65字节密钥(仅限公共密钥)以0x04开头,需要将其删除。 然后添加您描述的标题。

然后我能够validation这样的签名:

 var crypto = ECDsaCng(cngKey); var verify = crypto.VerifyHash(hash, sig); 

我只是觉得我要感谢上面的两篇文章,因为它帮助了我。 我必须使用RSACng对象使用RSA公钥validation签名。 我之前使用的是RSACryptoServiceProvider,但这不符合FIPS,所以我在切换到RSACng时遇到了一些问题。 它还需要.NET 4.6。 以下是我使用上述海报作为例子的方法:

  // This structure is as the header for the CngKey // all should be byte arrays in Big-Endian order //typedef struct _BCRYPT_RSAKEY_BLOB { // ULONG Magic; // ULONG BitLength; // ULONG cbPublicExp; // ULONG cbModulus; // ULONG cbPrime1; private key only // ULONG cbPrime2; private key only //} BCRYPT_RSAKEY_BLOB; // This is the actual Key Data that is attached to the header //BCRYPT_RSAKEY_BLOB // PublicExponent[cbPublicExp] // Modulus[cbModulus] //first get the public key from the cert (modulus and exponent) // not shown byte[] publicExponent = ; //Typically equal to from what I've found: {0x01, 0x00, 0x01} byte[] btMod = ; //for 128 bytes for 1024 bit key, and 256 bytes for 2048 keys //BCRYPT_RSAPUBLIC_MAGIC = 0x31415352, // flip to big-endian byte[] Magic = new byte[] { 0x52, 0x53, 0x41, 0x31}; // for BitLendth: convert the length of the key's Modulus as a byte array into bits, // so the size of the key, in bits should be btMod.Length * 8. Convert to a DWord, then flip for Big-Endian // example 128 bytes = 1024 bits = 0x00000400 = {0x00, 0x00, 0x04, 0x00} = flipped {0x00, 0x04, 0x00, 0x00} // example 256 bytes = 2048 bits = 0x00000800 = {0x00, 0x00, 0x08, 0x00} = flipped {0x00, 0x08, 0x00, 0x00} string sHex = (btMod.Length * 8).ToString("X8"); byte[] BitLength = Util.ConvertHexStringToByteArray(sHex); Array.Reverse(BitLength); //flip to Big-Endian // same thing for exponent length (in bytes) sHex = (publicExponent.Length).ToString("X8"); byte[] cbPublicExp = Util.ConvertHexStringToByteArray(sHex); Array.Reverse(cbPublicExp); // same thing for modulus length (in bytes) sHex = (btMod.Length).ToString("X8"); byte[] cbModulus = Util.ConvertHexStringToByteArray(sHex); Array.Reverse(cbModulus); // add the 0 bytes for cbPrime1 and cbPrime2 (always zeros for public keys, these are used for private keys, but need to be zero here) // just make one array with both 4 byte primes as zeros byte[] cbPrimes = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; //combine all the parts together into the one big byte array in the order the structure var keyImport = Magic.Concat(BitLength).Concat(cbPublicExp).Concat(cbModulus).Concat(cbPrimes).Concat(publicExponent).Concat(btMod).ToArray(); var cngKey = CngKey.Import(keyImport, CngKeyBlobFormat.GenericPublicBlob); // pass the key to the class constructor RSACng rsa = new RSACng(cngKey); //verify: our randomly generated M (message) used to create the signature (not shown), the signature, enum for SHA256, padding verified = rsa.VerifyData(M, signature, HashAlgorithmName.SHA256,RSASignaturePadding.Pkcs1); 

注意:模数(0x00)的符号字节可以包含在模数中,也可以不包含在模数中,因此如果包含模数,则长度将更大。 无论哪种方式,CNGkey似乎都可以处理它。