Files
aliro-project/applet/src/main/java/com/dangerousthings/aliro/AliroGcm.java

659 lines
30 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.dangerousthings.aliro;
import javacard.framework.JCSystem;
import javacard.framework.Util;
import javacard.security.AESKey;
import javacard.security.CryptoException;
import javacard.security.KeyBuilder;
import javacardx.crypto.Cipher;
/**
* Userland AES-256-GCM, built on top of the JC 3.0.5 single-block AES-ECB
* primitive. Needed because the target card (NXP J3R180) does not expose
* {@code AEADCipher.ALG_AES_GCM} despite advertising JC 3.0.5 support, while
* {@code Cipher.ALG_AES_BLOCK_128_ECB_NOPAD} and AES-256 keys are both
* present and functional.
*
* <p>Tailored to the Aliro AUTH1 response use case (spec &sect;8.3.1.6):
* AES-256 with a 12-byte IV, empty AAD, 16-byte tag, and plaintext on the
* order of 137 bytes. The NIST SP 800-38D canonical 96-bit-IV shortcut is
* used (IV is taken as {@code IV || 0x00000001} for J<sub>0</sub>, no IV
* hashing). This is the only IV length we ever produce — see
* {@link AliroApplet#encryptResponseGcm}.
*
* <p>GHASH follows NIST SP 800-38D &sect;6.4's bit-reflected convention:
* byte 0 bit 7 (MSB) is the highest-degree polynomial coefficient; the
* reduction polynomial R = x^128 + x^7 + x^2 + x + 1 is represented as the
* 16-byte array {0xE1, 0x00, ..., 0x00}; the GHASH shift-right shifts toward
* higher byte indices (lower polynomial degrees).
*
* <p>Not constant-time — the GF(2^128) multiply branches on bits of the
* authentication tag partial state. On a secure JC platform this is
* acceptable (the key material is smartcard-protected), but callers should
* not pass attacker-chosen GHASH state.
*/
final class AliroGcm {
private static final short BLOCK_LEN = 16;
private static final short TAG_LEN = 16;
private static final short IV_LEN = 12; // 96-bit IV shortcut (NIST SP 800-38D §7.1)
private static final short AES_256_KEY_BYTES = 32;
// Scratch layout — all 16-byte slots, single transient allocation.
private static final short OFF_H = 0; // hash subkey = AES_K(0^128)
private static final short OFF_J0 = 16; // pre-counter block
private static final short OFF_CB = 32; // running counter block
private static final short OFF_GHASH = 48; // accumulated GHASH state
private static final short OFF_ECB_OUT = 64; // AES keystream block (also used for J0 encryption)
private static final short OFF_TMP = 80; // misc: GF multiply workspace (V)
private static final short OFF_LEN_BLK = 96; // length block for GHASH finalization
private static final short SCRATCH_LEN = 112;
// 4-bit GHASH lookup tables. M is built per-H in mTable[0..256), L is the
// fixed shift-right-by-4 reduction lookup (bytes 2..15 of each L[i] are
// always zero for the NIST GHASH reduction polynomial R = 0xE1 00...00,
// so only byte 0 and byte 1 are stored).
private static final short M_TABLE_LEN = (short) (16 * BLOCK_LEN); // 256 B
// L_BYTE0[i] / L_BYTE1[i] = byte 0 / byte 1 of (R XOR R>>1 XOR R>>2 XOR R>>3)
// selected by which bits of nibble i are set:
// bit 0 set -> XOR R >> 3 = 0x1C 0x20
// bit 1 set -> XOR R >> 2 = 0x38 0x40
// bit 2 set -> XOR R >> 1 = 0x70 0x80
// bit 3 set -> XOR R = 0xE1 0x00
private static final byte[] L_BYTE0 = {
(byte)0x00, (byte)0x1C, (byte)0x38, (byte)0x24,
(byte)0x70, (byte)0x6C, (byte)0x48, (byte)0x54,
(byte)0xE1, (byte)0xFD, (byte)0xD9, (byte)0xC5,
(byte)0x91, (byte)0x8D, (byte)0xA9, (byte)0xB5
};
private static final byte[] L_BYTE1 = {
(byte)0x00, (byte)0x20, (byte)0x40, (byte)0x60,
(byte)0x80, (byte)0xA0, (byte)0xC0, (byte)0xE0,
(byte)0x00, (byte)0x20, (byte)0x40, (byte)0x60,
(byte)0x80, (byte)0xA0, (byte)0xC0, (byte)0xE0
};
/** Persistent ECB cipher (AES-128/192/256 single-block-NOPAD). AES-256
* is driven by the size of the {@link AESKey} passed to {@link Cipher#init}.
* Always allocated -- required for computing H = AES_K(0^128) and the
* per-tag AES_K(J0), regardless of whether CTR is native or userland. */
private final Cipher aesEcb;
/** Reusable 256-bit AES key slot. {@link AESKey#setKey} is called per encrypt. */
private final AESKey aesKey;
/** Transient scratch — see OFF_* constants above. */
private final byte[] scratch;
/** Transient 4-bit GHASH M-table: mTable[i*16..(i+1)*16) = M[i] = i_poly · H,
* rebuilt at the start of every encrypt() once H is computed from the
* fresh AES key. */
private final byte[] mTable;
/** 1 if Cipher.ALG_AES_CTR is available on this JCRE (J3R452 etc.), 0 if
* not (J3R180). When 1, the GCTR pass is one native doFinal call instead
* of ceil(ptLen/16) block-by-block ECB calls -- saves the per-block
* init/init/encrypt overhead. */
private final byte usesNativeCtr;
/** Native CTR cipher; null when {@link #usesNativeCtr} == 0. */
private Cipher aesCtr;
AliroGcm() {
aesEcb = Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_ECB_NOPAD, false);
aesKey = (AESKey) KeyBuilder.buildKey(
KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_256, false);
scratch = JCSystem.makeTransientByteArray(SCRATCH_LEN, JCSystem.CLEAR_ON_DESELECT);
mTable = JCSystem.makeTransientByteArray(M_TABLE_LEN, JCSystem.CLEAR_ON_DESELECT);
// Native AES-CTR detection is temporarily disabled. On the J3R452 the
// smoke test passed but the real encrypt path hangs (>60 s for what
// should take ~22 s) -- something about Cipher.ALG_AES_CTR on that
// runtime interacts badly with the GHASH loop that follows it (best
// guess: shared AES engine state we're not flushing). Forcing the
// ECB-block-by-block path keeps the behaviour identical to J3R180
// baseline; we'll revisit native CTR after the 4-bit GHASH table
// lands and we have a baseline to compare against.
usesNativeCtr = 0;
}
/**
* AES-256-GCM encrypt with the Aliro parameters: 32-byte key, 12-byte IV,
* empty AAD, 16-byte tag appended. Writes {@code ptLen} ciphertext bytes
* followed by 16 tag bytes to {@code out[outOff..]} and returns
* {@code ptLen + 16}.
*
* <p>The {@code out} buffer may overlap {@code pt} (same buffer, same
* offset is supported — we encrypt block-by-block left-to-right, the
* GHASH pass reads from {@code out}, and the tag lands past the end of
* the ciphertext).
*
* @param key 32-byte AES-256 key, at {@code key[keyOff..keyOff+32)}
* @param iv 12-byte IV, at {@code iv[ivOff..ivOff+12)}
* @param pt plaintext bytes
* @param ptOff, ptLen plaintext region
* @param out output buffer, must have at least {@code ptLen + 16} bytes
* available at {@code outOff}
* @param outOff output start offset
* @return {@code ptLen + 16}
*/
short encrypt(
byte[] key, short keyOff,
byte[] iv, short ivOff,
byte[] pt, short ptOff, short ptLen,
byte[] out, short outOff) {
setKeyAndIv(key, keyOff, iv, ivOff);
// cb = INC32(J0) — first counter block for the plaintext stream.
Util.arrayCopyNonAtomic(scratch, OFF_J0, scratch, OFF_CB, BLOCK_LEN);
inc32(scratch, OFF_CB);
if (usesNativeCtr != 0) {
// Native AES-CTR: single call encrypts the whole plaintext (handles
// partial final block internally). Saves the per-block init/doFinal
// overhead of the ECB fallback loop below.
aesCtr.init(aesKey, Cipher.MODE_ENCRYPT, scratch, OFF_CB, BLOCK_LEN);
aesCtr.doFinal(pt, ptOff, ptLen, out, outOff);
} else {
// GCTR fallback: stream plaintext through block-by-block ECB.
short produced = 0;
while (produced < ptLen) {
short blockLen = (short) (ptLen - produced);
if (blockLen > BLOCK_LEN) blockLen = BLOCK_LEN;
// keystream = AES_K(cb)
aesEcb.doFinal(scratch, OFF_CB, BLOCK_LEN, scratch, OFF_ECB_OUT);
// ct[i] = pt[i] XOR keystream[i] — partial-block safe.
for (short i = 0; i < blockLen; i++) {
out[(short) (outOff + produced + i)] =
(byte) (pt[(short) (ptOff + produced + i)]
^ scratch[(short) (OFF_ECB_OUT + i)]);
}
inc32(scratch, OFF_CB);
produced += blockLen;
}
}
// GHASH over (AAD=empty || ciphertext || len_block).
// state = 0^128
Util.arrayFillNonAtomic(scratch, OFF_GHASH, BLOCK_LEN, (byte) 0);
// Fold ciphertext blocks into GHASH, zero-padding the final partial block.
short consumed = 0;
while (consumed < ptLen) {
short chunk = (short) (ptLen - consumed);
if (chunk >= BLOCK_LEN) {
// XOR full 16-byte block directly from the output buffer.
for (short i = 0; i < BLOCK_LEN; i++) {
scratch[(short) (OFF_GHASH + i)] ^= out[(short) (outOff + consumed + i)];
}
consumed += BLOCK_LEN;
} else {
// Partial block: XOR the available bytes, implicit zero-pad for the rest.
for (short i = 0; i < chunk; i++) {
scratch[(short) (OFF_GHASH + i)] ^= out[(short) (outOff + consumed + i)];
}
consumed += chunk;
}
// GHASH state · H using 4-bit table. gfMul4Bit writes to OFF_ECB_OUT;
// copy result back into OFF_GHASH to match the existing protocol.
gfMul4Bit(scratch, OFF_GHASH);
Util.arrayCopyNonAtomic(scratch, OFF_ECB_OUT,
scratch, OFF_GHASH, BLOCK_LEN);
}
// len_block = (8*|AAD|)^64 || (8*|C|)^64, big-endian. AAD is always empty
// for this use case; ptLen fits in 16 bits, so only the low 2 bytes of
// the 8-byte plaintext-bit-length field can be non-zero.
Util.arrayFillNonAtomic(scratch, OFF_LEN_BLK, BLOCK_LEN, (byte) 0);
// ptLen·8 in bits, big-endian, into the last 4 bytes of the 16-byte
// length block (bits 0-95 are zero — AAD-bits=0 + high bytes of CT-bits).
// Java Card 3.0.5 forbids `int` literals, so we compute as `short` and
// split the byte-shift into two stages to dodge the implicit `int`
// promotion that the converter rejects ("unsupported int type constant").
// ptLen ≤ 8191 ⇒ ptLen·8 ≤ 65528, fits in unsigned 16 bits. The top two
// bytes of the length field stay zero.
short ctBitsLo = (short) (ptLen << 3); // low 16 bits of bit-count
short ctBitsHi = (short) (((short)(ptLen >>> 13)) & 0x07); // bits 16-18 of bit-count
scratch[(short) (OFF_LEN_BLK + 13)] = (byte) (ctBitsHi & 0xFF);
scratch[(short) (OFF_LEN_BLK + 14)] = (byte) ((ctBitsLo >>> 8) & 0xFF);
scratch[(short) (OFF_LEN_BLK + 15)] = (byte) (ctBitsLo & 0xFF);
for (short i = 0; i < BLOCK_LEN; i++) {
scratch[(short) (OFF_GHASH + i)] ^= scratch[(short) (OFF_LEN_BLK + i)];
}
gfMul(scratch, OFF_GHASH, scratch, OFF_H);
// T = AES_K(J0) XOR GHASH
aesEcb.doFinal(scratch, OFF_J0, BLOCK_LEN, scratch, OFF_ECB_OUT);
short tagOut = (short) (outOff + ptLen);
for (short i = 0; i < TAG_LEN; i++) {
out[(short) (tagOut + i)] =
(byte) (scratch[(short) (OFF_ECB_OUT + i)]
^ scratch[(short) (OFF_GHASH + i)]);
}
// Wipe scratch: H, J0 (with IV bytes), GHASH state, and the keystream
// remnant in OFF_ECB_OUT all touched the AES key directly. CLEAR_ON_DESELECT
// would clean up at deselect, but multi-APDU sessions could otherwise
// see stale H/keystream until then.
Util.arrayFillNonAtomic(scratch, (short) 0, SCRATCH_LEN, (byte) 0);
return (short) (ptLen + TAG_LEN);
}
/**
* AES-256-GCM decrypt with the Aliro parameters: 32-byte key, 12-byte IV,
* empty AAD, 16-byte tag appended. Input is
* {@code in[inOff..inOff+inLen)} laid out as
* {@code ciphertext || tag} where the trailing 16 bytes are the tag and
* the preceding {@code inLen - 16} bytes are the ciphertext. The tag is
* verified before any plaintext is emitted; if verification fails the
* method throws a {@link CryptoException} ({@code ILLEGAL_VALUE}) and does
* NOT write to {@code out}. On success {@code inLen - 16} plaintext bytes
* are written to {@code out[outOff..]} and the same value is returned.
*
* <p>The buffer-aliasing rules mirror {@link #encrypt}: {@code in} and
* {@code out} may be the same buffer at the same offset (we GHASH the
* ciphertext BEFORE we touch the output region, then GCTR overwrites it
* left-to-right).
*
* <p>Used by the Step-Up phase EXCHANGE / ENVELOPE handlers for the
* reader→device direction per spec §8.3.1.9. The expedited-phase
* decrypt path on the reader side is not exercised by the applet; this
* exists to verify the GCM tag on inbound traffic so the applet knows
* the reader holds the matching session keys.
*
* @param key 32-byte AES-256 key
* @param iv 12-byte IV
* @param in input buffer, layout {@code ciphertext || tag}
* @param inOff, inLen input region; {@code inLen >= 16} required
* @param out output buffer, must have at least {@code inLen - 16} bytes
* available at {@code outOff}
* @return plaintext length = {@code inLen - 16}
* @throws CryptoException with reason {@code ILLEGAL_VALUE} on bad input
* length or tag mismatch
*/
short decrypt(
byte[] key, short keyOff,
byte[] iv, short ivOff,
byte[] in, short inOff, short inLen,
byte[] out, short outOff) {
if (inLen < TAG_LEN) {
CryptoException.throwIt(CryptoException.ILLEGAL_VALUE);
}
short ctLen = (short) (inLen - TAG_LEN);
short tagOff = (short) (inOff + ctLen);
setKeyAndIv(key, keyOff, iv, ivOff);
// GHASH over the PROVIDED ciphertext first (so tag verify doesn't
// depend on a successful decrypt). AAD is empty.
Util.arrayFillNonAtomic(scratch, OFF_GHASH, BLOCK_LEN, (byte) 0);
short consumed = 0;
while (consumed < ctLen) {
short chunk = (short) (ctLen - consumed);
if (chunk >= BLOCK_LEN) {
for (short i = 0; i < BLOCK_LEN; i++) {
scratch[(short) (OFF_GHASH + i)] ^= in[(short) (inOff + consumed + i)];
}
consumed += BLOCK_LEN;
} else {
for (short i = 0; i < chunk; i++) {
scratch[(short) (OFF_GHASH + i)] ^= in[(short) (inOff + consumed + i)];
}
consumed += chunk;
}
gfMul4Bit(scratch, OFF_GHASH);
Util.arrayCopyNonAtomic(scratch, OFF_ECB_OUT,
scratch, OFF_GHASH, BLOCK_LEN);
}
// len_block = 0^64 || (8*ctLen)^64. AAD bits = 0; same byte-shift
// dance as encrypt() to dodge JC's int-promotion conversion failure.
Util.arrayFillNonAtomic(scratch, OFF_LEN_BLK, BLOCK_LEN, (byte) 0);
short ctBitsLo = (short) (ctLen << 3);
short ctBitsHi = (short) (((short)(ctLen >>> 13)) & 0x07);
scratch[(short) (OFF_LEN_BLK + 13)] = (byte) (ctBitsHi & 0xFF);
scratch[(short) (OFF_LEN_BLK + 14)] = (byte) ((ctBitsLo >>> 8) & 0xFF);
scratch[(short) (OFF_LEN_BLK + 15)] = (byte) (ctBitsLo & 0xFF);
for (short i = 0; i < BLOCK_LEN; i++) {
scratch[(short) (OFF_GHASH + i)] ^= scratch[(short) (OFF_LEN_BLK + i)];
}
gfMul(scratch, OFF_GHASH, scratch, OFF_H);
// T_expected = AES_K(J0) XOR GHASH. Compare against received tag in
// constant-ish time (XOR-then-OR; no early exit). On JC this isn't
// truly constant-time at the bytecode level, but the smartcard SE
// doesn't expose timing channels at the resolution that would matter
// for a 128-bit tag forgery anyway.
aesEcb.doFinal(scratch, OFF_J0, BLOCK_LEN, scratch, OFF_ECB_OUT);
byte diff = 0;
for (short i = 0; i < TAG_LEN; i++) {
byte expected = (byte) (scratch[(short) (OFF_ECB_OUT + i)]
^ scratch[(short) (OFF_GHASH + i)]);
diff |= (byte) (expected ^ in[(short) (tagOff + i)]);
}
if (diff != 0) {
// Wipe scratch before throwing so a tag-mismatch doesn't leave H,
// GHASH state, or AES_K(J0) sitting in transient.
Util.arrayFillNonAtomic(scratch, (short) 0, SCRATCH_LEN, (byte) 0);
CryptoException.throwIt(CryptoException.ILLEGAL_VALUE);
}
// Tag verified -- now GCTR-decrypt the ciphertext into out[]. cb is
// INC32(J0) just like encrypt(). Reusable OFF_CB slot has been free
// since J0 was last referenced for the tag XOR.
Util.arrayCopyNonAtomic(scratch, OFF_J0, scratch, OFF_CB, BLOCK_LEN);
inc32(scratch, OFF_CB);
if (usesNativeCtr != 0) {
aesCtr.init(aesKey, Cipher.MODE_ENCRYPT, scratch, OFF_CB, BLOCK_LEN);
// CTR is symmetric: encrypting the ciphertext with the same
// keystream produces the plaintext.
aesCtr.doFinal(in, inOff, ctLen, out, outOff);
} else {
short produced = 0;
while (produced < ctLen) {
short blockLen = (short) (ctLen - produced);
if (blockLen > BLOCK_LEN) blockLen = BLOCK_LEN;
aesEcb.doFinal(scratch, OFF_CB, BLOCK_LEN, scratch, OFF_ECB_OUT);
for (short i = 0; i < blockLen; i++) {
out[(short) (outOff + produced + i)] =
(byte) (in[(short) (inOff + produced + i)]
^ scratch[(short) (OFF_ECB_OUT + i)]);
}
inc32(scratch, OFF_CB);
produced += blockLen;
}
}
Util.arrayFillNonAtomic(scratch, (short) 0, SCRATCH_LEN, (byte) 0);
return ctLen;
}
/**
* Per-(key, IV) GCM setup, shared by {@link #encrypt} and {@link #decrypt}:
* loads the AES-256 key, derives H = AES_K(0^128), rebuilds the 4-bit
* GHASH M-table, and lays down J0 = IV || 0x00000001 at OFF_J0. M2D.3's
* stream-encrypt path calls {@link #encrypt} twice per Step-Up session
* with a different (key, IV) each time; consolidating the rekey path
* keeps that contract pinned to one method.
*/
private void setKeyAndIv(
byte[] key, short keyOff,
byte[] iv, short ivOff) {
aesKey.setKey(key, keyOff);
aesEcb.init(aesKey, Cipher.MODE_ENCRYPT);
// H = AES_K(0^128). Zero scratch[OFF_H..OFF_H+16) and encrypt in place.
Util.arrayFillNonAtomic(scratch, OFF_H, BLOCK_LEN, (byte) 0);
aesEcb.doFinal(scratch, OFF_H, BLOCK_LEN, scratch, OFF_H);
// Build the 4-bit GHASH M-table from the fresh H. One-time cost per
// encrypt (~700 JC bytecodes), amortized over the ~10 gfMul4Bit calls.
buildMTable();
// J0 = IV || 0x00000001 (12B IV + 4B counter). 96-bit IV canonical case.
Util.arrayCopyNonAtomic(iv, ivOff, scratch, OFF_J0, IV_LEN);
scratch[(short) (OFF_J0 + 12)] = 0x00;
scratch[(short) (OFF_J0 + 13)] = 0x00;
scratch[(short) (OFF_J0 + 14)] = 0x00;
scratch[(short) (OFF_J0 + 15)] = 0x01;
}
/**
* INC32 per NIST SP 800-38D §6.2: increments the last 4 bytes of the
* 16-byte block, big-endian, modulo 2^32.
*/
/**
* Builds the 16-entry M-table from H in scratch[OFF_H..OFF_H+16). Each
* entry M[i] = i_poly · H where i_poly is the polynomial whose bit-j
* coefficient is (i >> (3-j)) & 1, i.e. MSB of i (bit 3) is the α^0 term.
*
* <p>Iterative construction needing 3 shifts + 11 XORs:
* M[8] = H, M[4] = M[8] >> 1, M[2] = M[4] >> 1, M[1] = M[2] >> 1
* M[i] for non-power-of-two i = XOR of M[2^k] for each set bit k.
*/
private void buildMTable() {
// M[0] = 0
Util.arrayFillNonAtomic(mTable, (short) 0, BLOCK_LEN, (byte) 0);
// M[8] = H
Util.arrayCopyNonAtomic(scratch, OFF_H,
mTable, (short) (8 * BLOCK_LEN), BLOCK_LEN);
// M[4] = M[8] >> 1
shiftRight1WithR(mTable, (short) (8 * BLOCK_LEN),
mTable, (short) (4 * BLOCK_LEN));
// M[2] = M[4] >> 1
shiftRight1WithR(mTable, (short) (4 * BLOCK_LEN),
mTable, (short) (2 * BLOCK_LEN));
// M[1] = M[2] >> 1
shiftRight1WithR(mTable, (short) (2 * BLOCK_LEN),
mTable, (short) (1 * BLOCK_LEN));
// M[3, 5, 6, 7, 9..15] = XOR combinations of M[1], M[2], M[4], M[8].
// Inner loop unrolled to dodge JC's "int promotion" rejection on
// expressions like `1 << bit` and `(1 << bit) * BLOCK_LEN`.
for (short i = 3; i < 16; i++) {
if (i == 4 || i == 8) continue; // pure powers already built
short destOff = (short) (i * BLOCK_LEN);
Util.arrayFillNonAtomic(mTable, destOff, BLOCK_LEN, (byte) 0);
// bit 0 set -> XOR M[1]
if ((i & 0x01) != 0) {
short srcOff = (short) (1 * BLOCK_LEN);
for (short k = 0; k < BLOCK_LEN; k++) {
mTable[(short) (destOff + k)] ^= mTable[(short) (srcOff + k)];
}
}
// bit 1 set -> XOR M[2]
if ((i & 0x02) != 0) {
short srcOff = (short) (2 * BLOCK_LEN);
for (short k = 0; k < BLOCK_LEN; k++) {
mTable[(short) (destOff + k)] ^= mTable[(short) (srcOff + k)];
}
}
// bit 2 set -> XOR M[4]
if ((i & 0x04) != 0) {
short srcOff = (short) (4 * BLOCK_LEN);
for (short k = 0; k < BLOCK_LEN; k++) {
mTable[(short) (destOff + k)] ^= mTable[(short) (srcOff + k)];
}
}
// bit 3 set -> XOR M[8]
if ((i & 0x08) != 0) {
short srcOff = (short) (8 * BLOCK_LEN);
for (short k = 0; k < BLOCK_LEN; k++) {
mTable[(short) (destOff + k)] ^= mTable[(short) (srcOff + k)];
}
}
}
}
/**
* Shifts 16 bytes right by 1 bit (toward higher indices in NIST-reflected
* representation) and applies the GHASH reduction polynomial R =
* 0xE1 || 0^120 at byte 0 if the dropped LSB was set. Used only by
* {@link #buildMTable}; the per-multiply path uses shiftRight4WithL.
*/
private static void shiftRight1WithR(
byte[] srcBuf, short srcOff,
byte[] dstBuf, short dstOff) {
boolean lsb = (srcBuf[(short) (srcOff + 15)] & 0x01) != 0;
short carry = 0;
for (short k = 0; k < BLOCK_LEN; k++) {
short cur = (short) (srcBuf[(short) (srcOff + k)] & 0xFF);
short newCur = (short) ((cur >>> 1) | carry);
carry = (short) ((cur & 0x01) << 7);
dstBuf[(short) (dstOff + k)] = (byte) newCur;
}
if (lsb) {
dstBuf[dstOff] ^= (byte) 0xE1;
}
}
/**
* In-place shift of scratch[OFF_ECB_OUT..OFF_ECB_OUT+16) right by 4 bits,
* applying the L-table reduction for whatever 4 bits dropped off the LSB
* end. This is the per-multiply hot loop's shift primitive, ~3-4× faster
* than four consecutive shiftRight1WithR calls because the byte shift is
* one pass and the reduction is two byte XORs from precomputed L tables.
*/
private void shiftRight4WithL() {
// The 4 bits about to be dropped = low nibble of byte 15.
short droppedNibble = (short) (scratch[(short) (OFF_ECB_OUT + 15)] & 0x0F);
// Shift bytes right by 4 bits: byte k becomes (byte_{k-1} << 4) | (byte_k >> 4).
// Walk high to low so we don't read overwritten data.
for (short k = 15; k > 0; k--) {
short cur = (short) (scratch[(short) (OFF_ECB_OUT + k)] & 0xFF);
short prev = (short) (scratch[(short) (OFF_ECB_OUT + k - 1)] & 0xFF);
scratch[(short) (OFF_ECB_OUT + k)] =
(byte) ((cur >>> 4) | ((prev & 0x0F) << 4));
}
short cur0 = (short) (scratch[OFF_ECB_OUT] & 0xFF);
scratch[OFF_ECB_OUT] = (byte) (cur0 >>> 4);
// Apply reduction via L-table: bytes 2..15 of L[n] are always 0 for
// the NIST GHASH polynomial, so we only XOR bytes 0 and 1.
scratch[OFF_ECB_OUT] ^= L_BYTE0[droppedNibble];
scratch[(short) (OFF_ECB_OUT + 1)] ^= L_BYTE1[droppedNibble];
}
/**
* 4-bit GHASH multiply: Z = X · H using the precomputed {@link #mTable}.
* Reads X from {@code xBuf[xOff..xOff+16)} and writes the result to
* scratch[OFF_ECB_OUT..OFF_ECB_OUT+16). Caller is responsible for copying
* the result back to OFF_GHASH between multiplies (mirrors the existing
* {@link #gfMul} contract).
*
* <p>Algorithm (LSB-first Horner):
* <pre>
* Z = 0
* for each nibble of X, LSB-first:
* Z = (Z >> 4 with L reduction) XOR M[nibble]
* </pre>
* 32 iterations vs the bit-by-bit form's 128, plus the inner shift is
* ~4× cheaper. Net ~3-4× speedup on the J3R-family bytecode interpreter.
*/
private void gfMul4Bit(byte[] xBuf, short xOff) {
Util.arrayFillNonAtomic(scratch, OFF_ECB_OUT, BLOCK_LEN, (byte) 0);
for (short byteIdx = 15; byteIdx >= 0; byteIdx--) {
short xByte = (short) (xBuf[(short) (xOff + byteIdx)] & 0xFF);
// Low nibble first (LSB of byte, processed before high nibble).
short nLo = (short) (xByte & 0x0F);
shiftRight4WithL();
if (nLo != 0) {
short mOff = (short) (nLo * BLOCK_LEN);
for (short k = 0; k < BLOCK_LEN; k++) {
scratch[(short) (OFF_ECB_OUT + k)] ^=
mTable[(short) (mOff + k)];
}
}
// High nibble.
short nHi = (short) ((xByte >>> 4) & 0x0F);
shiftRight4WithL();
if (nHi != 0) {
short mOff = (short) (nHi * BLOCK_LEN);
for (short k = 0; k < BLOCK_LEN; k++) {
scratch[(short) (OFF_ECB_OUT + k)] ^=
mTable[(short) (mOff + k)];
}
}
}
}
private static void inc32(byte[] buf, short off) {
short i = (short) (off + 15);
// Carry propagation across 4 bytes.
if ((byte) (++buf[i]) != (byte) 0) return;
i--;
if ((byte) (++buf[i]) != (byte) 0) return;
i--;
if ((byte) (++buf[i]) != (byte) 0) return;
i--;
buf[i]++;
}
/**
* In-place GF(2^128) multiplication per NIST SP 800-38D §6.3, storing
* X ·_GF Y into X. Uses the bit-reflected convention:
* <ul>
* <li>byte 0 MSB = x^127 (highest-degree polynomial coefficient)
* <li>byte 15 LSB = x^0
* <li>reduction polynomial R = x^128 + x^7 + x^2 + x + 1 represented
* as {0xE1, 0x00, ..., 0x00}
* <li>"shift right" means shift toward higher byte indices
* </ul>
*
* <p>Algorithm 1 from NIST SP 800-38D:
* <pre>
* Z = 0; V = Y
* for i = 0..127:
* if bit i of X is 1: Z ^= V
* if lowest bit of V (byte 15 bit 0) is 1: V = (V >> 1) ^ R
* else: V = V >> 1
* return Z
* </pre>
*
* <p>This is the simple, portable NIST shift-XOR form. A 4-bit-table
* variant would be ~4x faster but adds 32 bytes of per-H precomputation
* and a more intricate shift; we encrypt ~137B per AUTH1, so the extra
* cycles are acceptable versus the complexity cost.
*/
private void gfMul(byte[] xBuf, short xOff, byte[] yBuf, short yOff) {
// V = Y (copied into OFF_TMP scratch region; gets shifted in place)
Util.arrayCopyNonAtomic(yBuf, yOff, scratch, OFF_TMP, BLOCK_LEN);
// Z = 0 (we'll accumulate in a second 16-byte region — reuse OFF_LEN_BLK,
// which is free until after the main GHASH loop finishes). gfMul is
// called both during GHASH accumulation AND during final-length mixing;
// at final-length mixing OFF_LEN_BLK has already been consumed, so it's
// safe to overwrite. But to avoid an aliasing bug if OFF_LEN_BLK is
// ever reused differently, we stage Z in OFF_ECB_OUT instead.
//
// Wait — OFF_ECB_OUT holds the AES keystream we need later for the
// tag (T = AES_K(J0) XOR GHASH). However, gfMul is never called
// between producing OFF_ECB_OUT (the AES_K(J0) output) and consuming
// it for the tag XOR. The last gfMul is for (state ^ len_block) ·_GF H,
// and only AFTER that do we compute AES_K(J0) into OFF_ECB_OUT. So
// using OFF_ECB_OUT as the Z accumulator is safe.
Util.arrayFillNonAtomic(scratch, OFF_ECB_OUT, BLOCK_LEN, (byte) 0);
for (short byteIdx = 0; byteIdx < BLOCK_LEN; byteIdx++) {
byte xByte = xBuf[(short) (xOff + byteIdx)];
for (short bit = 7; bit >= 0; bit--) {
// bit of X at polynomial position (byteIdx*8 + (7-bit)): byte-high bit first.
if (((xByte >> bit) & 0x01) != 0) {
for (short k = 0; k < BLOCK_LEN; k++) {
scratch[(short) (OFF_ECB_OUT + k)] ^= scratch[(short) (OFF_TMP + k)];
}
}
// V = V >> 1; if the bit that fell off (byte 15 low bit BEFORE shift) is 1,
// also XOR the reduction polynomial {0xE1, 0x00, ..., 0x00}.
boolean lsb = (scratch[(short) (OFF_TMP + 15)] & 0x01) != 0;
// Shift V right by 1 bit (toward higher byte indices).
short carry = 0;
for (short k = 0; k < BLOCK_LEN; k++) {
short cur = (short) (scratch[(short) (OFF_TMP + k)] & 0xFF);
short newCur = (short) ((cur >>> 1) | carry);
carry = (short) ((cur & 0x01) << 7);
scratch[(short) (OFF_TMP + k)] = (byte) newCur;
}
if (lsb) {
scratch[OFF_TMP] ^= (byte) 0xE1;
}
}
}
// Copy Z back into X in place.
Util.arrayCopyNonAtomic(scratch, OFF_ECB_OUT, xBuf, xOff, BLOCK_LEN);
}
}