org.bouncycastle.crypto.engines.XoodyakEngine Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of bcprov-debug-jdk15to18 Show documentation
Show all versions of bcprov-debug-jdk15to18 Show documentation
The Bouncy Castle Crypto package is a Java implementation of cryptographic algorithms. This jar contains JCE provider and lightweight API for the Bouncy Castle Cryptography APIs for JDK 1.5 to JDK 1.8.
package org.bouncycastle.crypto.engines;
import java.io.ByteArrayOutputStream;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.CryptoServicesRegistrar;
import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.OutputLengthException;
import org.bouncycastle.crypto.constraints.DefaultServiceProperties;
import org.bouncycastle.crypto.modes.AEADCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.Pack;
/**
* Xoodyak v1, https://csrc.nist.gov/CSRC/media/Projects/lightweight-cryptography/documents/finalist-round/updated-spec-doc/xoodyak-spec-final.pdf
*
* Xoodyak with reference to C Reference Impl from: https://github.com/XKCP/XKCP
*
*/
public class XoodyakEngine
implements AEADCipher
{
private boolean forEncryption;
private byte[] state;
private int phase;
private MODE mode;
private int Rabsorb;
private final int f_bPrime = 48;
private final int Rkout = 24;
private byte[] K;
private byte[] iv;
private final int PhaseDown = 1;
private final int PhaseUp = 2;
private final int NLANES = 12;
private final int NROWS = 3;
private final int NCOLUMS = 4;
private final int MAXROUNDS = 12;
private final int TAGLEN = 16;
final int Rkin = 44;
private byte[] tag;
private final int[] RC = {0x00000058, 0x00000038, 0x000003C0, 0x000000D0, 0x00000120, 0x00000014, 0x00000060,
0x0000002C, 0x00000380, 0x000000F0, 0x000001A0, 0x00000012};
private boolean aadFinished;
private boolean encrypted;
private boolean initialised = false;
private final ByteArrayOutputStream aadData = new ByteArrayOutputStream();
private final ByteArrayOutputStream message = new ByteArrayOutputStream();
enum MODE
{
ModeHash,
ModeKeyed
}
@Override
public void init(boolean forEncryption, CipherParameters params)
throws IllegalArgumentException
{
this.forEncryption = forEncryption;
if (!(params instanceof ParametersWithIV))
{
throw new IllegalArgumentException("Xoodyak init parameters must include an IV");
}
ParametersWithIV ivParams = (ParametersWithIV)params;
iv = ivParams.getIV();
if (iv == null || iv.length != 16)
{
throw new IllegalArgumentException("Xoodyak requires exactly 16 bytes of IV");
}
if (!(ivParams.getParameters() instanceof KeyParameter))
{
throw new IllegalArgumentException("Xoodyak init parameters must include a key");
}
KeyParameter key = (KeyParameter)ivParams.getParameters();
K = key.getKey();
if (K.length != 16)
{
throw new IllegalArgumentException("Xoodyak key must be 128 bits long");
}
CryptoServicesRegistrar.checkConstraints(new DefaultServiceProperties(
this.getAlgorithmName(), 128, params, Utils.getPurpose(forEncryption)));
state = new byte[48];
tag = new byte[TAGLEN];
initialised = true;
reset();
}
@Override
public String getAlgorithmName()
{
return "Xoodyak AEAD";
}
@Override
public void processAADByte(byte input)
{
if (aadFinished)
{
throw new IllegalArgumentException("AAD cannot be added after reading a full block(" + getBlockSize() +
" bytes) of input for " + (forEncryption ? "encryption" : "decryption"));
}
aadData.write(input);
}
@Override
public void processAADBytes(byte[] input, int inOff, int len)
{
if (aadFinished)
{
throw new IllegalArgumentException("AAD cannot be added after reading a full block(" + getBlockSize() +
" bytes) of input for " + (forEncryption ? "encryption" : "decryption"));
}
if ((inOff + len) > input.length)
{
throw new DataLengthException("input buffer too short");
}
aadData.write(input, inOff, len);
}
@Override
public int processByte(byte input, byte[] output, int outOff)
throws DataLengthException
{
return processBytes(new byte[]{input}, 0, 1, output, outOff);
}
private void processAAD()
{
if (!aadFinished)
{
byte[] ad = aadData.toByteArray();
AbsorbAny(ad, 0, ad.length, Rabsorb, 0x03);
aadFinished = true;
}
}
@Override
public int processBytes(byte[] input, int inOff, int len, byte[] output, int outOff)
throws DataLengthException
{
if (!initialised)
{
throw new IllegalArgumentException("Need call init function before encryption/decryption");
}
if (mode != MODE.ModeKeyed)
{
throw new IllegalArgumentException("Xoodyak has not been initialised");
}
if (inOff + len > input.length)
{
throw new DataLengthException("input buffer too short");
}
message.write(input, inOff, len);
int blockLen = message.size() - (forEncryption ? 0 : TAGLEN);
if (blockLen >= getBlockSize())
{
byte[] blocks = message.toByteArray();
len = blockLen / getBlockSize() * getBlockSize();
if (len + outOff > output.length)
{
throw new OutputLengthException("output buffer is too short");
}
processAAD();
encrypt(blocks, 0, len, output, outOff);
message.reset();
message.write(blocks, len, blocks.length - len);
return len;
}
return 0;
}
private int encrypt(byte[] input, int inOff, int len, byte[] output, int outOff)
{
int IOLen = len;
int splitLen;
byte[] P = new byte[Rkout];
int Cu = encrypted ? 0 : 0x80;
while (IOLen != 0 || !encrypted)
{
splitLen = Math.min(IOLen, Rkout); /* use Rkout instead of Rsqueeze, this function is only called in keyed mode */
if (forEncryption)
{
System.arraycopy(input, inOff, P, 0, splitLen);
}
Up(null, 0, Cu); /* Up without extract */
/* Extract from Up and Add */
for (int i = 0; i < splitLen; i++)
{
output[outOff + i] = (byte)(input[inOff++] ^ state[i]);
}
if (forEncryption)
{
Down(P, 0, splitLen, 0x00);
}
else
{
Down(output, outOff, splitLen, 0x00);
}
Cu = 0x00;
outOff += splitLen;
IOLen -= splitLen;
encrypted = true;
}
return len;
}
@Override
public int doFinal(byte[] output, int outOff)
throws IllegalStateException, InvalidCipherTextException
{
if (!initialised)
{
throw new IllegalArgumentException("Need call init function before encryption/decryption");
}
byte[] blocks = message.toByteArray();
int len = message.size();
if ((forEncryption && len + TAGLEN + outOff > output.length) || (!forEncryption && len - TAGLEN + outOff > output.length))
{
throw new OutputLengthException("output buffer too short");
}
processAAD();
int rv = 0;
if (forEncryption)
{
encrypt(blocks, 0, len, output, outOff);
outOff += len;
tag = new byte[TAGLEN];
Up(tag, TAGLEN, 0x40);
System.arraycopy(tag, 0, output, outOff, TAGLEN);
rv = len + TAGLEN;
}
else
{
int inOff = len - TAGLEN;
rv = inOff;
encrypt(blocks, 0, inOff, output, outOff);
tag = new byte[TAGLEN];
Up(tag, TAGLEN, 0x40);
for (int i = 0; i < TAGLEN; ++i)
{
if (tag[i] != blocks[inOff++])
{
throw new IllegalArgumentException("Mac does not match");
}
}
}
reset(false);
return rv;
}
@Override
public byte[] getMac()
{
return tag;
}
@Override
public int getUpdateOutputSize(int len)
{
return len;
}
@Override
public int getOutputSize(int len)
{
return len + TAGLEN;
}
@Override
public void reset()
{
if (!initialised)
{
throw new IllegalArgumentException("Need call init function before encryption/decryption");
}
reset(true);
}
private void reset(boolean clearMac)
{
if (clearMac)
{
tag = null;
}
Arrays.fill(state, (byte)0);
aadFinished = false;
encrypted = false;
phase = PhaseUp;
message.reset();
aadData.reset();
//Absorb key
int KLen = K.length;
int IDLen = iv.length;
byte[] KID = new byte[Rkin];
mode = MODE.ModeKeyed;
Rabsorb = Rkin;
System.arraycopy(K, 0, KID, 0, KLen);
System.arraycopy(iv, 0, KID, KLen, IDLen);
KID[KLen + IDLen] = (byte)IDLen;
AbsorbAny(KID, 0, KLen + IDLen + 1, Rabsorb, 0x02);
}
private void AbsorbAny(byte[] X, int Xoff, int XLen, int r, int Cd)
{
int splitLen;
do
{
if (phase != PhaseUp)
{
Up(null, 0, 0);
}
splitLen = Math.min(XLen, r);
Down(X, Xoff, splitLen, Cd);
Cd = 0;
Xoff += splitLen;
XLen -= splitLen;
}
while (XLen != 0);
}
private void Up(byte[] Yi, int YiLen, int Cu)
{
if (mode != MODE.ModeHash)
{
state[f_bPrime - 1] ^= Cu;
}
int[] a = new int[NLANES];
Pack.littleEndianToInt(state, 0, a, 0, a.length);
int x, y;
int[] b = new int[NLANES];
int[] p = new int[NCOLUMS];
int[] e = new int[NCOLUMS];
for (int i = 0; i < MAXROUNDS; ++i)
{
/* Theta: Column Parity Mixer */
for (x = 0; x < NCOLUMS; ++x)
{
p[x] = a[index(x, 0)] ^ a[index(x, 1)] ^ a[index(x, 2)];
}
for (x = 0; x < NCOLUMS; ++x)
{
y = p[(x + 3) & 3];
e[x] = ROTL32(y, 5) ^ ROTL32(y, 14);
}
for (x = 0; x < NCOLUMS; ++x)
{
for (y = 0; y < NROWS; ++y)
{
a[index(x, y)] ^= e[x];
}
}
/* Rho-west: plane shift */
for (x = 0; x < NCOLUMS; ++x)
{
b[index(x, 0)] = a[index(x, 0)];
b[index(x, 1)] = a[index(x + 3, 1)];
b[index(x, 2)] = ROTL32(a[index(x, 2)], 11);
}
/* Iota: round ant */
b[0] ^= RC[i];
/* Chi: non linear layer */
for (x = 0; x < NCOLUMS; ++x)
{
for (y = 0; y < NROWS; ++y)
{
a[index(x, y)] = b[index(x, y)] ^ (~b[index(x, y + 1)] & b[index(x, y + 2)]);
}
}
/* Rho-east: plane shift */
for (x = 0; x < NCOLUMS; ++x)
{
b[index(x, 0)] = a[index(x, 0)];
b[index(x, 1)] = ROTL32(a[index(x, 1)], 1);
b[index(x, 2)] = ROTL32(a[index(x + 2, 2)], 8);
}
System.arraycopy(b, 0, a, 0, NLANES);
}
Pack.intToLittleEndian(a, 0, a.length, state, 0);
phase = PhaseUp;
if (Yi != null)
{
System.arraycopy(state, 0, Yi, 0, YiLen);
}
}
void Down(byte[] Xi, int XiOff, int XiLen, int Cd)
{
for (int i = 0; i < XiLen; i++)
{
state[i] ^= Xi[XiOff++];
}
state[XiLen] ^= 0x01;
state[f_bPrime - 1] ^= (mode == MODE.ModeHash) ? (Cd & 0x01) : Cd;
phase = PhaseDown;
}
private int index(int x, int y)
{
return (((y % NROWS) * NCOLUMS) + ((x) % NCOLUMS));
}
private int ROTL32(int a, int offset)
{
return (a << (offset & 31)) ^ (a >>> ((32 - (offset)) & 31));
}
public int getBlockSize()
{
return Rkout;
}
public int getKeyBytesSize()
{
return 16;
}
public int getIVBytesSize()
{
return 16;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy