Take the 2-minute tour ×
Cryptography Stack Exchange is a question and answer site for software developers, mathematicians and others interested in cryptography. It's 100% free, no registration required.

I've done some reading about implementing AES256 and deriving a key from a password. If I understand correctly:

  • I want to generate a new salt (for the key) and a new IV (for the encrypted message) for every new message.
  • It should also not be a problem sending the salt and the IV together with the message.

I decided to pack everything together in one byte array where the first 16 bytes is the salt, the next 16 bytes is the IV and the rest is the encrypted message. This with a key length of 256 bits and 20000 iterations for generating the key. I also encode the whole thing in Base64 for transmission.

Can this approach be improved? Knowing that I'm limited to 256 bytes for the complete message (salt+iv+message). How secure is this?

(Feedback and changes at the bottom)

public class app {

    public static void main(String[] args) throws Exception {
        int iterations = 20000;
        int keyLength = 32;
        byte[] salt = getRandomBytes(16);
        byte[] iv = getRandomBytes(16);
        char[] password = "password_here".toCharArray();
        byte[] payload = "payload_here".getBytes();

        byte[] key = deriveKey(iterations, keyLength, salt, iv, password);
        byte[] encrypted = encrypt(iv, key, payload);
        byte[] bytes = concatBytes(salt, iv, encrypted);

        String output = Base64.encodeBytes(bytes);
        int outputLength = output.getBytes().length;
        System.out.println(outputLength + "\n" + output);
    }

    private static byte[] deriveKey(int iterations, int length, byte[] salt, byte[] iv, char[] password)
            throws InvalidKeySpecException, NoSuchAlgorithmException {
        PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, length * 8);
        SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        byte[] key = skf.generateSecret(spec).getEncoded();
        return key;
    }

    private static byte[] getRandomBytes(int length) throws NoSuchAlgorithmException {
        SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
        byte[] salt = new byte[length];
        sr.nextBytes(salt);
        return salt;
    }

    private static byte[] concatBytes(byte[]... arrays) throws IOException {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        for (byte[] array : arrays)
            outputStream.write(array);
        return outputStream.toByteArray();
    }

    private static byte[] encrypt(byte[] iv, byte[] key, byte[] text) throws NoSuchAlgorithmException,
            NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException,
            BadPaddingException {
        AlgorithmParameterSpec ivSpec = new IvParameterSpec(iv);
        SecretKeySpec newKey = new SecretKeySpec(key, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, newKey, ivSpec);
        return cipher.doFinal(text);
    }
}

Based on feedback I've made the following changes:

  • Implemented authenticated encryption by using GCM. I had to switch to the Bouncy Castle library to be able to use GCM.
  • I dropped the IV and am using the same bytes for the key salt and the GCM nonce.
  • Upped the number of iterations for deriving the key to 50k.

Resulting code:

public class app {

    public static void main(String[] args) throws Exception {

        Security.addProvider(new BouncyCastleProvider());

        int iterations = 50000;
        int keyLength = 32;
        byte[] salt = getRandomBytes(16);
        char[] password = "password_here".toCharArray();
        byte[] payload = "payload_here".getBytes();

        byte[] key = deriveKey(iterations, keyLength, salt, password);
        byte[] encrypted = encrypt(salt, key, payload);
        byte[] bytes = concatBytes(salt, encrypted);

        String output = Base64.encodeBytes(bytes);
        int outputLength = output.getBytes().length;
        System.out.println(outputLength + "\n" + output);
    }

    private static byte[] deriveKey(int iterations, int length, byte[] salt, char[] password)
            throws InvalidKeySpecException, NoSuchAlgorithmException {
        PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, length * 8);
        SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        return skf.generateSecret(spec).getEncoded();
    }

    private static byte[] getRandomBytes(int length) throws NoSuchAlgorithmException {
        SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
        byte[] bytes = new byte[length];
        sr.nextBytes(bytes);
        return bytes;
    }

    private static byte[] concatBytes(byte[]... arrays) throws IOException {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        for (byte[] array : arrays)
            outputStream.write(array);
        return outputStream.toByteArray();
    }

    private static byte[] encrypt(byte[] nonce, byte[] key, byte[] text) throws NoSuchAlgorithmException,
            NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException,
            BadPaddingException, IllegalStateException, InvalidCipherTextException {

        GCMBlockCipher gcm = new GCMBlockCipher(new AESFastEngine());
        AEADParameters params = new AEADParameters(new KeyParameter(key), 128, nonce);
        gcm.init(true, params);

        byte[] output = new byte[gcm.getOutputSize(text.length)];
        int len = gcm.processBytes(text, 0, text.length, output, 0);
        gcm.doFinal(output, len);

        return output;
    }
}
share|improve this question
    
You should use authenticated encryption. $\:$ If 256 bytes is a hard limit and you would like to handle $\hspace{.3 in}$ messages that are very close to being that long, then there are more complicated things you can do. $\hspace{.59 in}$ –  Ricky Demer Feb 6 at 8:43
    
@Ricky, thanks. As that post suggests, I've never heard of authenticated encryption before. I'm leaning towards using GCM, but I will have to look some more into it. -- Will I only be able to "win" space within the 256 bytes if I compress the data? DEFLATE for instance. –  ndsc Feb 6 at 12:11
    
@ndsc Btw 20000 PBKDF2 iterations are a bit on the low side these days. Recommend at least 50k and 100k if possible. –  Thomas Feb 7 at 4:42
    
Out of interest, where does this 256 byte limit come from? Packet limits? –  figlesquidge Feb 7 at 7:44
1  
@figlesquidge, I'm sending messages through Google Cloud Messaging, which imposes that limit. Also see: developer.chrome.com/apps/cloudMessaging.html –  ndsc Feb 7 at 9:07
show 2 more comments

1 Answer 1

There are other ways to "win" space. $\:$ Also, see this answer regarding compression.

You can remove (or just reduce the size of) the IV, since if the salts
are different then the derived keys should be sufficiently independent.

You can make the salt smaller than what it should be if bandwidth wasn't an issue.
Additionally, the salt length can depend on the message's length, although
it must be simple to figure out which part of an overall ciphertext is the salt.


Instead of using a standard authenticated encryption system, you can build a variable-width block cipher by having the inner PRF keys depend pseudorandomly on the block size, and handle authentication by

$\;$ choosing how the non-negative integer value(s) of $\sigma$ depend on the message's length,
$\;$ although it must be simple to calculate $\sigma$ given $\;\; \sigma + $ [the message's length]
$\;\;\;$ and
$\;$ choosing either "beginning" or "end"
$\;\;\;$ and
$\;$ having the encryptor put that many zeros at that part of the message
$\;$ before applying the forward direction of the variable-width block cipher
$\;\;\;$ and
$\;$ having the decryptor reject if the result of applying the reverse direction of the variable-width
$\;$ block cipher to the relevant part of the ciphertext does not have $\sigma$ zeros at its chosen end

.


The probability of a forgery by an efficient adversary
will be at most negligibly more than $\:\:$(# of tries)/(2^$\sigma$)$\;\;$.

The real decryption results will be computationally indistinguishable from the results that are:
reject whenever the real decryptor would, and otherwise output a message chosen
randomly from among those whose length's are compatible with the ciphertext's
length but were not themselves encrypted with the password and the ciphertext's salt.
(If you do use an IV, then the last word in the previous sentence should be replaced with "salt and IV".)

share|improve this answer
    
Thanks for your input. Building something myself is quite beyond my expertise. Would it be possible to use the same value for the salt and IV? Granted that I generate a new one for every message? –  ndsc Feb 7 at 19:09
    
That should work. $\;$ –  Ricky Demer Feb 7 at 19:29
    
Updated my code to use GCM and reused the salt for the GCM nonce. –  ndsc Feb 7 at 20:36
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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