Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
218 changes: 134 additions & 84 deletions ChaCha20/src/net/clarenceho/util/ChaCha20.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package net.clarenceho.util;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;

/*
* Quick-n-dirty standalone implementation of ChaCha 256-bit
* <p/>
Expand All @@ -17,7 +20,7 @@ public class ChaCha20 {
* Key size in byte
*/
public static final int KEY_SIZE = 32;

/*
* Nonce size in byte (reference implementation)
*/
Expand All @@ -28,122 +31,169 @@ public class ChaCha20 {
*/
public static final int NONCE_SIZE_IETF = 12;

private int[] matrix = new int[16];
/*
* Sigma ints
*/
public static final int[] SIGMA = {0x61707865, 0x3320646e, 0x79622d32, 0x6b206574};


protected static int littleEndianToInt(byte[] bs, int i) {
return (bs[i] & 0xff) | ((bs[i + 1] & 0xff) << 8) | ((bs[i + 2] & 0xff) << 16) | ((bs[i + 3] & 0xff) << 24);
}
/*
* Block size
*/
public static final int BLOCK_SIZE = 64;

protected static void intToLittleEndian(int n, byte[] bs, int off) {
bs[ off] = (byte)(n );
bs[++off] = (byte)(n >>> 8);
bs[++off] = (byte)(n >>> 16);
bs[++off] = (byte)(n >>> 24);
}

protected static int ROTATE(int v, int c) {
return (v << c) | (v >>> (32 - c));
}

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

public class WrongNonceSizeException extends Exception {
private static final long serialVersionUID = 2687731889587117531L;
/*
* Rounds (must be a multiple of 2)
*/
public static final int ROUNDS = 20;

private int[] initMatrix, matrix = new int[16];
private int lastBlock = BLOCK_SIZE;

private ChaCha20(byte[] key, byte[] nonce, int counter) {
init(key, nonce, counter);
}

public class WrongKeySizeException extends Exception {
private static final long serialVersionUID = -290509589749955895L;

/* Init methods */

private void init(byte[] key, byte[] nonce, int counter) {
if (key.length != KEY_SIZE)
throw new IllegalArgumentException("Invalid key size. Current: " + key.length + ", expected: " + KEY_SIZE);

System.arraycopy(SIGMA, 0, matrix, 0, SIGMA.length);

initKey(key);
initNonce(nonce, counter);

initMatrix = matrix.clone();
}


public ChaCha20(byte[] key, byte[] nonce, int counter)
throws WrongKeySizeException, WrongNonceSizeException {
private void initKey(byte[] key) {
matrix[4] = littleEndianToInt(key, 0);
matrix[5] = littleEndianToInt(key, 4);
matrix[6] = littleEndianToInt(key, 8);
matrix[7] = littleEndianToInt(key, 12);
matrix[8] = littleEndianToInt(key, 16);
matrix[9] = littleEndianToInt(key, 20);
matrix[10] = littleEndianToInt(key, 24);
matrix[11] = littleEndianToInt(key, 28);
}

if (key.length != KEY_SIZE) {
throw new WrongKeySizeException();
}

this.matrix[ 0] = 0x61707865;
this.matrix[ 1] = 0x3320646e;
this.matrix[ 2] = 0x79622d32;
this.matrix[ 3] = 0x6b206574;
this.matrix[ 4] = littleEndianToInt(key, 0);
this.matrix[ 5] = littleEndianToInt(key, 4);
this.matrix[ 6] = littleEndianToInt(key, 8);
this.matrix[ 7] = littleEndianToInt(key, 12);
this.matrix[ 8] = littleEndianToInt(key, 16);
this.matrix[ 9] = littleEndianToInt(key, 20);
this.matrix[10] = littleEndianToInt(key, 24);
this.matrix[11] = littleEndianToInt(key, 28);

if (nonce.length == NONCE_SIZE_REF) { // reference implementation
private void initNonce(byte[] nonce, int counter) {
if (nonce.length == NONCE_SIZE_REF) {
this.matrix[12] = 0;
this.matrix[13] = 0;
this.matrix[14] = littleEndianToInt(nonce, 0);
this.matrix[15] = littleEndianToInt(nonce, 4);

} else if (nonce.length == NONCE_SIZE_IETF) {
this.matrix[12] = counter;
this.matrix[13] = littleEndianToInt(nonce, 0);
this.matrix[14] = littleEndianToInt(nonce, 4);
this.matrix[15] = littleEndianToInt(nonce, 8);
} else {
throw new WrongNonceSizeException();
}
} else
throw new IllegalArgumentException("Invalid nonce size. Current: " + nonce.length
+ "expected: " + NONCE_SIZE_REF + " or " + NONCE_SIZE_IETF);
}


/* Encrypt/Decrypt methods */

public void encrypt(byte[] dst, byte[] src, int len) {
int[] x = new int[16];
byte[] output = new byte[64];
if (lastBlock != BLOCK_SIZE)
throw new IllegalArgumentException("Last size isn't " + BLOCK_SIZE); //As it's a streamcipher

dst = dst == null ? new byte[src.length] : dst;
lastBlock = src.length;

final int[] x = new int[16];
final byte[] output = new byte[BLOCK_SIZE];
int i, dpos = 0, spos = 0;

while (len > 0) {
for (i = 16; i-- > 0; ) x[i] = this.matrix[i];
for (i = 20; i > 0; i -= 2) {
quarterRound(x, 0, 4, 8, 12);
quarterRound(x, 1, 5, 9, 13);
System.arraycopy(matrix, 0, x, 0, matrix.length);

for (i = ROUNDS; i > 0; i -= 2) {
quarterRound(x, 0, 4, 8, 12);
quarterRound(x, 1, 5, 9, 13);
quarterRound(x, 2, 6, 10, 14);
quarterRound(x, 3, 7, 11, 15);

quarterRound(x, 0, 5, 10, 15);
quarterRound(x, 1, 6, 11, 12);
quarterRound(x, 2, 7, 8, 13);
quarterRound(x, 3, 4, 9, 14);
quarterRound(x, 2, 7, 8, 13);
quarterRound(x, 3, 4, 9, 14);
}
for (i = 16; i-- > 0; ) x[i] += this.matrix[i];
for (i = 16; i-- > 0; ) intToLittleEndian(x[i], output, 4 * i);

// TODO: (1) check block count is 32-bit vs 64-bit; (2) java int is signed!
for (i = 16; i-- > 0;) x[i] += this.matrix[i];
for (i = 16; i-- > 0;) intToLittleEndian(x[i], output, 4 * i);

this.matrix[12] += 1;
if (this.matrix[12] <= 0) {
if (this.matrix[12] <= 0)
this.matrix[13] += 1;
}
if (len <= 64) {
for (i = len; i-- > 0; ) {
dst[i + dpos] = (byte) (src[i + spos] ^ output[i]);
}
return;
}
for (i = 64; i-- > 0; ) {

for (i = (len <= BLOCK_SIZE ? len : BLOCK_SIZE); i-- > 0;)
dst[i + dpos] = (byte) (src[i + spos] ^ output[i]);
}
len -= 64;
spos += 64;
dpos += 64;

if (len <= BLOCK_SIZE) return;

len -= BLOCK_SIZE;
spos += BLOCK_SIZE;
dpos += BLOCK_SIZE;
}

}

public void decrypt(byte[] dst, byte[] src, int len) {
encrypt(dst, src, len);
}

/* int/byte conversation */

protected int littleEndianToInt(byte[] bs, int i) {
return (bs[i] & 0xff) | ((bs[i + 1] & 0xff) << 8) | ((bs[i + 2] & 0xff) << 16) | ((bs[i + 3] & 0xff) << 24);
}

private void intToLittleEndian(int n, byte[] bs, int off) {
bs[off] = (byte) (n);
bs[++off] = (byte) (n >>> 8);
bs[++off] = (byte) (n >>> 16);
bs[++off] = (byte) (n >>> 24);
}

/* Rotate */

private int rotate(int v, int c) {
return (v << c) | (v >>> (32 - c));
}

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

/* Reset method */

public void reset() {
matrix = initMatrix;
lastBlock = BLOCK_SIZE;
}

/* Factory methods */

public static ChaCha20 of(final String key, final String nonce) {
return of(key.getBytes(StandardCharsets.US_ASCII), nonce.getBytes(StandardCharsets.US_ASCII), 0);
}

public static ChaCha20 of(final String key, final String nonce, final int counter) {
return of(key.getBytes(StandardCharsets.US_ASCII), nonce.getBytes(StandardCharsets.US_ASCII), counter);
}

public static ChaCha20 of(final byte[] key, final byte[] nonce) {
return new ChaCha20(key, nonce, 0);
}

public static ChaCha20 of(final byte[] key, final byte[] nonce, final int counter) {
return new ChaCha20(key, nonce, counter);
}

}
Loading