Search code examples
c#encryptioncryptographyaescross-platform

Cross-Platform C# Crypto: Cannot generate right key/Choose right algorithm


I am creating a crypto class to use in both my chat client(Windows, .NET Framework) & server(Linux, .NET Core).

I figured I'd use BouncyCastle since its "Well documented", and since i need cross-platform which default lib doesn't support (CNG classes). So i got the key generation working (Haven't tested it cross-platform yet), but the encryption and decryption is not working dues to invalid key size.

I cant find any docs on this and have been stuck here for a while. Please point out if there's anything I'm doing horribly wrong

(I'm very new to crypto, C#... Well programming in general, been learning for 1 year)

I really hope my code isn't a total mess:

using Org.BouncyCastle.Asn1.Nist;
using Org.BouncyCastle.Asn1.Sec;
using Org.BouncyCastle.Asn1.X9;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Math.EC;
using Org.BouncyCastle.Security;
using System;
using System.IO;
using System.Text;

namespace ChatClient
{
    public class Crypto
    {
        private bool _ready = false;
        private X9ECParameters m_x9EC;

        private ECPublicKeyParameters m_myPubKey;
        private AsymmetricKeyParameter m_myPrivKey;

        private byte[] m_sharedSecret = null;

        public Crypto()
        {
            // Get curve
            m_x9EC = NistNamedCurves.GetByName("P-521");
            ECDomainParameters ecDomain = new ECDomainParameters(m_x9EC.Curve, m_x9EC.G, m_x9EC.N, m_x9EC.H, m_x9EC.GetSeed());

            // Create generator
            ECKeyPairGenerator g = (ECKeyPairGenerator)GeneratorUtilities.GetKeyPairGenerator("ECDH");
            g.Init(new ECKeyGenerationParameters(ecDomain, new SecureRandom()));

            // Generate keypair
            AsymmetricCipherKeyPair keyPair = g.GenerateKeyPair();

            // Set keys
            m_myPubKey = (ECPublicKeyParameters)keyPair.Public;
            m_myPrivKey = keyPair.Private;
        }

        public void GenPrivateKey(byte[] key)
        {
            // Why whould anyone even...
            if (key == null)
                throw new ArgumentNullException();

            // Split up the Base64-Encoded, comma-seperated crypto-coords of the server/client
            string str = Encoding.UTF8.GetString(key);
            string[] elements = str.Split(',');
            if (elements.Length != 2)
                throw new ArgumentException();

            // Generate a key out of the coordinates
            ECPoint point = m_x9EC.Curve.CreatePoint(
                new BigInteger(Convert.FromBase64String(elements[0])),
                new BigInteger(Convert.FromBase64String(elements[1]))
                );

            // Get public key
            ECPublicKeyParameters remotePubKey = new ECPublicKeyParameters("ECDH", point, SecObjectIdentifiers.SecP521r1);

            // Generate shared secret key
            IBasicAgreement aKeyAgree = AgreementUtilities.GetBasicAgreement("ECDH");
            aKeyAgree.Init(m_myPrivKey);
            m_sharedSecret = aKeyAgree.CalculateAgreement(remotePubKey).ToByteArray();

            // Debugging...
            Console.WriteLine("Key: {0}", Convert.ToBase64String(m_sharedSecret));

            // Authentication is done, class can now be used for encryption/decryption
            _ready = true;
        }

        public byte[] GetPublicKey()
        {
            // Assemble the Base64-Encoded, comma-seperated crypto-coords to send to the other client/server
            string str = string.Format(
                "{0},{1}",
                Convert.ToBase64String(m_myPubKey.Q.AffineXCoord.ToBigInteger().ToByteArray()),
                Convert.ToBase64String(m_myPubKey.Q.AffineYCoord.ToBigInteger().ToByteArray())
                );
            
            // Return it
            return Encoding.UTF8.GetBytes(str);
        }

        public byte[] Encrypt(byte[] unencryptedData)
        {
            // Keys need to be generated before we can start encrypting/decrypring, And please dont pass null into here...
            if (!_ready || unencryptedData == null)
                return null;

            using (MemoryStream ms = new MemoryStream())
            {
                using (System.Security.Cryptography.AesManaged cryptor = new System.Security.Cryptography.AesManaged())
                {
                    // Set parameters
                    cryptor.Mode = System.Security.Cryptography.CipherMode.CBC;
                    cryptor.Padding = System.Security.Cryptography.PaddingMode.PKCS7;
                    cryptor.KeySize = 128;
                    cryptor.BlockSize = 128;

                    // Get iv
                    byte[] iv = cryptor.IV;

                    // Encrypt the data
                    using (System.Security.Cryptography.CryptoStream cs = new System.Security.Cryptography.CryptoStream(ms, cryptor.CreateEncryptor(m_sharedSecret, iv), System.Security.Cryptography.CryptoStreamMode.Write))
                        cs.Write(unencryptedData, 0, unencryptedData.Length);
                    
                    // Get stuff that was encrpyted
                    byte[] encryptedContent = ms.ToArray();

                    // Create a new array for the data + iv
                    byte[] result = new byte[iv.Length + encryptedContent.Length];

                    //copy both arrays into one
                    System.Buffer.BlockCopy(iv, 0, result, 0, iv.Length);
                    System.Buffer.BlockCopy(encryptedContent, 0, result, iv.Length, encryptedContent.Length);

                    // Aaaand return it
                    return result;
                }
            }
            
            return null;
        }

        public byte[] Decrypt(byte[] encryptedData)
        {
            // Keys need to be generated before we can start encrypting/decrypring, And please dont pass null into here...
            if (!_ready || encryptedData == null)
                return null;

            // New arrays for iv and data
            byte[] iv = new byte[16];
            byte[] dat = new byte[encryptedData.Length - iv.Length];

            // Get iv and data
            System.Buffer.BlockCopy(encryptedData, 0, iv, 0, iv.Length);
            System.Buffer.BlockCopy(encryptedData, iv.Length, dat, 0, dat.Length);

            using (MemoryStream ms = new MemoryStream())
            {
                using (System.Security.Cryptography.AesManaged cryptor = new System.Security.Cryptography.AesManaged())
                {
                    // Set parameters
                    cryptor.Mode = System.Security.Cryptography.CipherMode.CBC;
                    cryptor.Padding = System.Security.Cryptography.PaddingMode.PKCS7;
                    cryptor.KeySize = 128;
                    cryptor.BlockSize = 128;

                    // Decrypt the data
                    using (System.Security.Cryptography.CryptoStream cs = new System.Security.Cryptography.CryptoStream(ms, cryptor.CreateDecryptor(m_sharedSecret, iv), System.Security.Cryptography.CryptoStreamMode.Write))
                        cs.Write(encryptedData, 0, encryptedData.Length);
                    
                    // Aaaand return it
                    return ms.ToArray();
                }
            }
            
            return null;
        }
    }
}

Where this part:

// Encrypt the data
using (System.Security.Cryptography.CryptoStream cs = new System.Security.Cryptography.CryptoStream(ms, cryptor.CreateEncryptor(m_sharedSecret, iv), System.Security.Cryptography.CryptoStreamMode.Write))

Throws this error:

Exception thrown: 'System.ArgumentException' in System.Core.dll
An unhandled exception of type 'System.ArgumentException' occurred in System.Core.dll
The specified key is not a valid size for this algorithm.

Solution

  • Closing this question, it was answered by Topaco in the comments

    In the code the shared secret is used directly as symmetric key, which causes the error due to the different sizes. Instead, the symmetric key (with the appropriate size) is usually derived from the shared secret, here and here.