| /* |
| * 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); |
| } |
| } |