3

I asked this question a while ago about IVs in AES, and I got a very nice and helpful answer (thanks!) so I was thinking maybe you guys could help me out again, this time with the actual key generation.

TL;DR - See bottom

Currently, I'm encrypting like this:

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
SecureRandom randomSecure = SecureRandom.getInstance("SHA1PRNG");
byte[] iv = new byte[cipher.getBlockSize()];
randomSecure.nextBytes(iv);
IvParameterSpec ivParams = new IvParameterSpec(iv);
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivParams);
rawCryptotext = cipher.doFinal(textToEncrypt.getBytes());

So you can see, I am using the SecureRandom class to generate the random IV.

However:

Notice the key variable in the 6th line of code:

SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

That variable comes from my getKey() method, which is as follows:

public static String getKey()
    {
        String possibleKey = "init";
        String validKey = null;

        while (possibleKey.length() != 16)
        {
            System.out.printf("\nPlease enter a 128-bit key: ");
            possibleKey = input.nextLine();
        }

        if (possibleKey.length() == 16)
        {
            validKey = possibleKey;
        }
        else
        {
            System.out.println("Something has gone very wrong...");
        }

        return validKey;
    }

You'll notice that I get the key from the user, and pass it in plain text to SecretKeySpec.

So my question: Does combining a plain-text key with a random IV make for secure encryption? Or should I hash the key string with SHA-256 or something first, and use the result for the key?

  • 3
    For Password Based Encryption see PBKDF2 – marstato Aug 28 '16 at 19:54
  • @marstato - Isn't that for RSA? – Android Dev Aug 28 '16 at 19:56
  • PBKDF2 can procudes keys of virtually any desired size. You can use it for whatever algorithm you want (all it does internally is hashing the inputs around). – marstato Aug 28 '16 at 19:59
  • @marstato Mmmmkay.... thanks, but if you could answer my original question in bold at the end that'd be helpful :) – Android Dev Aug 28 '16 at 20:04
7

No

What you're doing, is asking the user to enter a 16-character string or, in other words, a password. It's true that 16 bytes can fit 128 bits, but that only holds for data where all possible byte values are (equally) possible. Text consisting of characters is all but that. First, because the set of printable of characters is much smaller than that of all bytes. (It's likely they won't even be able to enter all possible bytes if they tried.) Second, because user's aren't going to enter random bytes, but words. And words are even more predictable.

If you want the user to enter a password, don't limit the length (except maybe to set a minimum), and use something like PBKDF2 to stretch the key and slow down dictionary guesses against the password. (As @marstato commented).

On the other hand, if you want to generate a random key, pull bytes out a strong random bit generator, and save it in a file.

  • (Also add the normal comments about using existing tools like GnuPG or TLS if you can.) – ilkkachu Aug 28 '16 at 20:30
3

I assume key is a password the user entered.

Do not use that for encryption directly. Brute-Force attacks against (possibly very weak) user passwords are far easier than against a full 128 (or 192, 256) bit key. Just hashing the password to get a key does not do the trick because it does not slow down the brute force by a relevant amount. Passwords need to be hashed tens of thousands of times in order to delay one single try long enought to make a brute-force attack against the raw password infeasible.

PBKDF2 is the solution for this. It combines a salt, a secure hashing function and tons of iterations:

// obtain password from wherever you like
char[] password;

// generate salt using a secureRandom; store salt with ciphertext
byte[] salt;

// key size in bits
int keySize = 128;
// choose so that it takes the computer of your average user about 1 second to do the derivation (lower if running on mobile devices, higher on an enterprise server)
int nIterations = 10000;

SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBDKF2WithHmacSHA256");
SecretKeySpec keySpec = keyFactory.generateSecret(new PBEKeySpec(password, salt, keysize));
// init cipher with keySpec
  • Thanks for the answer! Question though: Currently, I concatenate the IV to the ciphertext (after converting both to base64). I assume I need to do the same for the salt? – Android Dev Aug 29 '16 at 11:10
  • Yes; you need the salt to reproduce the key. And the salt should - just as the IV - be unique per ciphertext. That means you have to store it along the ciphertext. – marstato Aug 29 '16 at 11:13
  • Okay, I'll try this later today. BTW, do you think I should concatenate the IV, salt and ciphertext before, or after converting to base64? – Android Dev Aug 29 '16 at 11:21
  • Its entirely irrelevant. Use whichever way is easier for you to implement. – marstato Aug 29 '16 at 11:22
  • So I tried to implement your code, but it's throwing a NoSuchAlgorithmException on the line where you create the keyFactory variable. Also, where do you use the nIterations variable? You never pass it as a parameter to anything. – Android Dev Aug 29 '16 at 13:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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