diff --git a/core/java/src/com/southernstorm/noise/crypto/chacha20/ChaChaCore.java b/core/java/src/com/southernstorm/noise/crypto/chacha20/ChaChaCore.java index 62d2053b6464178c5e16acb28eabc494c74ad645..509b84854b461ddca75753629d7640ffc6ab5469 100644 --- a/core/java/src/com/southernstorm/noise/crypto/chacha20/ChaChaCore.java +++ b/core/java/src/com/southernstorm/noise/crypto/chacha20/ChaChaCore.java @@ -169,4 +169,45 @@ public final class ChaChaCore { v[c] += v[d]; v[b] = leftRotate7(v[b] ^ v[c]); } + + /** + * XOR's the output of ChaCha20 with a byte buffer. + * + * @param input The input byte buffer. + * @param inputOffset The offset of the first input byte. + * @param output The output byte buffer (can be the same as the input). + * @param outputOffset The offset of the first output byte. + * @param length The number of bytes to XOR between 1 and 64. + * @param block The ChaCha20 output block. + * + * @since 0.9.39 moved from ChaChaPolyCipherState + */ + public static void xorBlock(byte[] input, int inputOffset, byte[] output, int outputOffset, int length, int[] block) + { + int posn = 0; + int value; + while (length >= 4) { + value = block[posn++]; + output[outputOffset] = (byte)(input[inputOffset] ^ value); + output[outputOffset + 1] = (byte)(input[inputOffset + 1] ^ (value >> 8)); + output[outputOffset + 2] = (byte)(input[inputOffset + 2] ^ (value >> 16)); + output[outputOffset + 3] = (byte)(input[inputOffset + 3] ^ (value >> 24)); + inputOffset += 4; + outputOffset += 4; + length -= 4; + } + if (length == 3) { + value = block[posn]; + output[outputOffset] = (byte)(input[inputOffset] ^ value); + output[outputOffset + 1] = (byte)(input[inputOffset + 1] ^ (value >> 8)); + output[outputOffset + 2] = (byte)(input[inputOffset + 2] ^ (value >> 16)); + } else if (length == 2) { + value = block[posn]; + output[outputOffset] = (byte)(input[inputOffset] ^ value); + output[outputOffset + 1] = (byte)(input[inputOffset + 1] ^ (value >> 8)); + } else if (length == 1) { + value = block[posn]; + output[outputOffset] = (byte)(input[inputOffset] ^ value); + } + } } diff --git a/core/java/src/net/i2p/crypto/ChaCha20.java b/core/java/src/net/i2p/crypto/ChaCha20.java new file mode 100644 index 0000000000000000000000000000000000000000..37db1eeb9b6f68f4859ef8337b421a91961a826d --- /dev/null +++ b/core/java/src/net/i2p/crypto/ChaCha20.java @@ -0,0 +1,139 @@ +package net.i2p.crypto; + +/* + * Contains code from Noise ChaChaPolyCipherState: + * + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +import com.southernstorm.noise.crypto.chacha20.ChaChaCore; + +import net.i2p.data.DataHelper; + +/** + * ChaCha20, wrapper around Noise ChaChaCore. + * RFC 7539 + * + * @since 0.9.39 + */ +public final class ChaCha20 { + + private ChaCha20() {} + + /** + * Encrypt from plaintext to ciphertext + * + * @param key first 32 bytes used as the key + * @param iv first 12 bytes used as the iv + */ + public static void encrypt(byte[] key, byte[] iv, + byte[] plaintext, int plaintextOffset, + byte[] ciphertext, int ciphertextOffset, int length) { + int[] input = new int[16]; + int[] output = new int[16]; + ChaChaCore.initKey256(input, key, 0); + //System.out.println("initkey"); + //dumpBlock(input); + // RFC 7539 + // block counter + input[12] = 1; + // Words 13-15 are a nonce, which should not be repeated for the same + // key. The 13th word is the first 32 bits of the input nonce taken + // as a little-endian integer, while the 15th word is the last 32 + // bits. + //ChaChaCore.initIV(input, iv, counter); + //ChaChaCore.initIV(input, iv[4:11], iv[0:3]); + input[13] = (int) DataHelper.fromLongLE(iv, 0, 4); + input[14] = (int) DataHelper.fromLongLE(iv, 4, 4); + input[15] = (int) DataHelper.fromLongLE(iv, 8, 4); + //System.out.println("initIV"); + //dumpBlock(input); + ChaChaCore.hash(output, input); + //int ctr = 1; + //System.out.println("hash " + ctr); + //dumpBlock(output); + while (length > 0) { + int tempLen = 64; + if (tempLen > length) + tempLen = length; + ChaChaCore.hash(output, input); + //System.out.println("hash " + ++ctr); + //dumpBlock(output); + ChaChaCore.xorBlock(plaintext, plaintextOffset, ciphertext, ciphertextOffset, tempLen, output); + if (++(input[12]) == 0) + ++(input[13]); + plaintextOffset += tempLen; + ciphertextOffset += tempLen; + length -= tempLen; + } + } + + /** + * Encrypt from ciphertext to plaintext + * + * @param key first 32 bytes used as the key + * @param iv first 12 bytes used as the iv + */ + public static void decrypt(byte[] key, byte[] iv, + byte[] ciphertext, int ciphertextOffset, + byte[] plaintext, int plaintextOffset, int length) { + // it's symmetric! + encrypt(key, iv, ciphertext, ciphertextOffset, plaintext, plaintextOffset, length); + } + +/**** + public static void main(String[] args) { + // vectors as in RFC 7539 + byte[] plaintext = DataHelper.getASCII("Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it."); + byte[] key = new byte[32]; + for (int i = 0; i < 32; i++) { + key[i] = (byte) i; + } + byte[] iv = new byte[12]; + iv[7] = 0x4a; + byte[] out = new byte[plaintext.length]; + encrypt(key, iv, plaintext, 0, out, 0, plaintext.length); + // Ciphertext Sunscreen: + // 000 6e 2e 35 9a 25 68 f9 80 41 ba 07 28 dd 0d 69 81 n.5.%h..A..(..i. + // 016 e9 7e 7a ec 1d 43 60 c2 0a 27 af cc fd 9f ae 0b .~z..C`..'...... + // 032 f9 1b 65 c5 52 47 33 ab 8f 59 3d ab cd 62 b3 57 ..e.RG3..Y=..b.W + // 048 16 39 d6 24 e6 51 52 ab 8f 53 0c 35 9f 08 61 d8 .9.$.QR..S.5..a. + // 064 07 ca 0d bf 50 0d 6a 61 56 a3 8e 08 8a 22 b6 5e ....P.jaV....".^ + // 080 52 bc 51 4d 16 cc f8 06 81 8c e9 1a b7 79 37 36 R.QM.........y76 + // 096 5a f9 0b bf 74 a3 5b e6 b4 0b 8e ed f2 78 5e 42 Z...t.[......x^B + // 112 87 4d .M + System.out.println("Ciphertext:\n" + net.i2p.util.HexDump.dump(out)); + byte[] out2 = new byte[plaintext.length]; + decrypt(key, iv, out, 0, out2, 0, plaintext.length); + System.out.println("Plaintext:\n" + net.i2p.util.HexDump.dump(out2)); + } + + private static void dumpBlock(int[] b) { + byte[] d = new byte[64]; + for (int i = 0; i < 16; i++) { + //DataHelper.toLongLE(d, i*4, 4, b[i] & 0xffffffffL); + // use BE so the bytes look right + DataHelper.toLong(d, i*4, 4, b[i] & 0xffffffffL); + } + System.out.println(net.i2p.util.HexDump.dump(d)); + } +****/ +} diff --git a/router/java/src/com/southernstorm/noise/protocol/ChaChaPolyCipherState.java b/router/java/src/com/southernstorm/noise/protocol/ChaChaPolyCipherState.java index 3c796ebd062860da7c3747ddd0f19afead0858e8..3f640f7a46c3ec63e5d44ec6e9249ea5a98d2d20 100644 --- a/router/java/src/com/southernstorm/noise/protocol/ChaChaPolyCipherState.java +++ b/router/java/src/com/southernstorm/noise/protocol/ChaChaPolyCipherState.java @@ -89,45 +89,6 @@ public class ChaChaPolyCipherState implements CipherState { public boolean hasKey() { return haskey; } - - /** - * XOR's the output of ChaCha20 with a byte buffer. - * - * @param input The input byte buffer. - * @param inputOffset The offset of the first input byte. - * @param output The output byte buffer (can be the same as the input). - * @param outputOffset The offset of the first output byte. - * @param length The number of bytes to XOR between 1 and 64. - * @param block The ChaCha20 output block. - */ - private static void xorBlock(byte[] input, int inputOffset, byte[] output, int outputOffset, int length, int[] block) - { - int posn = 0; - int value; - while (length >= 4) { - value = block[posn++]; - output[outputOffset] = (byte)(input[inputOffset] ^ value); - output[outputOffset + 1] = (byte)(input[inputOffset + 1] ^ (value >> 8)); - output[outputOffset + 2] = (byte)(input[inputOffset + 2] ^ (value >> 16)); - output[outputOffset + 3] = (byte)(input[inputOffset + 3] ^ (value >> 24)); - inputOffset += 4; - outputOffset += 4; - length -= 4; - } - if (length == 3) { - value = block[posn]; - output[outputOffset] = (byte)(input[inputOffset] ^ value); - output[outputOffset + 1] = (byte)(input[inputOffset + 1] ^ (value >> 8)); - output[outputOffset + 2] = (byte)(input[inputOffset + 2] ^ (value >> 16)); - } else if (length == 2) { - value = block[posn]; - output[outputOffset] = (byte)(input[inputOffset] ^ value); - output[outputOffset + 1] = (byte)(input[inputOffset + 1] ^ (value >> 8)); - } else if (length == 1) { - value = block[posn]; - output[outputOffset] = (byte)(input[inputOffset] ^ value); - } - } /** * Set up to encrypt or decrypt the next packet. @@ -141,7 +102,7 @@ public class ChaChaPolyCipherState implements CipherState { ChaChaCore.initIV(input, n++); ChaChaCore.hash(output, input); Arrays.fill(polyKey, (byte)0); - xorBlock(polyKey, 0, polyKey, 0, 32, output); + ChaChaCore.xorBlock(polyKey, 0, polyKey, 0, 32, output); poly.reset(polyKey, 0); if (ad != null) { poly.update(ad, 0, ad.length); @@ -201,7 +162,7 @@ public class ChaChaPolyCipherState implements CipherState { if (tempLen > length) tempLen = length; ChaChaCore.hash(output, input); - xorBlock(plaintext, plaintextOffset, ciphertext, ciphertextOffset, tempLen, output); + ChaChaCore.xorBlock(plaintext, plaintextOffset, ciphertext, ciphertextOffset, tempLen, output); if (++(input[12]) == 0) ++(input[13]); plaintextOffset += tempLen;