GordianSpeckEngine.java
/*
* GordianKnot: Security Suite
* Copyright 2012-2026. Tony Washer
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package io.github.tonywasher.joceanus.gordianknot.impl.ext.engines;
import org.bouncycastle.crypto.BlockCipher;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.util.Pack;
/**
* Speck Cipher engine.
* <p>Cut down version of Tim Whittington's implementation available at
* https://github.com/timw/bc-java/blob/feature/simon-speck/core/src/main/java/org/bouncycastle/crypto/engines/SpeckEngine.java
* </p>
*/
public class GordianSpeckEngine
implements BlockCipher {
/**
* Base number of rounds.
*/
private static final int BASEROUNDS = 32;
/**
* Number of words in state.
*/
private static final int NUMWORDS = 2;
/**
* BlockSize.
*/
private static final int BLOCKSIZE = NUMWORDS * Long.BYTES;
/**
* Rotate3.
*/
private static final int ROT3 = 3;
/**
* Rotate8.
*/
private static final int ROT8 = 8;
/**
* The # of rounds.
*/
private int theRounds;
/**
* The expanded key schedule.
*/
private long[] theRoundKeys;
/**
* Are we encrypting?
*/
private boolean forEncryption;
@Override
public void init(final boolean pEncrypt,
final CipherParameters pParams) {
/* Reject invalid parameters */
if (!(pParams instanceof KeyParameter)) {
throw new IllegalArgumentException("Invalid parameter passed to Speck init - "
+ pParams.getClass().getName());
}
/* Validate keyLength */
final byte[] myKey = ((KeyParameter) pParams).getKey();
final int myKeyLen = myKey.length;
if ((((myKeyLen << 1) % BLOCKSIZE) != 0)
|| myKeyLen < BLOCKSIZE
|| myKeyLen > (BLOCKSIZE << 1)) {
throw new IllegalArgumentException("KeyBitSize must be 128, 192 or 256");
}
/* Generate the round keys */
forEncryption = pEncrypt;
generateRoundKeys(myKey);
}
@Override
public void reset() {
/* NoOp */
}
@Override
public String getAlgorithmName() {
return "Speck";
}
@Override
public int getBlockSize() {
return BLOCKSIZE;
}
@Override
public int processBlock(final byte[] pInput,
final int pInOff,
final byte[] pOutput,
final int pOutOff) {
/* Check buffers */
if (pInput == null || pInput.length - pInOff < BLOCKSIZE) {
throw new IllegalArgumentException("Invalid input buffer");
}
if (pOutput == null || pOutput.length - pOutOff < BLOCKSIZE) {
throw new IllegalArgumentException("Invalid output buffer");
}
/* Perform the encryption/decryption */
return forEncryption
? encryptBlock(pInput, pInOff, pOutput, pOutOff)
: decryptBlock(pInput, pInOff, pOutput, pOutOff);
}
/**
* Encrypt a block.
*
* @param pInput the input buffer
* @param pInOff the input offset
* @param pOutput the output offset
* @param pOutOff the output offset
* @return the bytes processed
*/
private int encryptBlock(final byte[] pInput,
final int pInOff,
final byte[] pOutput,
final int pOutOff) {
/* Load the bytes into the block */
long myX = Pack.bigEndianToLong(pInput, pInOff);
long myY = Pack.bigEndianToLong(pInput, pInOff + Long.BYTES);
/* Loop through the rounds */
for (int i = 0; i < theRounds; i++) {
/* Perform the encryption round */
myX = (ror64(myX, ROT8) + myY) ^ theRoundKeys[i];
myY = rol64(myY, ROT3) ^ myX;
}
/* Output the bytes from the block */
Pack.longToBigEndian(myX, pOutput, pOutOff);
Pack.longToBigEndian(myY, pOutput, pOutOff + Long.BYTES);
/* Return # of bytes processed */
return BLOCKSIZE;
}
/**
* Decrypt a block.
*
* @param pInput the input buffer
* @param pInOff the input offset
* @param pOutput the output offset
* @param pOutOff the output offset
* @return the bytes processed
*/
private int decryptBlock(final byte[] pInput,
final int pInOff,
final byte[] pOutput,
final int pOutOff) {
/* Load the bytes into the block */
long myX = Pack.bigEndianToLong(pInput, pInOff);
long myY = Pack.bigEndianToLong(pInput, pInOff + Long.BYTES);
/* Loop through the rounds */
for (int i = theRounds - 1; i >= 0; i--) {
/* Perform the decryption round */
myY = ror64(myX ^ myY, ROT3);
myX = rol64((myX ^ theRoundKeys[i]) - myY, ROT8);
}
/* Output the bytes from the block */
Pack.longToBigEndian(myX, pOutput, pOutOff);
Pack.longToBigEndian(myY, pOutput, pOutOff + Long.BYTES);
/* Return # of bytes processed */
return BLOCKSIZE;
}
/**
* Generate the round keys.
*
* @param pKey the key
*/
private void generateRoundKeys(final byte[] pKey) {
/* Determine number of key words */
final int numWords = pKey.length / Long.BYTES;
/* Number of rounds is increased by 1 for each key word > 2 */
theRounds = BASEROUNDS + (numWords - 2);
theRoundKeys = new long[theRounds];
/* Load base key */
theRoundKeys[0] = Pack.bigEndianToLong(pKey, (numWords - 1) * Long.BYTES);
/* Load remaining key bytes */
final long[] myL = new long[numWords];
for (int i = 0; i < numWords - 1; i++) {
myL[i] = Pack.bigEndianToLong(pKey, (numWords - i - 2) * Long.BYTES);
}
/* Key expansion using round function with round number as key */
for (int i = 0; i < theRounds - 1; i++) {
final int lw = (i + numWords - 1) % numWords;
myL[lw] = (ror64(myL[i % numWords], ROT8) + theRoundKeys[i]) ^ i;
theRoundKeys[i + 1] = rol64(theRoundKeys[i], ROT3) ^ myL[lw];
}
}
/**
* rotate left.
*
* @param pValue the value to rotate
* @param pBits the # of bits to rotate
* @return the rotated value
*/
private static long rol64(final long pValue,
final long pBits) {
return (pValue << pBits) | (pValue >>> (Long.SIZE - pBits));
}
/**
* rotate right.
*
* @param pValue the value to rotate
* @param pBits the # of bits to rotate
* @return the rotated value
*/
private static long ror64(final long pValue,
final long pBits) {
return (pValue >>> pBits) | (pValue << (Long.SIZE - pBits));
}
}