19

ECDSA 署名用の公開鍵を共有するクロスプラットフォームの方法を探しています。CngKey と標準の .NET 暗号化ライブラリを使用して、パフォーマンスの観点からは素晴らしいことを成し遂げましたが、33 (または 65) バイトの公開鍵 (secp256r1/P256 を使用) が 104 バイトに変換される方法を理解できませんでした。 MSによる..エルゴ、私はクロスプラットフォームの署名と検証をサポートできませんでした..

今はBouncyCastleを使っていますが、ホーリーハンドグラネードは遅いです!

したがって、次の要件に対する提案を探しています。

  1. クロスプラットフォーム/言語 (サーバーは .NET ですが、これは JSON/Web.API インターフェースを介して提供されます)
    • JavaScript、Ruby、Python、C++ など。
  2. サーバー上でそれほど遅くない
  3. クライアントでそれを使用できないほど遅い人ではありません。

クライアントはメッセージに署名できる必要があり、サーバーはサービスへの登録時に交換された公開鍵を使用して署名を検証できる必要があります。

とにかく、アイデアは素晴らしいだろう...ありがとう

4

4 に答える 4

37

そこで、ECCPublicKeyBlob と ECCPrivateKeyBlob でエクスポートされる CngKey の形式を把握しました。これにより、他のキー形式と楕円曲線署名などの CngKey との間で他のユーザーが相互運用できるようになります。

ECCPrivateKeyBlob は次のようにフォーマットされます (P256 の場合)。

  • [キーの種類 (4 バイト)][キーの長さ (4 バイト)][公開キー (64 バイト)][秘密キー (32 バイト)]
  • 16 進数の KEY TYPE は 45-43-53-32 です
  • 16 進数のキーの長さは 20-00-00-00 です
  • PUBLIC KEY は、圧縮されていない形式から先頭のバイトを除いたものです (他のライブラリでは圧縮されていないキーを示すために常に 04 です)。

ECCPublicKeyBlob は次のようにフォーマットされます (P256 の場合)。

  • [キーの種類 (4 バイト)][キーの長さ (4 バイト)][公開キー (64 バイト)]
  • 16 進数の KEY TYPE は 45-43-53-31 です
  • 16 進数のキーの長さは 20-00-00-00 です
  • PUBLIC KEY は、圧縮されていない形式から先頭のバイトを除いたものです (他のライブラリでは圧縮されていないキーを示すために常に 04 です)。

したがって、別の言語からの 16 進数の圧縮されていない公開鍵が与えられた場合、最初のバイトをトリミングし、それらの 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,
    ...
    ...
}
于 2014-06-17T03:00:50.697 に答える
5

あなたのおかげで、次のコードを使用して、証明書から 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;
    }

0x04削除する必要がある65 バイトの鍵 (公開鍵のみ) で始まります。次に、説明したヘッダーが追加されます。

その後、次のような署名を検証できました。

var crypto = ECDsaCng(cngKey);
var verify = crypto.VerifyHash(hash, sig);
于 2015-10-23T18:58:56.067 に答える
1

I just thought I would say thanks to both above posts as it helped me out tremendously. I had to verify a signature using RSA public key using the RSACng object. I was using the RSACryptoServiceProvider before, but that is not FIPS compliant, so I had some problems switching to RSACng. It also requires .NET 4.6. Here is how I got it to work using the above posters as an example:

                    // 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);

Note: The sign byte for the modulus (0x00) can either be included in the modulus or not, so the length will be one bigger if it is included. CNGkey seems to handle it ok either way.

于 2016-10-14T18:33:02.117 に答える