GordianSkeinXofEngine.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 io.github.tonywasher.joceanus.gordianknot.impl.ext.digests.GordianSkeinBase;
import io.github.tonywasher.joceanus.gordianknot.impl.ext.digests.GordianSkeinXof;
import io.github.tonywasher.joceanus.gordianknot.impl.ext.params.GordianSkeinParameters.GordianSkeinParametersBuilder;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.crypto.OutputLengthException;
import org.bouncycastle.crypto.StreamCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.util.Memoable;

/**
 * SkeinXof used as a stream Cipher.
 */
public class GordianSkeinXofEngine
        implements StreamCipher, Memoable {
    /**
     * index of next byte in keyStream.
     */
    private int theIndex;

    /**
     * Advanced stream.
     */
    private final byte[] theKeyStream;

    /**
     * Underlying digest.
     */
    private final GordianSkeinXof theSkeinXof;

    /**
     * Reset state.
     */
    private GordianSkeinXof theResetState;

    /**
     * Constructor.
     *
     * @param pLength the underlying stateLength
     */
    public GordianSkeinXofEngine(final int pLength) {
        final GordianSkeinBase myBase = new GordianSkeinBase(pLength, pLength);
        theSkeinXof = new GordianSkeinXof(myBase);
        theKeyStream = new byte[myBase.getBlockSize()];
    }

    /**
     * Constructor.
     *
     * @param pSource the source engine
     */
    private GordianSkeinXofEngine(final GordianSkeinXofEngine pSource) {
        theSkeinXof = new GordianSkeinXof(pSource.theSkeinXof);
        theKeyStream = new byte[theSkeinXof.getByteLength()];
        reset(pSource);
    }

    /**
     * initialise a SkeinXof cipher.
     *
     * @param forEncryption whether or not we are for encryption.
     * @param params        the parameters required to set up the cipher.
     * @throws IllegalArgumentException if the params argument is inappropriate.
     */
    public void init(final boolean forEncryption,
                     final CipherParameters params) {
        /*
         * SkeinXof encryption and decryption is completely symmetrical, so the 'forEncryption' is
         * irrelevant. (Like 90% of stream ciphers)
         */

        /* Determine parameters */
        CipherParameters myParams = params;
        byte[] newKey = null;
        byte[] newIV = null;
        if ((myParams instanceof ParametersWithIV)) {
            final ParametersWithIV ivParams = (ParametersWithIV) myParams;
            newIV = ivParams.getIV();
            myParams = ivParams.getParameters();
        }
        if (myParams instanceof KeyParameter) {
            final KeyParameter keyParam = (KeyParameter) myParams;
            newKey = keyParam.getKey();
        }
        if (newKey == null || newIV == null) {
            throw new IllegalArgumentException("A key and IV must be provided");
        }

        /* Initialise engine and mark as initialised */
        final GordianSkeinParametersBuilder myBuilder = new GordianSkeinParametersBuilder()
                .setKey(newKey)
                .setNonce(newIV)
                .setMaxOutputLen(-1);
        theSkeinXof.init(myBuilder.build());

        /* Save reset state */
        theResetState = theSkeinXof.copy();

        /* Initialise the stream block */
        theIndex = 0;
        makeStreamBlock();
    }

    @Override
    public String getAlgorithmName() {
        return theSkeinXof.getAlgorithmName();
    }

    @Override
    public int processBytes(final byte[] in,
                            final int inOff,
                            final int len,
                            final byte[] out,
                            final int outOff) {
        /* Check for errors */
        if (theResetState == null) {
            throw new IllegalStateException(getAlgorithmName() + " not initialised");
        }
        if ((inOff + len) > in.length) {
            throw new DataLengthException("input buffer too short");
        }
        if ((outOff + len) > out.length) {
            throw new OutputLengthException("output buffer too short");
        }

        /* Loop through the input bytes */
        for (int i = 0; i < len; i++) {
            out[i + outOff] = returnByte(in[i + inOff]);
        }
        return len;
    }

    @Override
    public void reset() {
        if (theResetState != null) {
            theSkeinXof.reset(theResetState);
            theIndex = 0;
            makeStreamBlock();
        }
    }

    @Override
    public byte returnByte(final byte in) {
        final byte out = (byte) (theKeyStream[theIndex] ^ in);
        theIndex = (theIndex + 1) % theKeyStream.length;

        if (theIndex == 0) {
            makeStreamBlock();
        }
        return out;
    }

    /**
     * Generate keystream.
     */
    private void makeStreamBlock() {
        /* Generate next output block */
        theSkeinXof.doOutput(theKeyStream, 0, theKeyStream.length);
    }

    @Override
    public GordianSkeinXofEngine copy() {
        return new GordianSkeinXofEngine(this);
    }

    @Override
    public void reset(final Memoable pState) {
        final GordianSkeinXofEngine e = (GordianSkeinXofEngine) pState;
        if (theKeyStream.length != e.theKeyStream.length) {
            throw new IllegalArgumentException();
        }
        theSkeinXof.reset(e.theSkeinXof);
        System.arraycopy(e.theKeyStream, 0, theKeyStream, 0, theKeyStream.length);
        theIndex = e.theIndex;
        theResetState = e.theResetState;
    }
}