Java AES/GCM/NoPadding - What is cipher.getIV() giving me?

Michael Hixson picture Michael Hixson · Aug 6, 2015 · Viewed 31.4k times · Source

I'm using AES/GCM/NoPadding encryption in Java 8 and I'm wondering whether my code has a security flaw. My code seems to work, in that it encrypts and decrypts text, but a few details are unclear.

My main question is this:

Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] iv = cipher.getIV(); // ?????

Does that IV satisfy the requirement of "For a given key, the IV MUST NOT repeat." from RFC 4106?

I'd also appreciate any answers / insight for my related questions (see below), but that first question is bugging me the most. I don't know where to find source code or documentation that answers this.


Here is the full code, roughly. I apologize in case I introduced errors while writing this post:

class Encryptor {
  Key key;

  Encryptor(byte[] key) {
    if (key.length != 32) throw new IllegalArgumentException();
    this.key = new SecretKeySpec(key, "AES");
  }

  // the output is sent to users
  byte[] encrypt(byte[] src) throws Exception {
    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
    cipher.init(Cipher.ENCRYPT_MODE, key);
    byte[] iv = cipher.getIV(); // See question #1
    assert iv.length == 12; // See question #2
    byte[] cipherText = cipher.doFinal(src);
    assert cipherText.length == src.length + 16; // See question #3
    byte[] message = new byte[12 + src.length + 16]; // See question #4
    System.arraycopy(iv, 0, message, 0, 12);
    System.arraycopy(cipherText, 0, message, 12, cipherText.length);
    return message;
  }

  // the input comes from users
  byte[] decrypt(byte[] message) throws Exception {
    if (message.length < 12 + 16) throw new IllegalArgumentException();
    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
    GCMParameterSpec params = new GCMParameterSpec(128, message, 0, 12);
    cipher.init(Cipher.DECRYPT_MODE, key, params);
    return cipher.doFinal(message, 12, message.length - 12);
  }
}

Suppose that users cracking my secret key = game over.


More detailed questions / related questions:

  1. Is the IV returned by cipher.getIV() safe for me to use in this way?

    • Does it avoid the catastrophe of reusing the IV,key combination in Galois/Counter Mode?
    • Is it still safe when I have multiple applications running this code at once, all displaying encrypted messages to users from the same src data (possibly in the same millisecond)?
    • What's the returned IV made of? Is it an atomic counter plus some random noise?
    • Do I need to avoid cipher.getIV() and construct an IV myself, with my own counter?
    • Is the source code implementing cipher.getIV() available online somewhere, assuming I'm using Oracle JDK 8 + JCE Unlimited Strength extension?
  2. Is that IV always 12 bytes long?

  3. Is the authentication tag always 16 bytes (128 bits) long?

  4. With #2 and #3, and the lack of padding, does that mean my encrypted messages are always 12 + src.length + 16 bytes long? (And so I can safely squish them into one byte array, for which I know the correct length?)

  5. Is it safe for me to display an unbounded number of src data encryptions to users, given constant src data that the users know?

  6. Is it safe for me to display an unbounded number of src data encryptions to users, if the src data is different every time (e.g. including System.currentTimeMillis() or random numbers)?

  7. Would it help if I padded the src data with random numbers before encryption? Say 8 random bytes in front and back, or only on one end? Or would that not help at all / make my encryption worse?

(Because these questions are all about the same block of my own code, and they are strongly related to each other, and others might/should have the same set of questions when implementing the same functionality, it felt wrong to split the questions into multiple posts. I can re-post them separately if that is more appropriate for StackOverflow's format. Let me know!)

Answer

Maarten Bodewes picture Maarten Bodewes · Aug 6, 2015

Q1: Is the IV returned by cipher.getIV() safe for me to use in this way?

Yes, it is at least for the Oracle provided implementation. It is generated separately using the default SecureRandom implementation. As it is 12 bytes in size (the default for GCM) then you have 96 bits of randomness. The chance that the counter repeats is abysmally small. You can look up the source in the OpenJDK (GPL'ed) which the Oracle JDK is based on.

I would however still recommend you to generate your own 12 random bytes as other providers may behave differently.


Q2: Is that IV always 12 bytes long?

It's extremely likely as it is the GCM default, but other lengths are valid for GCM. The algorithm will however have to do additional calculations for any other size than 12 bytes. Due to weaknesses it is strongly recommended to keep it at 12 bytes / 96 bits and API's may restrict you to that choice of IV size.


Q3: Is the authentication tag always 16 bytes (128 bits) long?

No, it can have any size in bytes ranging from 64 bits to 128 bits with 8 bit increments. If it is smaller it simply consists of the leftmost bytes of the authentication tag though. You can specify another size of tag using GCMParameterSpec as third parameter for your init call.

Note that the strength of GCM is strongly dependent on the size of the tag. I would recommend keeping it to 128 bits. 96 bits should be the minimum especially if you want to generate a lot of ciphertext.


Q4: With #2 and #3, and the lack of padding, does that mean my encrypted messages are always 12 + src.length + 16 bytes long? (And so I can safely squish them into one byte array, for which I know the correct length?)

See above. For the Oracle provider this is the case. Use GCMParameterSpec to be sure of it.


Q5: Is it safe for me to display an unbounded number of src data encryptions to users, given constant src data that the users know?

Virtually unbound, yes. I would start worrying after about 2^48 encryptions. In general you should however design for key change.


Q6: Is it safe for me to display an unbounded number of src data encryptions to users, if the src data is different every time (e.g. including System.currentTimeMillis() or random numbers)?

See answer to Q5


Q7: Would it help if I padded the src data with random numbers before encryption? Say 8 random bytes in front and back, or only on one end? Or would that not help at all / make my encryption worse?

No, it would not help at all. GCM uses CTR mode underneath, so it would just be encrypted with the key stream. It would not act as an IV. If you need a virtually boundless number of ciphertexts (higher than 2^48!) then I would suggest you use that random number and your key for a key derivation function or KDF. HKDF is currently best of breed, but you may need to use Bouncy Castle or implement it yourself.