RFR: 8326609: New AES implementation with updates specified in FIPS 197 [v8]

Valerie Peng valeriep at openjdk.org
Fri Oct 17 05:44:04 UTC 2025


On Thu, 16 Oct 2025 20:22:20 GMT, Shawn M Emery <duke at openjdk.org> wrote:

>> General:
>> -----------
>> i) This work is to replace the existing AES cipher under the Cryptix license.
>> 
>> ii) The lookup tables are employed for performance, but also for operating in constant time.
>> 
>> iii) Several loops have been unrolled for optimization purposes, but are harder to read and don't meet coding style guidelines.
>> 
>> iv) None of the AES related intrinsics has been modified in this PR, but the new code has been updated to use the intrinsics related hooks for the AES block and key table arguments.
>> 
>> Note: I've purposefully not seen the original Cryptix code, so when making a code review comment please don't quote the code in the AESCrypt.java file.
>> 
>> Correctness:
>> -----------------
>> The following AES-specific regression tests have passed in intrinsics (default) and non-intrinsic (-Xint) modes:
>> 
>> i) test/jdk/com/sun/crypto/provider/Cipher/AES: all 27 tests pass
>> 
>> -intrinsics mode for:
>> 
>> ii) test/hotspot/jtreg/compiler/codegen/aes: all 4 tests pass
>> 
>> iii) jck:api/java_security, jck:api/javax_crypto, jck:api/javax_net, jck:api/javax_security, jck:api/org_ietf, and jck:api/javax_xml/crypto: passed, with 10 known failures
>> 
>> iv) jdk_security_infra: passed, with 48 known failures
>> 
>> v) tier1 and tier2: all 110257 tests pass
>> 
>> Security:
>> -----------
>> In order to prevent side-channel (timing and differential power analysis) attacks the code has been constructed to operate in constant time and does not use conditionals based on the key or key expansion table.  This is accomplished by using lookup tables in both the cipher and inverse cipher of AES.
>> 
>> Performance:
>> ------------------
>> All AES related benchmarks have been executed against the new and original Cryptix code:
>> 
>> micro:org.openjdk.bench.javax.crypto.AES
>> 
>> micro:org.openjdk.bench.javax.crypto.full.AESBench
>> 
>> micro:org.openjdk.bench.javax.crypto.full.AESExtraBench
>> 
>> micro:org.openjdk.bench.javax.crypto.full.AESGCMBench
>> 
>> micro:org.openjdk.bench.javax.crypto.full.AESGCMByteBuffer
>> 
>> micro:org.openjdk.bench.javax.crypto.full.AESGCMCipherInputStream
>> 
>> micro:org.openjdk.bench.javax.crypto.full.AESGCMCipherOutputStream
>> 
>> micro:org.openjdk.bench.javax.crypto.full.AESKeyWrapBench.
>> 
>> micro:org.openjdk.bench.java.security.CipherSuiteBench (AES)
>> 
>> The benchmarks were executed in different compiler modes (default (no compiler options), -Xint, and -Xcomp) and on two different architectures (x86 and arm64) with the following encryption re...
>
> Shawn M Emery has updated the pull request incrementally with one additional commit since the last revision:
> 
>   Updates for code review comments from @valeriepeng

src/java.base/share/classes/com/sun/crypto/provider/AES_Crypt.java line 911:

> 909:             }
> 910:             sessionK[0] = genRoundKeys(key, rounds);
> 911:             sessionK[1] = invGenRoundKeys();

Given the decryption round keys are somewhat based on the encryption round keys, we could combine these two methods into one, e.g.

 private static int[][] genRoundKeys(byte[] key, int rounds) {
        int[][] ks = new int[2][]; // key schedule

        int wLen = (rounds + 1) * WB;
        int nk = key.length / WB;

        // generate the round keys for encryption
        int[] w = new int[wLen];
        for (int i = 0, j = 0; i < nk; i++, j+=4) {
            w[i] = ((key[j] & 0xFF) << 24)
                    | ((key[j + 1] & 0xFF) << 16)
                    | ((key[j + 2] & 0xFF) << 8)
                    | (key[j + 3] & 0xFF);
        }
        for (int i = nk; i < wLen; i++) {
            int tmp = w[i - 1];
            if (i % nk == 0) {
                int rW = (tmp << 8) & 0xFFFFFF00 | (tmp >>> 24);
                tmp = subWord(rW) ^ RCON[(i / nk) - 1];
            } else if ((nk > 6) && ((i % nk) == WB)) {
                tmp = subWord(tmp);
            }
            w[i] = w[i - nk] ^ tmp;
        }
        ks[0] = w;

        // generate the decryption round keys based on encryption ones
        int[] dw = new int[wLen];
        int[] temp = new int[WB];

        // Intrinsics requires the inverse key expansion to be reverse order
        // except for the first and last round key as the first two round keys
        // are without a mix column transform.
        for (int i = 1; i < rounds; i++) {
            System.arraycopy(w, i * WB, temp, 0, WB);
            invMixRKey(temp);
            System.arraycopy(temp, 0, dw, wLen - (i * WB), WB);
        }
        // dw[0...3] <- w[0...3] AND dw[4...7] <- w[(wLen - 4)...(wLen -1)]
        System.arraycopy(w, 0, dw, 0, WB);
        System.arraycopy(w, wLen - WB, dw, WB, WB);
        ks[1] = dw;
        Arrays.fill(temp, 0);

        return ks;
    }

-------------

PR Review Comment: https://git.openjdk.org/jdk/pull/27807#discussion_r2438441223


More information about the security-dev mailing list