Java AES CTR IV and counter

jarok Source

I have tried to find the answer by myself looking here and searching elsewhere for quite a while, but I still have some questions.

Assume this Java code:

try
{   
    int cipherMode = Cipher.ENCRYPT_MODE;

    SecretKeySpec secretKey = ...; // generated previously using KeyGenerator       

    byte[] nonceAndCounter = new byte[16];
    byte[] nonceBytes      = ...; // generated previously using SecureRandom's nextBytes(8);

    // use first 8 bytes as nonce
    Arrays.fill(nonceAndCounter, (byte) 0);
    System.arraycopy(nonceBytes, 0, nonceAndCounter, 0, 8);

    IvParameterSpec ivSpec = new IvParameterSpec(nonceAndCounter);
    Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");

    cipher.init(cipherMode, secretKey, ivSpec);

    File inFile  = new File(...);
    File outFile = new File(...);

    long bytesRead = 0;

    try (FileInputStream is = new FileInputStream(inFile);
         FileOutputStream os = new FileOutputStream(outFile))
    {       
        byte[] inBuf       = new byte[512 * 1024];
        byte[] outBuf      = new byte[512 * 1024];
        int    readLen     = 0;

        ByteBuffer byteBuffer = ByteBuffer.allocate(8);
        byteBuffer.putLong(bytesRead);

        while ((readLen = is.read(inBuf)) != -1)
        {
            bytesRead += readLen;

            cipher.update(inBuf, 0, readLen, outBuf, 0);

            os.write(outBuf);
        }

        cipher.doFinal(outBuf, 0);
        os.write(outBuf);
        is.close();
        os.close();
    }
    catch (Exception e) {
        System.out.printf("Exception for file: %s\n", e);
    }
} 
catch (Exception e) { 
    System.out.printf("Exception: %s\n", e);
}

My questions are:

  1. Is the above code considered OK regarding counter updates for CTR mode? Specifically, I am not updating the counter myself. Should I use the following while loop instead? I tried this because I looked at what cipher.getIV() returns in the loop, but it does not change and the description for getIV() does not go into much details:

        while ((readLen = is.read(inBuf)) != -1)
        {   
            // use offset for last 8 bytes as counter
            byteBuffer.putLong(bytesRead);
            System.arraycopy(byteBuffer.array(), 0, nonceAndCounter, 8, 8);
    
            bytesRead += readLen;
    
            IvParameterSpec ivSpec = new IvParameterSpec(nonceAndCounter);
    
            cipher.init(cipherMode, secretKey, ivSpec);
    
            cipher.update(inBuf, 0, readLen, outBuf, 0);
    
            os.write(outBuf);
        }
    
    1. I have more questions related to the modified while loop approach. Is it OK to call cipher.init() in such a way? I do this because I haven't found a way to update just IV (counter really).

    2. Is such a large block size OK or should it be made smaller? In that case how big should it be?

javaencryptionaesinitialization-vector

Answers

answered 1 year ago Dave G #1

Instead of generating the iv/nonce like you did here:

// use first 8 bytes as nonce
Arrays.fill(nonceAndCounter, (byte) 0);
System.arraycopy(nonceBytes, 0, nonceAndCounter, 0, 8);

IvParameterSpec ivSpec = new IvParameterSpec(nonceAndCounter);
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");

cipher.init(cipherMode, secretKey, ivSpec);

You should consider doing the following:

Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
// By doing this here w/o an IvParameterSpec, you let the
// cipher initialization create it. Less chance (see what I did there)
// to influence values that should be completely random.
cipher.init(cipherMode, secretKey);
AlgorithmParameters ivSpec = cipher.getParameters();
byte[] nonceAndCounter= ivSpec.getEncoded()

On the receiving side when you will need to construct an IvParameterSpec from the value returned to nonceAndCounter then build out your decrypt mode.

answered 1 year ago Artjom B. #2

  1. Is the above code considered OK regarding counter updates for CTR mode?

Yes.

But you might want to tweak the nonce and counter sizes a bit. If your nonce is only 64 bit long, you'll likely run into a nonce collision after 232 encryptions due to the birthday paradox (the probability is increasing if you approach that point). If you use the same key for all of these encryptions (I mean messages/files not blocks) and there is a collision, this is considered a catastrophic break for CTR-mode, because it is a two-time or many-time pad.

You should consider using a 96 bit nonce and a 32 bit counter. The disadvantage is that you can only encrypt up to 232 blocks safely which is something like 68 GB per message/file.

  1. I have more questions related to the modified while loop approach. Is it OK to call cipher.init() in such a way?

No.

You really should not update the counter yourself. Note that you have a mismatch between bytes and block cipher blocks: Your proposed counter update uses the bytes that were already processed as a fresh counter value which advances much quicker than the natural CTR mode counter which counts block-wise. You're exhausting the counters so that a collision is getting more probable. For example, if the nonces differ by 1 when represented numerically, then an overlap might occur if the nonce part is short and the counter part is long. If the nonce is 96 bit long then you can only encrypt messages/files with a size of 68/16 GB = 4.5 GB safely.

Also, since you have a byte/block mismatch, this is not CTR mode anymore and you will have a hard time to port this code to other languages.

  1. Is such a large block size OK or should it be made smaller? In that case how big should it be?

Not sure what you mean, AES has a fixed block size of 128 bit or 16 byte.

If you mean the input/output buffers, then you should benchmark it on your platform of choice to be sure. It certainly looks OK.

comments powered by Disqus