Issues with RSA Encryption and Decryption in Crypter Application

222 Views Asked by At

I'm currently working on a cryptographic application for educational purposes, and I'm encountering some issues with RSA encryption and decryption. The application acts as a crypter, allowing encryption and decryption of files. However, during the decryption process, I'm encountering the following error:

Unhandled Exception: System.Security.Cryptography.CryptographicException: Key does not exist.

Here's an overview of the main code:

public static class Crypto
    {
        private const int KeySize = 256;
        private const int IvSize = 128;
        private const int SaltSize = 64;
        private const int Iterations = 50000;

        private static Rfc2898DeriveBytes GenerateKey(string password, byte[] salt, int size)
        {
            return new Rfc2898DeriveBytes(password, salt, Iterations);
        }

        private static byte[] SerializeRSAParameters(RSAParameters p)
        {
            using (var ms = new MemoryStream())
            {
                var formatter = new BinaryFormatter();
                formatter.Serialize(ms, p);
                return ms.ToArray();
            }
        }

        private static RSAParameters DeserializeRSAParameters(byte[] p)
        {
            using (var ms = new MemoryStream(p))
            {
                var formatter = new BinaryFormatter();
                return (RSAParameters)formatter.Deserialize(ms);
            }
        }

        public static byte[] EncryptData(byte[] data, string password)
        {
            var salt = new byte[SaltSize / 8];
            using (var rng = new RNGCryptoServiceProvider())
            {
                rng.GetBytes(salt);
            }

            var keyGenerator = GenerateKey(password, salt, KeySize / 8);
            var keyBytes = keyGenerator.GetBytes(KeySize / 8);

            using (var rsa = new RSACng())
            {
                var encryptedKey = rsa.Encrypt(keyBytes, RSAEncryptionPadding.OaepSHA256);
                var rsaParamsBytes = SerializeRSAParameters(rsa.ExportParameters(true));
                Console.WriteLine("RSA params byte length in encryption: " + rsaParamsBytes.Length);

                var iv = new byte[IvSize / 8];
                using (var rng = new RNGCryptoServiceProvider())
                {
                    rng.GetBytes(iv);
                }

                using (var aes = new AesManaged())
                {
                    aes.Mode = CipherMode.CBC;
                    aes.Padding = PaddingMode.PKCS7;

                    using (var encryptor = aes.CreateEncryptor(keyBytes, iv))
                    using (var ms = new MemoryStream())
                    using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
                    {
                        cs.Write(data, 0, data.Length);
                        cs.FlushFinalBlock();

                        var encryptedData = ms.ToArray();
                        var encryptedKeyLength = BitConverter.GetBytes(encryptedKey.Length);
                        var result = new byte[0].Concat(salt).Concat(iv).Concat(encryptedKeyLength).Concat(encryptedKey).Concat(BitConverter.GetBytes(rsaParamsBytes.Length)).Concat(rsaParamsBytes).Concat(encryptedData).ToArray();

                        Console.WriteLine("Encryption key hash: " + BitConverter.ToString(SHA256.Create().ComputeHash(keyBytes)));
                        return result;
                    }
                }
            }
        }

        public static byte[] DecryptData(byte[] data, string password)
        {
            var salt = data.Take(SaltSize / 8).ToArray();
            var iv = data.Skip(SaltSize / 8).Take(IvSize / 8).ToArray();
            var encryptedKeyLength = BitConverter.ToInt32(data.Skip((SaltSize + IvSize) / 8).Take(4).ToArray(), 0);
            var encryptedKey = data.Skip((SaltSize + IvSize) / 8 + 4).Take(encryptedKeyLength).ToArray();
            var rsaParamsLength = BitConverter.ToInt32(data.Skip((SaltSize + IvSize) / 8 + 4 + encryptedKeyLength).Take(4).ToArray(), 0);
            var rsaParamsBytes = data.Skip((SaltSize + IvSize) / 8 + 4 + encryptedKeyLength + 4).Take(rsaParamsLength).ToArray();
            Console.WriteLine("RSA params byte length in decryption: " + rsaParamsBytes.Length);

            using (var rsa = new RSACng())
            {
                rsa.ImportParameters(DeserializeRSAParameters(rsaParamsBytes));
                var keyBytes = rsa.Decrypt(encryptedKey, RSAEncryptionPadding.OaepSHA256);

                Console.WriteLine("Decryption key hash: " + BitConverter.ToString(SHA256.Create().ComputeHash(keyBytes)));

                var actualData = data.Skip((SaltSize + IvSize) / 8 + 4 + encryptedKeyLength + 4 + rsaParamsLength).ToArray();

                using (var aes = new AesManaged())
                {
                    aes.Mode = CipherMode.CBC;
                    aes.Padding = PaddingMode.PKCS7;

                    using (var decryptor = aes.CreateDecryptor(keyBytes, iv))
                    using (var ms = new MemoryStream(actualData))
                    using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
                    {
                        var decryptedData = new byte[actualData.Length];
                        var bytesRead = cs.Read(decryptedData, 0, decryptedData.Length);

                        return decryptedData.Take(bytesRead).ToArray();
                    }
                }
            }
        }
    }
1

There are 1 best solutions below

0
On

First of all, binary formatters are very obsolete, and RSAParameters are not marked for serialization either... Try instead:

private static byte[] SerializeRSAParameters(RSAParameters p)
{
    string json = JsonSerializer.Serialize(p, new JsonSerializerOptions() { IncludeFields = true, });
    return Encoding.UTF8.GetBytes(json);
}

private static RSAParameters DeserializeRSAParameters(byte[] p)
{
    var parameters = JsonSerializer.Deserialize<RSAParameters>(Encoding.UTF8.GetString(p), new JsonSerializerOptions() { IncludeFields = true, });
    return parameters;
}

Secondly, you're not reading all bytes from the crypto stream. Try instead in DecryptData:

            using (var plain = new MemoryStream())
            {
                cs.CopyTo(plain);
                return plain.ToArray();
            }

Also, but maybe because it's an educational project it's by design - but you do add the private key to encrypted data! That's not so good from a security standpoint...

Finally, your actual issue. With the above fixes the code seems to work for me... I have the following main (and verified in the debugger the data is correct):

byte[] encrypted = Crypto.EncryptData(new byte[100], "password");
byte[] decrypted = Crypto.DecryptData(encrypted, "password");

Console.WriteLine($"Length of decrypted is {decrypted.Length}");

I have not analyzed the code further. Could you include the full stack trace of your exception?