Генерация открытых и закрытых ключей ECDH с помощью .Net C#

#c# #.net #cryptography #bouncycastle

#c# #.net #криптография #bouncycastle

Вопрос:

Я переношу код nodejs на .Net и я застрял в этой части, где мне нужно сгенерировать открытые и закрытые ключи.

Код Javascript:

 const dh = crypto.createECDH('prime256v1');
let privk = dh.getPrivateKey();
let pubk = dh.getPublicKey();
 

Я попробовал то же самое с .Net C # с

 var ecdh = new ECDiffieHellmanCng(CngKey.Create(CngAlgorithm.ECDiffieHellmanP256, null, new CngKeyCreationParameters { ExportPolicy = CngExportPolicies.AllowPlaintextExport }));

var privateKey = ecdh.Key.Export(CngKeyBlobFormat.EccPrivateBlob);
var publickey = ecdh.Key.Export(CngKeyBlobFormat.EccPublicBlob);
 

Однако, когда я попытался обменять эти ключи, сгенерированные с помощью C #, на сервер Google FCM, я получил ошибку недопустимого аргумента. Когда я копирую сгенерированный массив byte[] из nodejs в .Чистый код C # в качестве констант работает. Очевидно, что сгенерированные ключи не соответствуют требованиям сервера. Поскольку я работаю с недокументированным интерфейсом, я не могу сказать, почему ключи не принимаются. Я вижу, что ключи, сгенерированные с помощью nodejs, имеют длину 32 байта для закрытого ключа и 65 байт для открытого ключа. Ключи, сгенерированные на C #, имеют длину 140 и 96 байт. Как сгенерировать ключи на C #, чтобы они соответствовали свойствам ключей в nodejs?

Комментарии:

1. Ключи c #, вероятно, добавляют дополнение. Заполнение добавляет случайные биты в конец ключа, поэтому реальная длина ключа не определяется. См. docs.microsoft.com/en-us/dotnet/api /…

Ответ №1:

Я смог решить свою проблему, используя Bouncy Castle

             ECKeyPairGenerator gen = new ECKeyPairGenerator("ECDH");
            SecureRandom secureRandom = new SecureRandom();
            X9ECParameters ecp = NistNamedCurves.GetByName("P-256");
            ECDomainParameters ecSpec = new ECDomainParameters(ecp.Curve, ecp.G, ecp.N, ecp.H, ecp.GetSeed());
            ECKeyGenerationParameters ecgp = new ECKeyGenerationParameters(ecSpec, secureRandom);
            gen.Init(ecgp);
            AsymmetricCipherKeyPair eckp = gen.GenerateKeyPair();

            ECPublicKeyParameters ecPub = (ECPublicKeyParameters)eckp.Public;
            ECPrivateKeyParameters ecPri = (ECPrivateKeyParameters)eckp.Private;

            byte[] publicKeyBytes = ecPub.Q.GetEncoded();
 

Ответ №2:

Вы можете использовать ECDiffieHellman для шифрования сообщений. У вас есть два варианта: статический-статический ECDH и статический-эфемерный ECDH:

Для статически-статического ECDH получателю необходимо будет знать открытый ключ отправителя (это может быть или не быть опцией в вашем приложении). У вас также должны быть некоторые данные, уникальные для этого сообщения (это может быть серийный номер, который вы получаете откуда-то еще в протоколе или строке базы данных, или что-то еще, или это может быть одноразовый номер). Затем вы используете ECDH для генерации секретного ключа и использования его для шифрования ваших данных. Это даст вам желаемую длину зашифрованных данных в 16 байт, но она не является полностью асимметричной: шифровальщик также может расшифровывать сообщения (опять же: это может быть или не быть проблемой в вашем приложении).

Статический-эфемерный немного отличается: здесь шифровальщик генерирует временную (эфемерную) пару ключей EC. Затем он использует эту пару ключей вместе с открытым ключом получателя для генерации секретного ключа, который можно использовать для шифрования данных. Наконец, он отправляет открытый ключ пары эфемерных ключей получателю вместе с зашифрованными данными. Это может лучше вписаться в ваше приложение, но полные зашифрованные данные теперь будут 2*32 16 = 80 байт с использованием ECDH-256 и AES (как отмечает Грегс, вы можете сэкономить 32 байта, отправив только координату x открытого ключа, но я в это не верю.NET предоставляет функциональность для пересчета координаты y).

Вот небольшой класс, который будет выполнять статический-статический ECDH:

 public static class StaticStaticDiffieHellman
{
  private static Aes DeriveKeyAndIv(ECDiffieHellmanCng privateKey, ECDiffieHellmanPublicKey publicKey, byte[] nonce)
  {
    privateKey.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
    privateKey.HashAlgorithm = CngAlgorithm.Sha256;
    privateKey.SecretAppend = nonce;
    byte[] keyAndIv = privateKey.DeriveKeyMaterial(publicKey);
    byte[] key = new byte[16];
    Array.Copy(keyAndIv, 0, key, 0, 16);
    byte[] iv = new byte[16];
    Array.Copy(keyAndIv, 16, iv, 0, 16);

    Aes aes = new AesManaged();
    aes.Key = key;
    aes.IV = iv;
    aes.Mode = CipherMode.CBC;
    aes.Padding = PaddingMode.PKCS7;

    return aes;
  }

  public static byte[] Encrypt(ECDiffieHellmanCng privateKey, ECDiffieHellmanPublicKey publicKey, byte[] nonce, byte[] data){
    Aes aes = DeriveKeyAndIv(privateKey, publicKey, nonce);
    return aes.CreateEncryptor().TransformFinalBlock(data, 0, data.Length);
  }

  public static byte[] Decrypt(ECDiffieHellmanCng privateKey, ECDiffieHellmanPublicKey publicKey, byte[] nonce, byte[] encryptedData){
    Aes aes = DeriveKeyAndIv(privateKey, publicKey, nonce);
    return aes.CreateDecryptor().TransformFinalBlock(encryptedData,0, encryptedData.Length);
  }
}

// Usage:

ECDiffieHellmanCng key1 = new ECDiffieHellmanCng();    
ECDiffieHellmanCng key2 = new ECDiffieHellmanCng();

byte[] data = Encoding.UTF8.GetBytes("TestTestTestTes");
byte[] nonce = Encoding.UTF8.GetBytes("whatever");

byte[] encryptedData = StaticStaticDiffieHellman.Encrypt(key1, key2.PublicKey, nonce, data);

Console.WriteLine(encryptedData.Length); // 16

byte[] decryptedData = StaticStaticDiffieHellman.Decrypt(key2, key1.PublicKey, nonce, encryptedData);

Console.WriteLine(Encoding.UTF8.GetString(decryptedData));