Импортировать открытый ключ из другого места в CngKey?

Я ищу кросс-платформенный способ обмена открытыми ключами для подписания ECDSA. У меня было замечательное дело с точки зрения производительности с CngKey и стандартными.NET-криптовыми библиотеками, но тогда я не мог понять, как открытый (33) (или 65) байтовый открытый ключ (с использованием secp256r1/P256) превращается в 104 байта по MS.. Ergo, я не мог поддерживать кросс-платформенную подпись и проверку.

Сейчас я использую BouncyCastle, но священная гранада - это МЕДЛЕННО!

Итак, ищите предложения по следующим требованиям:

  1. Кросс-платформа/Языки (сервер -.NET, но это поддерживается через интерфейс JSON/Web.API)
    • JavaScript, Ruby, Python, C++ и т.д.
  2. Не сумасшедший, как медленный на сервере
  3. Не так больно медленно люди не могут использовать его на клиенте.

Клиент должен иметь возможность подписывать сообщение, сервер должен иметь возможность проверить подпись с открытым ключом, который был обменен при регистрации в службе.

В любом случае, Идеи были бы замечательными... Спасибо

Ответ 1

Итак, я выяснил формат CngKey, экспортированный в ECCPublicKeyBlob и ECCPrivateKeyBlob. Это должно позволить другим взаимодействовать между другими ключевыми форматами и подписанием CngKey для подписи Elliptcal Curve и т.д.

ECCPrivateKeyBlob отформатирован (для P256) следующим образом

  • [KEY TYPE (4 bytes)] [KEY LENGTH (4 bytes)] [PUBLIC KEY (64 bytes)] [ЧАСТНЫЙ КЛЮЧ (32 байт)]
  • КЛЮЧЕВЫЙ ТИП в HEX - 45-43-53-32
  • КЛЮЧЕВАЯ ДЛИНА в HEX составляет 20-00-00-00
  • PUBLIC KEY - это несжатый формат минус ведущий байт (который всегда означает 04, чтобы обозначить несжатый ключ в других библиотеках)

ECCPublicKeyBlob отформатирован (для P256) следующим образом

  • [KEY TYPE (4 bytes)] [KEY LENGTH (4 bytes)] [PUBLIC KEY (64 bytes)]
  • КЛЮЧЕВЫЙ ТИП в HEX - 45-43-53-31
  • КЛЮЧЕВАЯ ДЛИНА в HEX составляет 20-00-00-00
  • PUBLIC KEY - это несжатый формат минус ведущий байт (который всегда означает 04, чтобы обозначить несжатый ключ в других библиотеках)

Так что, учитывая несжатый открытый ключ в Hex с другого языка, вы можете обрезать первый байт, добавить эти 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.

Значения MAGIC, доступные в .NET, находятся в Официальный 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,
    ...
    ...
}

Ответ 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

Я просто подумал, что могу сказать спасибо обоим выше постам, так как это очень помогло мне. Я должен был проверить подпись, используя открытый ключ RSA, используя объект RSACng. Раньше я использовал 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 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 справляется с этим в любом случае.