Rijndael Cryptography Function - Key/IV/Salt Setup

2019-07-27 05:34发布

问题:

I'm working on a simple utility to encrypt files as a learning experience. Everything seems to be working fine, but I'm wondering if I have setup the Key/IV/Salt data securely.

I have noticed that when it comes to cryptography most people seem to envision a working environment that is loaded with malware manned by a wizard remotely ready to dive through the memory of a running application/page file to get at these secure files.

Let's pretend that you're on a clean machine and you encrypt some files and turn off your computer. What I want to know is whether or not someone can take your hard drive and retrieve the contents of the files using the code I have proposed.

The attack vector I am most concerned with is ensuring that the page files/file caches are inaccessible. I also want to make sure that the Key/IV system used is not going to make a rainbow table/hash based attack feasible.

Entering the Password:

The password is entered using a text box with the passwordchar value set to true.

I'm not really concerned with the string being in memory as long as it is properly removed after the encryption. I read that using SecureString is kind of pointless at this point because if you have malware on your computer already, you could just as easily have a keylogger on there which renders everything else useless.

private static string salt = "02341235XadfaDADFexA8932F7Dz3J3X";

I salt the password using a hard coded 32 character string. (The above string is just an example.)

To get at it, it will require someone to decompile/view the .exe file itself with a hex editor (something that I know if very easy to do, but an extra step nonetheless).

I have considered making this salt editable, but I'm not sure how I could securely store it. I think it's a little ridiculous to encrypt your salt because then you will have the same issue etc, so just leaving it as a hard coded string inside the exe itself seems to make the most sense to me.

The way this works is if you decide to make your password "thepassword", it is actually saved as "thepasswordfaDADFexA8932F7Dz3J3X".

The main key here is that you always have a 32 character password, regardless of what you enter.

The Key and IV:

The Key and IV are also salted as follows. This is what I wanted to get some input on, because to be honest I'm not entirely sure what it's doing:

UnicodeEncoding UE = new UnicodeEncoding();
byte[] keysalt = UE.GetBytes("Xjafe231x42X423XadXCadfkhjeAdS");        //Another string of random characters hard coded in the exe
byte[] IVSodium = UE.GetBytes("83Xkda7l78Dkx85KdJazppoqq6SaxDs");        //Another string of random characters hard coded in the exe
byte[] key = new Rfc2898DeriveBytes(password, keysalt).GetBytes(32);   //Derive the key using the password and salt
byte[] IV = new Rfc2898DeriveBytes(password, IVSodium).GetBytes(16);    //Derive the IV using the password and salt

My main concern here is that the IV is based on the key. Again, I'm not sure if this will cause any issues and I was hoping you guys could let me know if there are issues, what they are.

Also, is this another scenario where hard coding the salt is a bad practice? Should this be stored in the encrypted file, and if so, does it really make it more secure? Should I make this editable as well?

The crypto streams are setup using the using keyword:

using (FileStream fsCrypt = new FileStream(cryptFile, FileMode.Create))
{
    using (RijndaelManaged RMCrypto = new RijndaelManaged())
    {
        using (CryptoStream cs = new CryptoStream(fsCrypt, RMCrypto.CreateEncryptor(key, IV), CryptoStreamMode.Write))
        {
            using (FileStream fsIn = new FileStream(inputFile, FileMode.Open))
            {
                byte[] buffer = new byte[4096]; //4096 is kind of arbitrary - better idea?
                int data;
                long bytesRead = 0;
                while((data = fsIn.Read(buffer, 0, buffer.Length)) > 0)
                {
                    bytesRead += data;
                    /////////////////////////////////////////
                    // Handle Aborts and Update Progress Bar
                    /////////////////////////////////////////
                    if (!caller.isClosing)
                        caller.Invoke((MethodInvoker)delegate {
                            caller.fileProgressBar.Value = ((int)(((double)bytesRead / totalBytes) * 100)); 
                        });
                    else
                        return false; //Encryption Aborted
                    /////////////////////////////////////////
                    cs.Write(buffer, 0, data);
                    fsIn.Close();
                    cs.Close();
                    fsCrypt.Close();
                    return true;
                }
            }
        }
    }
}

Thanks for your time and please let me know if there is a better way to setup the Key/IV/Salt. I think that it is most likely secure enough as long as there is not a mathematical issue with the IV and Key containing similar characters. If so, should I use a hard coded IV as well? That seems weird.

Note that I'm not saving a hash of the password or anything like that. The password is not saved anywhere. It is just used to generate the Key and the IV.

Thanks for your time.

Edit: Here are the changes recommended for anyone looking in the future.

Note that this is not using a pepper - just a random salt, although it would be easy enough to add

byte[] salt = new byte[32];                                        //Create a 32 byte salt
rand.NextBytes(salt);                                              //Fill it with random values (use RandomNumberGenerator rand = new RNGCryptoServiceProvider(); to be safe
byte[] IV = new byte[16];                                          //Create a 16 byte IV
rand.NextBytes(IV);                                                //Fill it with random numbers
byte[] key = new Rfc2898DeriveBytes(password, salt).GetBytes(32);  //Derive our Key by mixing our password with the salt

using (FileStream fsCrypt = new FileStream(cryptFile, FileMode.Create))
{
    using (RijndaelManaged RMCrypto = new RijndaelManaged())
    {
        using (CryptoStream cs = new CryptoStream(fsCrypt, RMCrypto.CreateEncryptor(key, IV), CryptoStreamMode.Write))
        {
            using (FileStream fsIn = new FileStream(inputFile, FileMode.Open))
            {
                fsCrypt.Write(salt, 0, salt.Length); //Write our salt to the file
                fsCrypt.Write(IV, 0, IV.Length);     //Write our IV to the file
                fsIn.CopyTo(cs);                     //Encrypt and Write
            }
        }
    }
}

回答1:

The salt is used for two purposes:

  1. to prevent rainbow table attacks (and it does if applied correctly);
  2. to prevent identical passwords to generate the same password hash.

To do this the salt needs to be 8 to 16 bytes (not characters) of random data, stored with the password hash. Using a static hash as you do defeats both purposes of the hash.

If you need strings, use base 64 to encode the salt and password hash. If you want you can add static data (sometimes called "pepper") to the salt before calling the password hash function. This may add some security if the program data cannot be easily read by an attacker.

You should never directly mix the salt and the password yourself; the Rfc2898DeriveBytes (which is an implementation of PBKDF2) already mixes the two. You should also never store the password, nor should you have to append any data to it. PBKDF2 can handle any size of input, so it doesn't add any functionality.

Now the IV can be taken from the PBKDF2 function (using GetBytes). There is however a problem, it's likely that this will double the initial amount of iterations of PBKDF2 function, which costs CPU time and reduces the advantage over an attacker. It's probably better to just generate a random IV and prefix it to the ciphertext.

So in the end you should store salt | IV | ciphertext, then use salt | pepper as salt and calculate your key, then encrypt/decrypt using the calculated key and IV.



回答2:

As far as I know,

  1. IV/Salt does not need to be private. It can be stored in plain-text on the hard drive. In fact, salt must be in plain-text, otherwise you cannot generate the same output with it.
  2. It is not a good idea to use your key information for generating the IV as it may leak your key information.
  3. There is no way you can prevent attacks like rainbow. But with salt, rainbow attack becomes expensive as the talbes only work with this salt value.
  4. There is a standard of key derivation fucntion that may useful to you (http://en.wikipedia.org/wiki/PBKDF2).