【问题标题】:Import a Public key from somewhere else to CngKey?将公钥从其他地方导入 CngKey?
【发布时间】:2014-08-06 17:30:21
【问题描述】:

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

我现在正在使用 BouncyCastle,但神圣的手榴弹速度太慢了!

所以,寻找以下要求的建议:

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

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

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

【问题讨论】:

  • 使用 OpenSSL 库。这是您可以获得的最跨平台的。
  • 鉴于最近反复出现的 OpenSSL 问题,我有点害怕。但如果包装 openSSL 是获得性能和支持的唯一方法,我会再看一次(或 3 次)..
  • OpenSSL 中的曲线应该没有任何问题。它已经在比特币等应用程序中使用了几年。你应该没事。
  • 他们在 HTTPS 中使用它已经有几年(很多很多年)了,而且“没有问题”......因此我担心 :-P.. 但是是的,考虑到用例我可能可以安全地使用它……只是有点害羞。

标签: c# javascript cryptography bouncycastle cng


【解决方案1】:

所以我已经弄清楚了在 ECCPublicKeyBlob 和 ECCPrivateKeyBlob 中导出的 CngKey 的格式。这应该允许其他人在其他密钥格式和 CngKey 之间进行互操作,以进行椭圆曲线签名等。

ECCPrivateKeyBlob 的格式(对于 P256)如下

  • [KEY TYPE(4 字节)][KEY LENGTH(4 字节)][PUBLIC KEY(64 字节)][PRIVATE KEY(32 字节)]
  • HEX 中的 KEY TYPE 是 45-43-53-32
  • 十六进制的键长度为 20-00-00-00
  • PUBLIC KEY 是未压缩格式减去前导字节(在其他库中始终为 04 表示未压缩密钥)

ECCPublicKeyBlob 的格式(对于 P256)如下

  • [KEY TYPE(4 个字节)][KEY LENGTH(4 个字节)][PUBLIC KEY(64 个字节)]
  • 十六进制的KEY TYPE是45-43-53-31
  • 十六进制的键长度为 20-00-00-00
  • PUBLIC KEY 是未压缩格式减去前导字节(在其他库中始终为 04 表示未压缩密钥)

因此,给定一个来自其他语言的未压缩十六进制公钥,您可以修剪第一个字节,将这 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's official 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,
    ...
    ...
}

【讨论】:

  • 您对此有任何可引用的参考吗? P384 和 P521 的字节签名是否相同?即,公钥的实际字节是否总是可以导出为len = int32_le(blob[4..7]); pub = blob[8..7+len*2]; priv = blob[8+len*2...]
  • 我什么都找不到,只能对内部项目进行单元测试,并成功使用这种存储数十万条 PII 的算法..
  • msdn.microsoft.com/en-us/library/windows/desktop/…。实际的魔法值来自 bcrypt.h,例如#define BCRYPT_ECDSA_PRIVATE_P256_MAGIC 0x32534345L
  • 谢谢,这拯救了我的一天。
  • 所以。很多。挖掘。找到这个答案。谢谢!我希望它对其他人的价值和对我的价值一样多。
【解决方案2】:

多亏了你,我能够使用以下代码从证书中导入 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);

【讨论】:

    【解决方案3】:

    我只是想对以上两个帖子表示感谢,因为它极大地帮助了我。我必须使用RSACng 对象使用RSA 公钥验证签名。我之前使用的是 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 = <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)可以包含在模数中,也可以不包含,因此如果包含,长度会大一。无论哪种方式,CNGkey 似乎都可以处理。

    【讨论】:

      【解决方案4】:

      您可以通过这样的方式将 EC 密钥转换为 BCRYPT_ECCKEY_BLOB。我们应该忽略 EC 密钥的第一个字节,因为它只是表示压缩/未压缩格式。

      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 进行导入。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-03-13
        • 1970-01-01
        • 2013-08-06
        • 2016-08-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-01-29
        相关资源
        最近更新 更多