Java AES CTR IV and counter
Asked Answered
M

2

5

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?

Mureil answered 20/7, 2017 at 9:57 Comment(2)
If the file is unit of data to be encrypted your first code is fine; with init and multiple update (and doFinal) the counter is updated internally even though you don't see it. Note that CTR is malleable and without integrity protection your system is likely insecure, but that's offtopic for SO; see security.stackexchange.com/questions/2202/… and (several) linked Qs.Turku
Thank you too - the bit about internal counter update is what I was missing. As for integrity protection - I was considering using CTR or GCM mode and now that you pointed this out I will have to reconsider my choice (doFinal() for GCM automatically adds authentication part at the end as far as I understand).Mureil
B
7
  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.

Baur answered 20/7, 2017 at 18:3 Comment(1)
I was sort of expecting that counter is updated internally, but was not sure because it's not mentioned in the documentation for update() and doFinal(), hence the question.Thank you also for pointing out the flaws with the second approach. As for nonce/counter size - yes, I will have to think about counter wrapping since in general some files may actually be bigger than 68G.Mureil
C
0

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.

Cia answered 20/7, 2017 at 11:10 Comment(2)
I cannot recommend your way of doing this, because Java will fill all bits and use the whole block as a nonce which is then used in full as a counter. You don't have much chance to detect or prevent counter overlapping to occur. Tested with this code on this site.Baur
The intent here is to NOT have a predictable value in either the nonce or counter. Please see this article: security.stackexchange.com/a/63189/38251Cia

© 2022 - 2024 — McMap. All rights reserved.