659 lines
30 KiB
Java
659 lines
30 KiB
Java
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 §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 §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);
|
||
}
|
||
}
|