All Downloads are FREE. Search and download functionalities are using the official Maven repository.

tsec.cipher.symetric.bouncy.internal.XChaCha20Engine Maven / Gradle / Ivy

The newest version!
package tsec.cipher.symmetric.bouncy.internal;


import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.engines.ChaChaEngine;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.util.Pack;

import java.nio.charset.StandardCharsets;

/**
 * The actual XChaCha20 Engine, intended for use with the
 * XChaChaPoly1305 construction provided in tsec.
 *
 * This class draws inspiration from:
 * Libsodium xchacha implementation
 * https://github.com/jedisct1/libsodium/blob/master/src/libsodium/crypto_stream/xchacha20/stream_xchacha20.c
 *
 * Daniel J. Bernstein's Awesome XSalsa20 Paper:
 * https://cr.yp.to/snuffle/xsalsa-20081128.pdf
 *
 * Tink's XChacha and Snuffle (How they organized their setKey, setSigma and quarterround)
 * https://github.com/google/tink/blob/master/java/src/main/java/com/google/crypto/tink/subtle/XChaCha20Poly1305.java
 *
 * Differences between Tink and this:
 * - No ByteBuffer allocs. Only using the functions provided by BouncyCastle's `Pack`, which are
 * essentially only loops. Those utilities allow the endian functions to be made in a nicer way.
 * - Still using the bouncycastle ChaCha for everything else. Might as well, unless I pretend to write
 * the whole primitive out a-la tink.
 * - `setKey` in terms of an int array, which is done beforehand by the implementation.
 *
 * If I'm incurring a bouncy dependency, it's simply not to rewrite the entire primitives myself.
 *
 *
 */
public class XChaCha20Engine extends ChaChaEngine {

    public String getAlgorithmName() {
        return "XChaCha20";
    }

    public final static int BLOCK_SIZE_BYTES = 64;
    public final static int NONCE_SIZE_BYTES = 24;
    public final static int KEY_SIZE_BYTES = 32;

    protected int getNonceSize() {
        return NONCE_SIZE_BYTES;
    }

    public final static int BLOCK_SIZE_INTS = 16;
    public final static int NONCE_SIZE_INTS = 6;
    public final static int KEY_SIZE_INTS = 8;


    static final int[] SIGMA =
            Pack.littleEndianToInt("expand 32-byte k".getBytes(StandardCharsets.US_ASCII), 0, 4);

    public XChaCha20Engine(){
        super(20);
    }


    /**
     * To reader: This one is a pretty deep rabbithole, so here we go:
     * DJB's initial Chacha representation:
     * https://cr.yp.to/streamciphers/timings/estreambench/submissions/salsa20/chacha8/ref/chacha.c
     * https://github.com/jedisct1/libsodium/blob/master/src/libsodium/crypto_stream/chacha20/ref/chacha20_ref.c
     * RFC Paper:
     * https://tools.ietf.org/html/rfc7539
     *
     * XChacha20IETF is the ChaCha20 function defined over the same specification used in the XSalsa20 Paper,
     * simply applied to the ChaCha20 function. That is, the construction of the initial state for the
     * ChaCha20 IETF spec is applied. That being:
     * - Words 0 to 3 are the Sigma constant
     * - Words 4 to 11 are the key
     * - Words 12 to 15 are for counter (12) and nonce (13-15).
     *
     * HChacha20 is constructed Similar to XSalsa20 (See HSalsa20 Doc).
     *
     * We apply HSalsa20 over the key and 4 bytes of our little endian packed nonce. From there,
     * we derive a key to apply to the IETF variant of ChaCha20.
     *
     * Words 0 to 11 are set the same way as before. Word 12, our counter, is set to 1 after the
     * HChaCha20 application, and we then set the last three words with our remainder of our nonce
     * (with the first position zeroed out).
     *
     *
     */
    protected void setKey(byte[] keyBytes, byte[] ivBytes) {
        super.setKey(keyBytes, ivBytes);

        if (keyBytes == null || keyBytes.length != KEY_SIZE_BYTES) {
            throw new IllegalArgumentException(getAlgorithmName() + " requires a 256 bit key");
        }

        if (ivBytes == null || ivBytes.length != NONCE_SIZE_BYTES) {
            throw new IllegalArgumentException(getAlgorithmName() + " requires a 192 bit nonce");
        }

        int[] nonceInt = Pack.littleEndianToInt(ivBytes, 0, 6);

        int[] chachaKey = hChaCha20Internal(keyBytes, nonceInt);
        setSigma(engineState);
        setKey(engineState, chachaKey);
        engineState[12] = 1; // Counter
        engineState[13] = 0;
        engineState[14] = nonceInt[4];
        engineState[15] = nonceInt[5];
    }

    private static void setSigma(int[] state) {
        System.arraycopy(SIGMA, 0, state, 0, SIGMA.length);
    }

    private static void setKey(int[] state, int[] key) {
        System.arraycopy(key, 0, state, 4, KEY_SIZE_INTS);
    }

    private static void quarterRound(int[] x, int a, int b, int c, int d) {
        x[a] += x[b];
        x[d] = Integer.rotateLeft(x[d] ^ x[a], 16);
        x[c] += x[d];
        x[b] = Integer.rotateLeft(x[b] ^ x[c], 12);
        x[a] += x[b];
        x[d] = Integer.rotateLeft(x[d] ^ x[a], 8);
        x[c] += x[d];
        x[b] = Integer.rotateLeft(x[b] ^ x[c], 7);
    }

    private static void doubleRound(final int[] state) {
        for (int i = 0; i < 10; i++) {
            quarterRound(state, 0, 4, 8, 12);
            quarterRound(state, 1, 5, 9, 13);
            quarterRound(state, 2, 6, 10, 14);
            quarterRound(state, 3, 7, 11, 15);
            quarterRound(state, 0, 5, 10, 15);
            quarterRound(state, 1, 6, 11, 12);
            quarterRound(state, 2, 7, 8, 13);
            quarterRound(state, 3, 4, 9, 14);
        }
    }

    private static void setIntNonce(int[] state, int[] nonce) {
        System.arraycopy(nonce, 0, state, 12, 4);
    }

    /**
     * Extremely similar to the XSalsa20 Construction applied to
     * ChaCha20:
     *
     * - Sigma, key and nonce are set the same way.
     * - We apply doubleRound over the state
     * - return the little endian packed result.
     *
     * Note: This implementation foregoes extra checks as it is only meant to
     * be called _after_ checking the correct key size in
     * `set key`
     *
     * @param key our key, in bytes
     * @param nonceInt our nonce, in little endian format.
     * @return the hchacha20 output, as an integer array packed in
     * little endian format.
     */
    private static int[] hChaCha20Internal(final byte[] key, final int[] nonceInt) {
        int[] x = new int[BLOCK_SIZE_INTS];
        int[] intKey = Pack.littleEndianToInt(key, 0, 8);

        setSigma(x);
        setKey(x, intKey);
        setIntNonce(x, nonceInt);
        doubleRound(x);
        System.arraycopy(x, 12, x, 4, 4);
        return x;
    }

    public static byte[] hChaCha20Byte(final byte[] key, final int[] nonceInt) {
        return Pack.intToLittleEndian(hChaCha20Internal(key, nonceInt));
    }

    public static byte[] hChaCha20(final byte[] key, final byte[] nonce){
        if (key == null || key.length != KEY_SIZE_BYTES) {
            throw new IllegalArgumentException("HChaCha20 requires a 256 bit key");
        }
        if (nonce == null || nonce.length < 16) {
            throw new IllegalArgumentException("HChaCha20 requires a 256 bit key");
        }

        int[] x = new int[BLOCK_SIZE_INTS];
        int[] intKey = Pack.littleEndianToInt(key, 0, KEY_SIZE_INTS);
        int[] nonceInt = Pack.littleEndianToInt(nonce, 0, 4);
        setSigma(x);
        setKey(x, intKey);
        setIntNonce(x, nonceInt);
        doubleRound(x);
        System.arraycopy(x, 12, x, 4, 4);
        return Pack.intToLittleEndian(x);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy