从其他地方导入公钥到CngKey
本文关键字:公钥 CngKey 导入 其他 方导入 | 更新日期: 2023-09-27 18:08:48
我正在寻找一种跨平台的方式来共享ECDSA签名的公钥。我有一个伟大的事情从性能的角度与CngKey和标准。net加密库,但后来我无法弄清楚如何33(或65)字节的公钥(使用secp256r1/P256)被MS变成104字节。因此,我无法支持跨平台签名和验证。
我现在正在使用BouncyCastle,但是它太慢了!
所以,寻找以下要求的建议:
- 跨平台/语言(服务器是。net,但这是通过JSON/Web服务。API接口)
- JavaScript, Ruby, Python, c++等
- 不疯狂,因为在服务器上慢
- 并不是慢到让人无法在客户端使用它。
客户端必须能够签署消息,服务器必须能够使用注册到服务时交换的公钥验证签名。
无论如何,想法会很棒…由于
所以我已经找出了在ECCPublicKeyBlob和ECCPrivateKeyBlob中导出的cnkey的格式。这应该允许其他人在其他密钥格式和CngKey之间进行交互,用于椭圆曲线签名等。
ECCPrivateKeyBlob的格式(对于P256)如下
- [密钥类型(4字节)][密钥长度(4字节)][公钥(64字节)][私钥(32字节)]
- KEY TYPE in HEX = 45-43-53-32
- KEY LENGTH in HEX = 20-00-00-00
- PUBLIC KEY是未压缩的格式减去前导字节(在其他库中总是为04表示未压缩的密钥)
ECCPublicKeyBlob的格式(对于P256)如下
- [密钥类型(4字节)][密钥长度(4字节)][公开密钥(64字节)]
- KEY TYPE in HEX = 45-43-53-31
- KEY LENGTH in HEX = 20-00-00-00
- PUBLIC KEY是未压缩的格式减去前导字节(在其他库中总是为04表示未压缩的密钥)
给定另一种语言的未压缩的十六进制公钥,您可以修剪第一个字节,将这8个字节添加到前面并使用
导入它CngKey.Import(key,CngKeyBlobFormat.EccPrivateBlob);
注意:键blob格式是由Microsoft文档。
KEY TYPE和KEY LENGTH在BCRYPT_ECCKEY_BLOB struct中定义为:
{ 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
开始。然后添加您描述的标题。
那么我就可以验证这样的签名:
var crypto = ECDsaCng(cngKey);
var verify = crypto.VerifyHash(hash, sig);
我只是想感谢上面两个帖子,因为它极大地帮助了我。我必须使用RSACng对象使用RSA公钥验证签名。我以前使用RSACryptoServiceProvider,但这不是FIPS兼容的,所以我在切换到rsacg时遇到了一些问题。它还需要。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 = <your public key exponent>; //Typically equal to from what I've found: {0x01, 0x00, 0x01}
byte[] btMod = <your public key modulus>; //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)既可以包含在模数中,也可以不包含在模数中,因此如果包含它,长度将大1。
按如下方式将EC key转换为BCRYPT_ECCKEY_BLOB。我们应该忽略EC key的第一个字节,因为它只表示压缩/未压缩格式。
BCRYPT_ECCKEY_BLOB eccBlobHeader;
PCHAR bycrtptKey;
eccBlobHeader.dwMagic = BCRYPT_ECDH_PUBLIC_P384_MAGIC;
eccBlobHeader.cbKey = 48;//size of EC key(without 1st byte)
memcpy(bycrtptKey, &eccBlobHeader, 8);//copying 8bytes header blob
memcpy(bycrtptKey+ 8,publicKeyFromOtherParty+1,publicKeyFromOtherPartySize- 1);
现在使用bycrtptKey导入