blob: 148ad80f5ab75de9188a48e589315db0c0a067e7 [file] [log] [blame] [raw]
/*
* Copyright 2015-2016 Rivoreo
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
/* Translated from the ChaCha20 Simple library C code; the original copyright notice are: */
/*
Copyright (C) 2014 insane coder (http://insanecoding.blogspot.com/, http://chacha20.insanecoding.org/)
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
This implementation is intended to be simple, many optimizations can be performed.
*/
package org.rivoreo.crypto;
public class ChaCha20 {
public ChaCha20(final byte[] key, int key_len, final byte[] nonce) {
if(key_len != 16 && key_len != 32) throw new IllegalArgumentException("key length can only be 16 or 32");
if(nonce.length != 8) throw new IllegalArgumentException("nonce length can only be 8");
//byte[] constants;
//schedule[0] = unsigned_bytes_to_int_le(constants, 0);
//schedule[1] = unsigned_bytes_to_int_le(constants, 4);
//schedule[2] = unsigned_bytes_to_int_le(constants, 8);
//schedule[3] = unsigned_bytes_to_int_le(constants, 12);
schedule[0] = 0x61707865;
schedule[1] = 0x3320646e;
schedule[2] = 0x79622d32;
schedule[3] = 0x6b206574;
schedule[4] = unsigned_bytes_to_int_le(key, 0);
schedule[5] = unsigned_bytes_to_int_le(key, 4);
schedule[6] = unsigned_bytes_to_int_le(key, 8);
schedule[7] = unsigned_bytes_to_int_le(key, 12);
schedule[8] = unsigned_bytes_to_int_le(key, 16 % key_len);
schedule[9] = unsigned_bytes_to_int_le(key, 20 % key_len);
schedule[10] = unsigned_bytes_to_int_le(key, 24 % key_len);
schedule[11] = unsigned_bytes_to_int_le(key, 28 % key_len);
schedule[12] = 0; //Counter
schedule[13] = 0; //Counter
schedule[14] = unsigned_bytes_to_int_le(nonce, 0);
schedule[15] = unsigned_bytes_to_int_le(nonce, 4);
}
public ChaCha20(final byte[] key, final byte[] nonce) {
this(key, key.length, nonce);
}
private int[] schedule = new int[16];
//private int[] keystream = new int[16];
private byte[] keystream = new byte[64];
private int available = 0;
private static int unsigned_byte_to_int(byte v) {
return v < 0 ? (int)v + 256 : v;
}
/*
private void debug_print_bitstr(int[] a, int len) {
for(int i=0; i<len; i++) System.err.printf("0x%x\n", a[i]);
}
private void debug_print_bytes(byte[] a, int len) {
for(int i=0; i<len; i++) System.err.printf("0x%x, ", a[i]);
System.err.println();
}
*/
private static long unsigned_int_to_long(int v) {
return v < 0 ? (long)v + 4294967296l : v;
}
private static int unsigned_bytes_to_int_le(byte[] a, int offset) {
if(a.length + offset < 4) throw new IllegalArgumentException("a.length + offset < 4");
/*
return (unsigned_byte_to_int(a[offset]) & 0xff) |
((unsigned_byte_to_int(a[offset + 1]) << 8) & 0xff00) |
((unsigned_byte_to_int(a[offset + 2]) << 16) & 0xff0000) |
((unsigned_byte_to_int(a[offset + 3]) << 24) & 0xff000000);
*/
return unsigned_byte_to_int(a[offset]) |
unsigned_byte_to_int(a[offset + 1]) << 8 |
unsigned_byte_to_int(a[offset + 2]) << 16 |
unsigned_byte_to_int(a[offset + 3]) << 24;
}
/*
private int unsigned_bytes_to_int(byte[] a, int offset) {
if(a.length + offset < 4) throw new IllegalArgumentException("a.length + offset < 4");
return unsigned_byte_to_int(a[offset]) << 24 |
unsigned_byte_to_int(a[offset + 1]) << 16 |
unsigned_byte_to_int(a[offset + 2]) << 8 |
unsigned_byte_to_int(a[offset + 3]);
}
*/
public void set_counter(long counter) {
schedule[12] = (int)(counter & 0xffffffff);
schedule[13] = (int)(counter >>> 32);
available = 0;
}
private static void quarterround(int[] x, int a, int b, int c, int d) {
x[a] += x[b];
int v = x[d] ^ x[a];
x[d] = (v << 16) | (v >>> (32 - 16));
x[c] += x[d];
v = x[b] ^ x[c];
x[b] = (v << 12) | (v >>> (32 - 12));
x[a] += x[b];
v = x[d] ^ x[a];
x[d] = (v << 8) | (v >>> (32 - 8));
x[c] += x[d];
v = x[b] ^ x[c];
x[b] = (v << 7) | (v >>> (32 - 7));
}
public void block(int[] output) {
//System.err.printf("method: org.rivoreo.crypto.ChaCha20::block(int[%d])\n", output.length);
//debug_print_bitstr(schedule, 16);
int i;
for(i = 0; i < schedule.length; i++) output[i] = schedule[i];
i = 10;
while(i-- > 0) {
quarterround(output, 0, 4, 8, 12);
quarterround(output, 1, 5, 9, 13);
quarterround(output, 2, 6, 10, 14);
quarterround(output, 3, 7, 11, 15);
quarterround(output, 0, 5, 10, 15);
quarterround(output, 1, 6, 11, 12);
quarterround(output, 2, 7, 8, 13);
quarterround(output, 3, 4, 9, 14);
}
i = 0;
while(i < 16) {
int result = output[i] + schedule[i];
/*
output[i] = result & 0xff;
output[i + 1] = (result >>> 8) & 0xff;
output[i + 2] = (result >>> 16) & 0xff;
output[i + 3] = (result >>> 24) & 0xff;
*/
output[i] = ((result & 0xff) << 24) |
((result & 0xff00) << 8) |
((result & 0xff0000) >>> 8) |
((result & 0xff000000) >>> 24);
i++;
}
//(++schedule[12] == 0 && ++schedule[13] == 0 && ++schedule[14] == 0 && ++schedule[15] == 0);
if(++schedule[12] != 0) return;
if(++schedule[13] != 0) return;
if(++schedule[14] != 0) return;
if(++schedule[15] != 0) return;
}
public void block(byte[] output) {
int[] buffer = new int[16];
block(buffer);
for(int i = 0, j = 0; i < buffer.length; i++) {
int v = buffer[i];
output[j++] = (byte)((v >>> 24) & 0xff);
output[j++] = (byte)((v >>> 16) & 0xff);
output[j++] = (byte)((v >>> 8) & 0xff);
output[j++] = (byte)((v) & 0xff);
}
}
public void encrypt(byte[] output, byte[] input, int length) {
//System.err.printf("method: org.rivoreo.crypto.ChaCha20::encrypt(byte[%d], byte[%d], %d)\n", output.length, input.length, length);
if(length <= 0) return;
//System.err.printf("encrypt: available = %d\n", available);
//debug_print_bytes(input, length);
int i = 0;
if(available > 0) {
int amount = Math.min(length, available);
int ki = keystream.length - available, j = 0;
//System.err.printf("encrypt: amount = %d\n", amount);
//debug_print_bytes(keystream, keystream.length);
do {
output[i] = (byte)(input[i] ^ keystream[ki++]);
i++;
} while(++j < amount);
//debug_print_bytes(output,
available -= amount;
length -= amount;
}
//System.err.printf("encrypt: length = %d\n", length);
while(length > 0) {
int amount = Math.min(length, keystream.length);
block(keystream);
int ki = 0, j = 0;
//debug_print_bytes(keystream, 64);
//System.err.printf("encrypt: amount = %d\n", amount);
do {
//System.err.printf("encrypt: i = %d, ki = %d\n", i, ki);
output[i] = (byte)(input[i] ^ keystream[ki++]);
i++;
} while(++j < amount);
length -= amount;
available = keystream.length - amount;
}
}
public void encrypt(byte[] data, int length) {
encrypt(data, data, length);
}
public void encrypt(byte[] data) {
encrypt(data, data.length);
}
public void decrypt(byte[] output, byte[] input, int length) {
encrypt(output, input, length);
}
}