java.lang.Object
net.i2p.crypto.AESEngine
net.i2p.crypto.CryptixAESEngine

public final class CryptixAESEngineextends AESEngine
AES encryption engine using Cryptix's Rijndael implementation with CBC mode. Supports 128-bit block size with 16-byte IV. No padding is provided.
  • Field Details

    • _ciphers

      private final LinkedBlockingQueue<Cipher> _ciphers
      Initialized ciphers ready for reuse.
    • MIN_SYSTEM_AES_LENGTH

      private static final int MIN_SYSTEM_AES_LENGTH
      Smallest message where the system AES is worth using, in bytes.
      See Also:
    • USE_SYSTEM_AES

      private static final boolean USE_SYSTEM_AES
      True to use the JVM's AES when it is genuinely faster.
    • CACHE

      private static final boolean CACHE
      Whether initialized ciphers are pooled.
      See Also:
    • CACHE_SIZE

      private static final int CACHE_SIZE
      Ciphers held for reuse.
      See Also:
    • KEY_SPEC_CACHE_SIZE

      static final int KEY_SPEC_CACHE_SIZE
      Upper bound on the per-session SecretKeySpec memo cache. Plenty for the handful of concurrent transport and session keys the router works with at any moment. Package-visible for the unit tests.
      See Also:
    • _keySpecs

      private final Map<SessionKey, SecretKeySpec> _keySpecs
      Memoized SecretKeySpec per SessionKey for the system-AES path.

      Why memoize at all: building the spec clones the key material, but SecretKeySpec.getEncoded() returns the spec's *internal* array, so a reused instance passes the same byte[] reference to Cipher.init(int, Key) and skips JCE makeSessionKey re-expansion of the 256-bit key on every JVM-path encrypt/decrypt.

      This cache is deliberately NOT the same as SessionKey.getPreparedKey(), which is owned by the legacy Cryptix path and lives per SessionKey - keep the two fast paths decoupled. SessionKey equality is value-based, so distinct-but-identical keys share one spec (their bytes are equal, so sharing is correct).

      DO NOT make this map access-ordered: with accessOrder=true, get() counts as a structural modification, so every lookup would need a lock and that would serialize every AES op of at least MIN_SYSTEM_AES_LENGTH bytes across the whole router. Insertion order keeps lookups lock-free and eviction drops the eldest, not the coldest.

    • _keySpecReads

      private final ConcurrentMap<SessionKey, SecretKeySpec> _keySpecReads
      Lock-free read side of the _keySpecs memo. Written only inside the synchronized(_keySpecs) miss path, so it is always a mirror of _keySpecs (plus, transiently, the entry being evicted). Bounded by the same KEY_SPEC_CACHE_SIZE.
  • Constructor Details

    • CryptixAESEngine

      public CryptixAESEngine(I2PAppContext context)
      Create the engine, allocating the cipher cache only when the system AES-NI provider is in use.
      Parameters:
      context - the app context supplying the provider and the RNG
  • Method Details

    • hasAESNI

      private static boolean hasAESNI()
      Check for AES support in processor and JVM.

      On x86-64 this consults CPUID when libjcpuid is available; a missing helper library must not silently disable the JDK path, so the JDK's own implementation (AES-NI intrinsics where present, tables otherwise) is assumed instead - it is at least as good as the bundled Cryptix tables. On other 64-bit ABIs (notably AArch64, which has AES in the base ISA) the JDK path is used as well. 32-bit and non-mainstream JVMs keep the Cryptix tables.

      Returns:
      whether the JDK Cipher path should be used
    • encrypt

      public void encrypt(byte[] payload, int payloadIndex, byte[] out, int outIndex, SessionKey sessionKey, byte[] iv, int length)
      Encrypt the payload with the session key.
      Overrides:
      encrypt in class AESEngine
      Parameters:
      payload - data to be encrypted
      payloadIndex - index into the payload to start encrypting
      out - where to store the result
      outIndex - where in out to start writing
      sessionKey - private esession key to encrypt to
      iv - must be 16 bytes
      length - must be a multiple of 16
    • encrypt

      public void encrypt(byte[] payload, int payloadIndex, byte[] out, int outIndex, SessionKey sessionKey, byte[] iv, int ivOffset, int length)
      Encrypt the payload with the session key.
      Overrides:
      encrypt in class AESEngine
      Parameters:
      payload - data to be encrypted
      payloadIndex - index into payload to start encrypting
      out - where to store result
      outIndex - where in out to start writing
      sessionKey - private session key to encrypt to
      iv - must be 16 bytes
      ivOffset - offset into IV
      length - must be a multiple of 16
    • decrypt

      public void decrypt(byte[] payload, int payloadIndex, byte[] out, int outIndex, SessionKey sessionKey, byte[] iv, int length)
      Decrypt the payload with the session key.
      Overrides:
      decrypt in class AESEngine
      Parameters:
      payload - data to be decrypted
      payloadIndex - index into the payload to start decrypting
      out - where to store the cleartext
      outIndex - where in out to start writing
      sessionKey - private session key to decrypt to
      iv - 16 bytes
      length - must be a multiple of 16 (will overrun to next mod 16 if not)
    • decrypt

      public void decrypt(byte[] payload, int payloadIndex, byte[] out, int outIndex, SessionKey sessionKey, byte[] iv, int ivOffset, int length)
      Decrypt the payload with the session key.
      Overrides:
      decrypt in class AESEngine
      Parameters:
      payload - data to be decrypted
      payloadIndex - index into the payload to start decrypting
      out - where to store the cleartext
      outIndex - where in out to start writing
      sessionKey - private session key to decrypt to
      iv - 16 bytes starting at ivOffset
      ivOffset - offset into IV
      length - must be a multiple of 16 (will overrun to next mod 16 if not)
    • encryptBlock

      public final void encryptBlock(byte[] payload, int inIndex, SessionKey sessionKey, byte[] out, int outIndex)
      Encrypt exactly 16 bytes using the session key.
      Overrides:
      encryptBlock in class AESEngine
      Parameters:
      payload - plaintext data, 16 bytes starting at inIndex
      inIndex - starting index in payload
      sessionKey - private session key
      out - out parameter, 16 bytes starting at outIndex
      outIndex - starting index in output
    • decryptBlock

      public final void decryptBlock(byte[] payload, int inIndex, SessionKey sessionKey, byte[] rv, int outIndex)
      Decrypt exactly 16 bytes of data with the session key provided.
      Overrides:
      decryptBlock in class AESEngine
      Parameters:
      payload - encrypted data, 16 bytes starting at inIndex
      inIndex - starting index in payload
      sessionKey - private session key
      rv - out parameter, 16 bytes starting at outIndex
      outIndex - starting index in output
    • getKeySpec

      SecretKeySpec getKeySpec(SessionKey sessionKey)
      Get (or build and remember) the SecretKeySpec for a session key. Package-visible so the unit tests can verify reuse, longevity across sessions and the size bound.

      The hit path is lock-free: a ConcurrentHashMap lookup only. The miss path takes synchronized(_keySpecs) - that is the only place the KEY_SPEC_CACHE_SIZE bound is enforced, by evicting the eldest entry in insertion order.

      Parameters:
      sessionKey - the session key (value-equality, may be a fresh object each call carrying the same bytes as a cached one)
      Returns:
      a spec reusing the key's internal byte array; never null
    • acquire

      private Cipher acquire()
      Obtain a Cipher from the cache, or create a new one if empty.
      Returns:
      cached or new
    • release

      private void release(Cipher cipher)
      Return the Cipher to the cache without scrubbing it first.

      The pooled Cipher keeps the round keys of the operation that just finished. Re-initializing it with a zero key was dropped because Cipher.init(int, Key) at the next acquire overwrites the schedule anyway, and the extra key schedule is pure overhead. It protected nothing either: the live key bytes are already resident for the life of the SessionKey in the _keySpecReads memo and in SessionKey.getPreparedKey() on the legacy Cryptix path.

      Parameters:
      cipher - the Cipher to return to the cache