当前位置: 首页 > 知识库问答 >
问题:

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

张高义
2023-03-14

我正在寻找一种跨平台的方式来共享ECDSA签名的公钥。从性能角度来看,我对CngKey和标准的.NET加密库有很大的了解,但后来我无法理解MS如何将33(或65)字节的公钥(使用secp256r1/P256)转换为104字节。因此,我无法支持跨平台签名和验证。。

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

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

  • 跨平台/语言(服务器是.NET,但这是通过JSON/Web.API接口提供的)
    • JavaScript、Ruby、Python、C等

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

    不管怎样,想法会很棒的...谢谢

共有3个答案

朱修德
2023-03-14

我想我应该感谢以上两个帖子,因为它们极大地帮助了我。我必须使用RSACng对象来验证使用RSA公钥的签名。我以前使用的是RSACryptoServiceProvider,但它不符合FIPS标准,所以我在切换到RSACng时遇到了一些问题。它还要求。净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似乎可以以任何方式处理它。

甘西岭
2023-03-14

多亏了你,我能够从证书中导入一个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);
平庆
2023-03-14

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

ECCPrivateKeyBlob的格式(对于P256)如下所示:

  • [密钥类型(4 字节)][密钥长度(4 字节)][公钥(64 字节)][私钥(32 字节)]
  • 十六进制中的密钥类型为 45-43-53-32
  • 十六进制中的密钥长度为 20-00-00-00
  • 公钥是未压缩的格式减去前导字节(始终为 04,表示其他库中的未压缩键)

ECCPublicKeyBlob的格式(对于P256)如下

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

因此,给定一个来自另一种语言的十六进制未压缩公钥,您可以修剪第一个字节,将这8个字节添加到前面,然后使用

CngKey.Import(key,CngKeyBlobFormat.EccPrivateBlob);

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

密钥类型和密钥长度在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中的魔法值位于微软官方的GitHub dotnet/corefx BCrypt/Interop.blob中。

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,
    ...
    ...
}
 类似资料:
  • 我想通过RSACryptServiceProvider签名和验证数据 为此,我需要公钥和私钥。 在我的项目中,我将公钥和私钥存储在XML文件中。 有一次,我生成了我的公钥和私钥 在上面,我展示了“通过公钥验证数据” 我的SignData方法类似。 在那里, 我的问题是:举个例子,我认识到;通过crt/pem/cert等导入公钥和私钥。证书文件。在那里我存储了XML文件。 我的解决方案错了吗? 第二

  • 我有这样的代码来导入一个ECDH键: 这是可行的,但是它只返回私钥,而不是整个密钥对。如何从私钥中导出公钥?

  • 我试图从私钥中生成一个公共ECDSA密钥,但我还没有在互联网上找到多少关于如何实现这一点的帮助。几乎所有的事情都是为了从公钥规范生成公钥,我不知道如何得到它。到目前为止,我总结了以下内容: 但是,在运行时,我会出现以下错误: 我做错了什么?有更好/更简单的方法吗? 编辑:我已经设法编译了一些代码,但它不能正常工作: 当我运行它时,它会生成一个公钥,但它不是私钥对应的公钥。

  • 我们如何加载他们的公钥?

  • 我在将存在acrofield从一个pdf导入另一个pdf时遇到问题。这两个pdf相似。我尝试导入并保存文件(下面的代码)。如果我从文件系统打开它,我看不到更改,但如果我用pdfbox打开它,我看到之前插入的acrofile。我注意到文件大小增加了,但当我打开它时,我看不到可填充的字段。 提前谢谢你

  • 从其他应用程序(如 Photoshop、Illustrator、After Effects、Sketch)中导入资源,甚至将图像从 Web 浏览器拖放到 Adobe XD 或将图像从剪贴板粘贴到 Adobe XD 中。 您可以将资源从其他 Adobe 应用程序(例如 Photoshop、Illustrator)和第三方应用程序(如 Sketch)导入 Adobe XD。您可以在 XD 中进一步改进