31

I have to encrypt, store and then later decrypt large files. What is the best way of doing that? I heard RSA encryption is expensive and was advised to use RSA to encrypt an AES key and then use the AES key to encrypt the large files. Any suggestions with example will be great.

1

5 Answers 5

19

One organism's large is another's petite, though we all know expensive when we see it. Wink, wink.

Try benchmarking something like the following in your environment and see where you're at:

EDIT 2/13/2012: The code has been updated as I've become (imperceptibly) smarter and also noticed a few cut'n'paste errors that had crept in. Mea culpa.

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

...

    // Rfc2898DeriveBytes constants:
    public readonly byte[] salt = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; // Must be at least eight bytes.  MAKE THIS SALTIER!
    public const int iterations = 1042; // Recommendation is >= 1000.

    /// <summary>Decrypt a file.</summary>
    /// <remarks>NB: "Padding is invalid and cannot be removed." is the Universal CryptoServices error.  Make sure the password, salt and iterations are correct before getting nervous.</remarks>
    /// <param name="sourceFilename">The full path and name of the file to be decrypted.</param>
    /// <param name="destinationFilename">The full path and name of the file to be output.</param>
    /// <param name="password">The password for the decryption.</param>
    /// <param name="salt">The salt to be applied to the password.</param>
    /// <param name="iterations">The number of iterations Rfc2898DeriveBytes should use before generating the key and initialization vector for the decryption.</param>
    public void DecryptFile(string sourceFilename, string destinationFilename, string password, byte[] salt, int iterations)
    {
        AesManaged aes = new AesManaged();
        aes.BlockSize = aes.LegalBlockSizes[0].MaxSize;
        aes.KeySize = aes.LegalKeySizes[0].MaxSize;
        // NB: Rfc2898DeriveBytes initialization and subsequent calls to   GetBytes   must be eactly the same, including order, on both the encryption and decryption sides.
        Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(password, salt, iterations);
        aes.Key = key.GetBytes(aes.KeySize / 8);
        aes.IV = key.GetBytes(aes.BlockSize / 8);
        aes.Mode = CipherMode.CBC;
        ICryptoTransform transform = aes.CreateDecryptor(aes.Key, aes.IV);

        using (FileStream destination = new FileStream(destinationFilename, FileMode.CreateNew, FileAccess.Write, FileShare.None))
        {
            using (CryptoStream cryptoStream = new CryptoStream(destination, transform, CryptoStreamMode.Write))
            {
                try
                {
                    using (FileStream source = new FileStream(sourceFilename, FileMode.Open, FileAccess.Read, FileShare.Read))
                    {
                        source.CopyTo(cryptoStream);
                    }
                }
                catch (CryptographicException exception)
                {
                    if (exception.Message == "Padding is invalid and cannot be removed.")
                        throw new ApplicationException("Universal Microsoft Cryptographic Exception (Not to be believed!)", exception);
                    else
                        throw;
                }
            }
        }
    }

    /// <summary>Encrypt a file.</summary>
    /// <param name="sourceFilename">The full path and name of the file to be encrypted.</param>
    /// <param name="destinationFilename">The full path and name of the file to be output.</param>
    /// <param name="password">The password for the encryption.</param>
    /// <param name="salt">The salt to be applied to the password.</param>
    /// <param name="iterations">The number of iterations Rfc2898DeriveBytes should use before generating the key and initialization vector for the decryption.</param>
    public void EncryptFile(string sourceFilename, string destinationFilename, string password, byte[] salt, int iterations)
    {
        AesManaged aes = new AesManaged();
        aes.BlockSize = aes.LegalBlockSizes[0].MaxSize;
        aes.KeySize = aes.LegalKeySizes[0].MaxSize;
        // NB: Rfc2898DeriveBytes initialization and subsequent calls to   GetBytes   must be eactly the same, including order, on both the encryption and decryption sides.
        Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(password, salt, iterations);
        aes.Key = key.GetBytes(aes.KeySize / 8);
        aes.IV = key.GetBytes(aes.BlockSize / 8);
        aes.Mode = CipherMode.CBC;
        ICryptoTransform transform = aes.CreateEncryptor(aes.Key, aes.IV);

        using (FileStream destination = new FileStream(destinationFilename, FileMode.CreateNew, FileAccess.Write, FileShare.None))
        {
            using (CryptoStream cryptoStream = new CryptoStream(destination, transform, CryptoStreamMode.Write))
            {
                using (FileStream source = new FileStream(sourceFilename, FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    source.CopyTo(cryptoStream);
                }
            }
        }
    }
4
  • This example only encrypts/decrypts using AES. Do you mean to say try the similar thing with RSA and profile both?
    – kalrashi
    Commented Feb 13, 2012 at 8:42
  • It's up to you. Your question didn't explain the perceived threat(s) against the data, the size of the files, any network transfers or exposure involved, available processing resources, ... . In this case it sounds like you would do well to collect at least a little benchmark data on the cost of encryption in your environment and work from there. Others have touched on public vs. private key cryptography and the relative strength of different algorithms.
    – HABO
    Commented Feb 13, 2012 at 15:18
  • 2
    To avoid the "Padding is invalid and cannot be removed." exception, the encrypting side has to call source.FlushFinalBlock() after source.CopyTo(crytoStream); else you would actually mis the last block.
    – DipSwitch
    Commented May 31, 2016 at 15:30
  • 2
    Note: The IV SHOULD NOT be derived from the password. stackoverflow.com/a/8041580/887092. Perhaps "MUST NOT". Commented May 18, 2020 at 6:43
16

This may help

/// Encrypts a file using Rijndael algorithm.
///</summary>
///<param name="inputFile"></param>
///<param name="outputFile"></param>
private void EncryptFile(string inputFile, string outputFile)
{

    try
    {
        string password = @"myKey123"; // Your Key Here
        UnicodeEncoding UE = new UnicodeEncoding();
        byte[] key = UE.GetBytes(password);

        string cryptFile = outputFile;
        FileStream fsCrypt = new FileStream(cryptFile, FileMode.Create);

        RijndaelManaged RMCrypto = new RijndaelManaged();

        CryptoStream cs = new CryptoStream(fsCrypt,
            RMCrypto.CreateEncryptor(key, key),
            CryptoStreamMode.Write);

        FileStream fsIn = new FileStream(inputFile, FileMode.Open);

        int data;
        while ((data = fsIn.ReadByte()) != -1)
            cs.WriteByte((byte)data);


        fsIn.Close();
        cs.Close();
        fsCrypt.Close();
    }
    catch
    {
        MessageBox.Show("Encryption failed!", "Error");
    }
}

///
/// Decrypts a file using Rijndael algorithm.
///</summary>
///<param name="inputFile"></param>
///<param name="outputFile"></param>
private void DecryptFile(string inputFile, string outputFile)
{

    {
        string password = @"myKey123"; // Your Key Here

        UnicodeEncoding UE = new UnicodeEncoding();
        byte[] key = UE.GetBytes(password);

        FileStream fsCrypt = new FileStream(inputFile, FileMode.Open);

        RijndaelManaged RMCrypto = new RijndaelManaged();

        CryptoStream cs = new CryptoStream(fsCrypt,
            RMCrypto.CreateDecryptor(key, key),
            CryptoStreamMode.Read);

        FileStream fsOut = new FileStream(outputFile, FileMode.Create);

        int data;
        while ((data = cs.ReadByte()) != -1)
            fsOut.WriteByte((byte)data);

        fsOut.Close();
        cs.Close();
        fsCrypt.Close();

    }
}

source: http://www.codeproject.com/Articles/26085/File-Encryption-and-Decryption-in-C

1
  • 6
    This code has some serious problems: a) It doesn't work if the password doesn't have sufficient length, but the problem is that a password is usually of low entropy and therefore not directly usable as a key. b) Using the key as the IV is makes this deterministic encryption and therefore not semantically secure. The IV must be randomly generated. It doesn't have to be secret, but only unpredictable, so it can be simply written in front of the ciphertext and used during decryption. c) No authentication! The ciphertext may be changed without the recipient detecting this.
    – Artjom B.
    Commented Apr 24, 2016 at 18:38
11

Generally the strategy you have described is used when data will be encrypted on one machine (like a server) and then decrypted by another machine (client). The server will encrypt the data using symmetric key encryption (for performance) with a newly generated key and encrypt this symmetric key with a public key (matching a client's private key). The server sends the client both the encrypted data and the encrypted symmetric key. The client can decrypt the symmetric key with it's private key and then use this symmetric key for decrypting the data. If you are encrypting and decrypting the data on the same machine it may not make sense to use both RSA and AES as you would not be trying to pass the encryption key to another machine.

2
  • 1
    Thanks, but my problem is slightly different. I want to encrypt the large file(s) and then store in and the encryption (i.e. security) is needed in case the storage gets compromised. For client server communication I send in the AES key encrypted with RSA, how does it transalte in my scenario? Do I store the AES key as part of the pre-pending or appending bytes of the symmetric key encrypted blob?
    – kalrashi
    Commented Feb 13, 2012 at 8:28
  • A great example is actually found on the X509Certificate2 MSDN Doc (I was looking in RSACryptoProvider and related classes):
    – kalrashi
    Commented Feb 13, 2012 at 20:49
4

Like you heard asymmetric cryptography, like RSA, is much slower than symmetric cryptography (e.g. AES) but it does have it's advantages (simpler key management, e.g. a single private key to protect).

The key (pun intended) is to use the advantages of both (private key of asymmetric and speed of symmetric) while ignoring the inconvenience of the other (many secret keys and slow speed).

You can do this by using RSA once per file (no huge performance impact) to encrypt a (symmetric) secret key that is used to encrypt (much faster) your large file. This *wrapping of the symmetric key allows you to only manage a single, private key.

Here's a link to my old (but still true) blog post that gives an example to do this using C# and the .NET framework (Microsoft of Mono).

1
  • Thanks. The example was quite helpful.
    – kalrashi
    Commented Feb 13, 2012 at 8:38
3

RSA

It's true asymmetric cryptography (RSA, ECC, etc.) is slower than symmetric (AES, ChaCha20, etc). RSA and others are great for securing a random symmetric key (or establishing one). AES and others are great for efficient encryption, used along with integrity checking (HMAC).

Importantly, mature symmetric ciphers don't have any known theoretical weakness. Unless your attackers has the symmetric key, the encryption cannot be broken. Currently, all mature asymmetric cryptography (RSA, ECC) are based on mathematical properties that are vulnerable to being cracked by a future Quantum Computer (if it ever comes).

Also, handling of public/private keys becomes a problem. It's simple for a human to remember a password - their brain cannot be hacked. With public/private keys, they need to be stored somewhere. Particularly the private key is sensitive. Computers have TDM components that can create and store public/private keys separate to the CPU. This is very complicated to use.

So with that in mind, RSA should only be used if and when it's absolutely necessary.

AES

Here is a complete version I wrote recently, that returns the wrapping streamer, so you can use it however you need.

Also, this method generates IV from random generator instead of the password digestor. This is best practice, for example 7z does this - see https://crypto.stackexchange.com/questions/61945/is-it-ok-to-transmit-an-iv-as-a-custom-http-header. The IV is included in the header for the output.

Usage:

void Save()
{
    var encryptedFilePath = Directory.GetCurrentDirectory() + "\\data.bin.aes";
    using(var fileStream = File.Create(encryptedFilePath))
    {
        using (var cryptoStream = Security.FileEncryptor.CreateEncryptor(fileStream, passwordHere))
        {
            var formatter = new BinaryFormatter();
            formatter.Serialize(cryptoStream, myObject);
            cryptoStream.Flush();
        }

    }
}

void Load()
{
    var encryptedFilePath = Directory.GetCurrentDirectory() + "\\data.bin.aes";

    using(var fileStream = File.Open(encryptedFilePath, FileMode.Open))
    {
        using (var cryptoStream = Security.FileEncryptor.CreateDecryptor(fileStream, passwordHere))
        {
            var formatter = new BinaryFormatter();
            var myObject = (myObjectType)formatter.Deserialize(cryptoStream);
        }
    }
}

Utility:

using System.IO;
using System.Security.Cryptography;
using System;

namespace Security
{

    class FileEncryptor
    {
        public static Stream CreateEncryptor(Stream source, string password)
        {
            byte[] SaltBytes = new byte[16];
            RandomNumberGenerator.Fill(SaltBytes); //RandomNumberGenerator is used for .Net Core 3

            AesManaged aes = new AesManaged();
            aes.BlockSize = aes.LegalBlockSizes[0].MaxSize;
            aes.KeySize = aes.LegalKeySizes[0].MaxSize;

            Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(password, SaltBytes, iterations);
            aes.Key = key.GetBytes(aes.KeySize / 8);

            byte[] IVBytes = new byte[aes.BlockSize / 8];
            RandomNumberGenerator.Fill(IVBytes); //RandomNumberGenerator is used for .Net Core 3
            aes.IV = IVBytes;

            aes.Mode = CipherMode.CBC;
            ICryptoTransform transform = aes.CreateEncryptor(aes.Key, aes.IV);

            //Store/Send the Salt and IV - this can be shared. It's more important that it's very random, than being private.
            source.WriteByte((byte)SaltBytes.Length);
            source.Write(SaltBytes, 0, SaltBytes.Length);
            source.WriteByte((byte)IVBytes.Length);
            source.Write(IVBytes, 0, IVBytes.Length);
            source.Flush();

            var cryptoStream = new CryptoStream(source, transform, CryptoStreamMode.Write);
            return cryptoStream;
        }

        public static Stream CreateDecryptor(Stream source, string password)
        {
            var ArrayLength = source.ReadByte();
            if (ArrayLength == -1) throw new Exception("Salt length not found");
            byte[] SaltBytes = new byte[ArrayLength];
            var readBytes = source.Read(SaltBytes, 0, ArrayLength);
            if (readBytes != ArrayLength) throw new Exception("No support for multiple reads");

            ArrayLength = source.ReadByte();
            if (ArrayLength == -1) throw new Exception("Salt length not found");
            byte[] IVBytes = new byte[ArrayLength];
            readBytes = source.Read(IVBytes, 0, ArrayLength);
            if (readBytes != ArrayLength) throw new Exception("No support for multiple reads");

            AesManaged aes = new AesManaged();
            aes.BlockSize = aes.LegalBlockSizes[0].MaxSize;
            aes.KeySize = aes.LegalKeySizes[0].MaxSize;
            aes.IV = IVBytes;

            Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(password, SaltBytes, iterations);
            aes.Key = key.GetBytes(aes.KeySize / 8);

            aes.Mode = CipherMode.CBC;
            ICryptoTransform transform = aes.CreateDecryptor(aes.Key, aes.IV);

            var cryptoStream = new CryptoStream(source, transform, CryptoStreamMode.Read);
            return cryptoStream;
        }

        public const int iterations = 1042; // Recommendation is >= 1000.
    }
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.