GordianZuc256Mac.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.macs;
import io.github.tonywasher.joceanus.gordianknot.impl.ext.engines.GordianZuc128Engine;
import io.github.tonywasher.joceanus.gordianknot.impl.ext.engines.GordianZuc256Engine;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.Mac;
/**
* Zuc128Engine implementation.
* Based on <a href="http://www.is.cas.cn/ztzl2016/zouchongzhi/201801/W020180126529970733243.pdf">Zuc Specification</a>
* Donated to BouncyCastle.
*/
public class GordianZuc256Mac
implements Mac {
/**
* The Maximum Bit Mask.
*/
private static final int TOPBIT = 0x80;
/**
* The Zuc256 Engine.
*/
private final GordianZuc256Engine theEngine;
/**
* The mac length.
*/
private final int theMacLength;
/**
* The calculated Mac in words.
*/
private final int[] theMac;
/**
* The active keyStream.
*/
private final int[] theKeyStream;
/**
* The initialised state.
*/
private GordianZuc256Engine theState;
/**
* The current word index.
*/
private int theWordIndex;
/**
* The current byte index.
*/
private int theByteIndex;
/**
* Constructor.
*
* @param pLength the bit length of the Mac
*/
public GordianZuc256Mac(final int pLength) {
theEngine = new GordianZuc256Engine(pLength);
theMacLength = pLength;
final int numWords = pLength / 32; // Integer.SIZE
theMac = new int[numWords];
theKeyStream = new int[numWords + 1];
}
/**
* Obtain Algorithm Name.
*
* @return the name
*/
public String getAlgorithmName() {
return "Zuc256Mac-" + theMacLength;
}
/**
* Obtain Mac Size.
*
* @return the size in Bytes
*/
public int getMacSize() {
return theMacLength / Byte.SIZE;
}
/**
* Initialise the Mac.
*
* @param pParams the parameters
*/
public void init(final CipherParameters pParams) {
/* Initialise the engine */
theEngine.init(true, pParams);
theState = theEngine.copy();
initKeyStream();
}
/**
* Initialise the keyStream.
*/
private void initKeyStream() {
/* Initialise the Mac */
for (int i = 0; i < theMac.length; i++) {
theMac[i] = theEngine.makeKeyStreamWord();
}
/* Initialise the KeyStream */
for (int i = 0; i < theKeyStream.length - 1; i++) {
theKeyStream[i] = theEngine.makeKeyStreamWord();
}
theWordIndex = theKeyStream.length - 1;
theByteIndex = Integer.BYTES - 1;
}
/**
* Update the mac with a single byte.
*
* @param in the byte to update with
*/
public void update(final byte in) {
/* shift for next byte */
shift4NextByte();
/* Loop through the bits */
final int bitBase = theByteIndex * Byte.SIZE;
for (int bitMask = TOPBIT, bitNo = 0; bitMask > 0; bitMask >>= 1, bitNo++) {
/* If the bit is set */
if ((in & bitMask) != 0) {
/* update theMac */
updateMac(bitBase + bitNo);
}
}
}
/**
* Shift for next byte.
*/
private void shift4NextByte() {
/* Adjust the byte index */
theByteIndex = (theByteIndex + 1) % Integer.BYTES;
/* Adjust keyStream if required */
if (theByteIndex == 0) {
theKeyStream[theWordIndex] = theEngine.makeKeyStreamWord();
theWordIndex = (theWordIndex + 1) % theKeyStream.length;
}
}
/**
* Shift for final update.
*/
private void shift4Final() {
/* Adjust the byte index */
theByteIndex = (theByteIndex + 1) % Integer.BYTES;
/* No need to read another word to the keyStream */
if (theByteIndex == 0) {
theWordIndex = (theWordIndex + 1) % theKeyStream.length;
}
}
/**
* Update the Mac.
*
* @param bitNo the bit number
*/
private void updateMac(final int bitNo) {
/* Loop through the Mac */
for (int wordNo = 0; wordNo < theMac.length; wordNo++) {
theMac[wordNo] ^= getKeyStreamWord(wordNo, bitNo);
}
}
/**
* Obtain the keyStreamWord.
*
* @param wordNo the wordNumber
* @param bitNo the bitNumber
* @return the word
*/
private int getKeyStreamWord(final int wordNo, final int bitNo) {
/* Access the first word and return it if this is bit 0 */
final int myFirst = theKeyStream[(theWordIndex + wordNo) % theKeyStream.length];
if (bitNo == 0) {
return myFirst;
}
/* Access the second word */
final int mySecond = theKeyStream[(theWordIndex + wordNo + 1) % theKeyStream.length];
return (myFirst << bitNo) | (mySecond >>> (Integer.SIZE - bitNo));
}
/**
* Update the mac.
*
* @param in the input buffer
* @param inOff the starting offset in the input buffer
* @param len the length of data to process
*/
public void update(final byte[] in, final int inOff, final int len) {
for (int byteNo = 0; byteNo < len; byteNo++) {
update(in[inOff + byteNo]);
}
}
/**
* Finalize the mac.
*
* @param out the output buffer
* @param outOff the starting offset in the output buffer
* @return the size of the mac
*/
public int doFinal(final byte[] out, final int outOff) {
/* shift for final update */
shift4Final();
/* Finish the Mac and output it */
updateMac(theByteIndex * Byte.SIZE);
for (int i = 0; i < theMac.length; i++) {
GordianZuc128Engine.encode32be(theMac[i], out, outOff + i * Integer.BYTES);
}
/* Reset the Mac */
reset();
return getMacSize();
}
/**
* Reset the Mac.
*/
public void reset() {
theEngine.reset(theState);
initKeyStream();
}
}