1

I have searched for a good plain text file encryption in C# and came across a post on SO and have been using it for a while now.

I don't remember the original post I got the code from; but here's the copy of the original code, slightly modified for my own use:

public static class StringCipher
{
    private const int Keysize = 256;

    private const int DerivationIterations = 1000;

    public static string Encrypt(string plainText, string fileName)
    {
        try
        {
            var saltStringBytes = Generate256BitsOfRandomEntropy();
            var ivStringBytes = Generate256BitsOfRandomEntropy();
            var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
            using (var password = new Rfc2898DeriveBytes(Machine.Udid, saltStringBytes, DerivationIterations))
            {
                var keyBytes = password.GetBytes(Keysize / 8);
                using (var symmetricKey = new RijndaelManaged())
                {
                    symmetricKey.BlockSize = 256;
                    symmetricKey.Mode = CipherMode.CBC;
                    symmetricKey.Padding = PaddingMode.PKCS7;
                    using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes))
                    {
                        using (var memoryStream = new MemoryStream())
                        {
                            using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
                            {
                                cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
                                cryptoStream.FlushFinalBlock();
                                var cipherTextBytes = saltStringBytes;
                                cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray();
                                cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray();
                                memoryStream.Close();
                                cryptoStream.Close();
                                return Convert.ToBase64String(cipherTextBytes);
                            }
                        }
                    }
                }
            }
        }
        catch (Exception exception)
        {
            throw new Exception("Failed to encrypt " + fileName + " - " + exception.Message, exception);
        }
    }

    public static string Decrypt(string cipherText, string fileName)
    {
        try
        {
            var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText);
            var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray();
            var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray();
            var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) * 2).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8) * 2)).ToArray();
            using (var password = new Rfc2898DeriveBytes(Machine.Udid, saltStringBytes, DerivationIterations))
            {
                var keyBytes = password.GetBytes(Keysize / 8);
                using (var symmetricKey = new RijndaelManaged())
                {
                    symmetricKey.BlockSize = 256;
                    symmetricKey.Mode = CipherMode.CBC;
                    symmetricKey.Padding = PaddingMode.PKCS7;
                    using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes))
                    {
                        using (var memoryStream = new MemoryStream(cipherTextBytes))
                        {
                            using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
                            {
                                var plainTextBytes = new byte[cipherTextBytes.Length];
                                var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
                                memoryStream.Close();
                                cryptoStream.Close();
                                return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
                            }
                        }
                    }
                }
            }
        }
        catch (Exception exception)
        {
            throw new Exception("Failed to decrypt " + fileName + " - " + exception.Message, exception);
        }
    }

    private static byte[] Generate256BitsOfRandomEntropy()
    {
        var randomBytes = new byte[32];
        using (var rngCsp = new RNGCryptoServiceProvider())
        {
            rngCsp.GetBytes(randomBytes);
        }
        return randomBytes;
    }
}

Every now and then, my app randomly throws this exception:

Padding is invalid and cannot be removed.

And this is my stack trace:

at System.Security.Cryptography.RijndaelManagedTransform.DecryptData(Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount, Byte[]& outputBuffer, Int32 outputOffset, PaddingMode paddingMode, Boolean fLast)
   at System.Security.Cryptography.RijndaelManagedTransform.TransformFinalBlock(Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount)
   at System.Security.Cryptography.CryptoStream.FlushFinalBlock()
   at System.Security.Cryptography.CryptoStream.Dispose(Boolean disposing)
   at System.IO.Stream.Close()
   at System.IO.Stream.Dispose()
   at DisplayMapper.Lib.StringCipher.Decrypt(String cipherText, String fileName) in C:\Users\Latheesan\Desktop\MyApp\StringCipher.cs:line 82

The line 82 is referring to this:

return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);

I have been reading up on this error and many post point to saying that the same key/password used to encrypt must be used to decrypt etc...

That is what I am doing in my implementation, I use a static password generated from a hash of the cpu/motherboard/hdd serial numbers with some random strings (which comes from Machine.Udid.

So, I am not sure what's causing this error. I haven't been able to re-produce it as the error occurs randomly. Any ideas?

Latheesan
  • 23,247
  • 32
  • 107
  • 201
  • If I was you, I'd adjust to use the `CryptoStream` in write mode rather than read mode. I'm not saying this is the source of the issue, but it makes the implementation easier and avoids issues that *can* throw the exception you are getting. – Luke Joshua Park Jul 01 '18 at 21:57
  • @LukeJoshuaPark write mode for the decrypt function? – Latheesan Jul 01 '18 at 21:58
  • 1
    Yes, sorry, should have clarified what part. The way you structure your code is... odd. You use a using statement and yet still intentionally close the `CryptoStream` and `MemoryStream`. You should fix this. – Luke Joshua Park Jul 01 '18 at 21:59
  • Now the error is happening every time (strange) at this line: `var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);` – Latheesan Jul 01 '18 at 22:01
  • Well, it isn't really that strange. When I said use the `CryptoStream` in write mode I expected you'd realize I also meant to adjust the stream code accordingly :) – Luke Joshua Park Jul 01 '18 at 22:03

0 Answers0