Search code examples
c#encryptionpublic-key-encryption

How do I specify a Bouncy Castle public key from a Byte array?


I need to implement key exchange through an I2C interface with a microcontoller, with the keys transferred byte by byte. I am working in Visual Studio 2022, C#. Because I need access to the Secret Agreement (ECDH), I can't use the Visual Studio encryption library, so have turned to Bouncy Castle. I am able to get the two parts of the public key into Byte arrays to send to the micro - example code shown - but have not been able to figure out how to put the public key received from the micro into a Bouncy Castle public key object to use in generating a secret agreement.

        public void TestBC()
        {
            String SecretString = "";
            int j;

            AsymmetricKeyPair<AsymmetricECPublicKey, AsymmetricECPrivateKey> InitiatorKey = GenerateECDHKeyPair();
            AsymmetricKeyPair<AsymmetricECPublicKey, AsymmetricECPrivateKey> RecipientKey = GenerateECDHKeyPair();
            String RecipPublicKeyX = RecipientKey.PublicKey.W.XCoord.ToString();
            String RecipPublicKeyY = RecipientKey.PublicKey.W.YCoord.ToString();
            MessageBox.Show(RecipPublicKeyW + "\r\n" + RecipPublicKeyX + "\r\n" + RecipPublicKeyY);
            Byte[] SecretAgreement = GetSecretAgreement(InitiatorKey.PrivateKey, RecipientKey.PublicKey);
            Byte[] publicX = ConvertHexStringToByteArray(RecipPublicKeyX);
            Byte[] publicY = ConvertHexStringToByteArray(RecipPublicKeyY);

            for(j=0; j<SecretAgreement.Length; j++)
                SecretString += String.Format("{0:X2}",SecretAgreement[j]);
            MessageBox.Show(SecretString);
        }

        public AsymmetricKeyPair<AsymmetricECPublicKey, AsymmetricECPrivateKey> GenerateECDHKeyPair()
        {
            FipsEC.KeyGenerationParameters KeyGenParameters = new FipsEC.KeyGenerationParameters(FipsEC.DomainParams.P256).For(FipsEC.Cdh);
            FipsEC.KeyPairGenerator KpGen = CryptoServicesRegistrar.CreateGenerator(KeyGenParameters, new Org.BouncyCastle.Security.SecureRandom());
            return KpGen.GenerateKeyPair();
        }

        public static byte[] GetSecretAgreement(AsymmetricECPrivateKey InitiatorPrivate, AsymmetricECPublicKey RecipientPublic)
        {
            IAgreementCalculatorService agreeFact = CryptoServicesRegistrar.CreateService(InitiatorPrivate);
            IAgreementCalculator<FipsEC.AgreementParameters> agreement = agreeFact.CreateAgreementCalculator(FipsEC.Cdh);
            return agreement.Calculate(RecipientPublic);
        }

        public static byte[] ConvertHexStringToByteArray(string hexString)
        {
            if (hexString.Length % 2 != 0)
            {
                throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, "The hex string cannot have an odd number of digits: {0}", hexString));
            }

            byte[] data = new byte[hexString.Length / 2];
            for (int index = 0; index < data.Length; index++)
            {
                string byteValue = hexString.Substring(index * 2, 2);
                data[index] = byte.Parse(byteValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture);
            }

            return data;
        }

Visual Studio intellisense does not show any constructors, properties, or methods for the AsymmetricECPublicKey object that will allow me to put in the pbulic key from a Byte array (or string). And the agreement calculator appears to require a public key object. I found a couple of similar questions already posted, but they did not help me. Does anyone know how to use a Byte array or string to do this?


Solution

  • Preliminary note: The question and answer refer to the C#/BouncyCastle FIPS version and not the regular C#/BouncyCastle version.

    If the public key is given as raw x and y coordinates, it should first be converted to the uncompressed format. The uncompressed format is given by the concatenation of a leading 0x04 byte, the x coordinate and the y coordinate. The last two are 32 bytes each for P-256 (if the x coordinate is smaller, it must be padded from the front with 0x00 to 32 bytes; the same applies to the y coordinate):

    Byte[] publicX = ...;
    Byte[] publicY = ...;
    Byte[] uncompressedKey = new byte[1 + 32 + 32];  // pad too short values (i.e. values < 32 bytes) with leading 0x00
    uncompressedKey[0] = 0x04;
    Buffer.BlockCopy(publicX, 0, uncompressedKey, 1 + (32 - publicX.Length), publicX.Length);          
    Buffer.BlockCopy(publicY, 0, uncompressedKey, 1 + 32 + (32 - publicY.Length), publicY.Length);
    

    The uncompressed key can then be imported as follows:

    FipsEC.KeyGenerationParameters keyGenParameters = new FipsEC.KeyGenerationParameters(FipsEC.DomainParams.P256).For(FipsEC.Cdh);
    AsymmetricECPublicKey publicKey = new AsymmetricECPublicKey(FipsEC.Alg, KeyGenParameters.DomainParameters, KeyGenParameters.DomainParameters.Curve.DecodePoint(uncompressedKey));
    

    For completeness: It is also possible to convert the key to the compressed format and import it. The compressed format is the concatenation of a leading 0x02 byte (if y is even) or 0x03 byte (if y is odd) and the x coordinate (with this information the public key is uniquely determined, since from this information the y coordinate can be reconstructed).