Search code examples
c#.netcryptographyx509ecdsa

How to import public key From X.509 SubjectPublicKeyInfo format?


I serialize the ECDiffieHellmanPublicKey key BLOB to a byte array by ToByteArray() method but this method is obsolete.

ECDiffieHellmanPublicKey.ToByteArray() and the associated constructor do not have a consistent and interoperable implementation on all platforms. Use ECDiffieHellmanPublicKey.ExportSubjectPublicKeyInfo() instead.

Microsoft suggest ExportSubjectPublicKeyInfo() method instead. when I replace ToByteArray to ExportSubjectPublicKeyInfo() method CngKey.Import makes an error:

System.Security.Cryptography.CryptographicException: 'The parameter is incorrect.'

How can I fix the code:

    public static void Main()
    {
        var client = new ECDiffieHellmanCng();
        client.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
        client.HashAlgorithm = CngAlgorithm.Sha256;

        // var clientPublicKey = client.PublicKey.ToByteArray();
        var clientPublicKey = client.PublicKey.ExportSubjectPublicKeyInfo();

        var server = new ECDiffieHellmanCng();
        server.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
        server.HashAlgorithm = CngAlgorithm.Sha256;

        // var serverPublicKey = server.PublicKey.ToByteArray();
        var serverPublicKey = server.PublicKey.ExportSubjectPublicKeyInfo();

        var enc = Encrypt(client, serverPublicKey, "Hello");
        Console.WriteLine(Convert.ToBase64String(enc.Item1));

        var dec = Decrypt(server, clientPublicKey, enc.Item2, enc.Item1);
        Console.WriteLine(Encoding.UTF8.GetString(dec));
    }

   public static (byte[], byte[]) Encrypt(ECDiffieHellmanCng client, byte[] serverPublicKey, string plainText)
    {
        var cngKey = CngKey.Import(serverPublicKey, CngKeyBlobFormat.EccPublicBlob);
        var sharedSecret = client.DeriveKeyMaterial(cngKey);

        using var aes = Aes.Create();
        aes.Key = sharedSecret;
        aes.GenerateIV();
        aes.Padding = PaddingMode.PKCS7;
        var plainBytes = Encoding.UTF8.GetBytes(plainText);
        using var encryptor = aes.CreateEncryptor();
        var cipherBytes = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length);

        return (cipherBytes, aes.IV);
    }

    public static byte[] Decrypt(ECDiffieHellmanCng server, byte[] clientPublicKey, byte[] iv, byte[] cipher)
    {
        var cngKey = CngKey.Import(clientPublicKey, CngKeyBlobFormat.EccPublicBlob);
        var sharedSecret = server.DeriveKeyMaterial(cngKey);

        using var aes = Aes.Create();
        aes.Padding = PaddingMode.PKCS7;
        aes.IV = iv;
        aes.Key = sharedSecret;

        using var decryptor = aes.CreateDecryptor();
        var plain = decryptor.TransformFinalBlock(cipher, 0, cipher.Length);

        return plain;
    }

Solution

  • One option is: Instead of CngKey.Import() an ECDiffieHellmanCng() instance is to be created in the Encrypt() and Decrypt() methods.
    Into this instance the passed public SPKI key is imported with ImportSubjectPublicKeyInfo().
    To DeriveKeyMaterial() the Key property of the ECDiffieHellmanCng() instance is to be passed.

    E.g. for Encrypt():

    public static (byte[], byte[]) Encrypt(ECDiffieHellmanCng client, byte[] serverPublicKey, string plainText)
    {
        var server = new ECDiffieHellmanCng();
        server.ImportSubjectPublicKeyInfo(serverPublicKey, out _);
        var sharedSecret = client.DeriveKeyMaterial(server.Key);
        ...
    

    Note that ECDiffieHellmanCng is only supported on Windows, s. here.