ChaChaPoly implementation
This commit is contained in:
parent
688faaec7c
commit
a47b03674d
200
NoiseJava/src/com/southernstorm/noise/crypto/ChaChaCore.java
Normal file
200
NoiseJava/src/com/southernstorm/noise/crypto/ChaChaCore.java
Normal file
@ -0,0 +1,200 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.southernstorm.noise.crypto;
|
||||
|
||||
/**
|
||||
* Implementation of the ChaCha20 core hash transformation.
|
||||
*/
|
||||
public final class ChaChaCore {
|
||||
|
||||
private ChaChaCore() {}
|
||||
|
||||
/**
|
||||
* Hashes an input block with ChaCha20.
|
||||
*
|
||||
* @param output The output block, which must contain at least 16
|
||||
* elements and must not overlap with the input.
|
||||
* @param input The input block, which must contain at least 16
|
||||
* elements.
|
||||
*/
|
||||
public static void hash(int[] output, int[] input)
|
||||
{
|
||||
int index;
|
||||
|
||||
// Copy the input to the output to start with.
|
||||
for (index = 0; index < 16; ++index)
|
||||
output[index] = input[index];
|
||||
|
||||
// Perform the 20 ChaCha rounds in groups of two.
|
||||
for (index = 0; index < 20; index += 2) {
|
||||
// Column round.
|
||||
quarterRound(output, 0, 4, 8, 12);
|
||||
quarterRound(output, 1, 5, 9, 13);
|
||||
quarterRound(output, 2, 6, 10, 14);
|
||||
quarterRound(output, 3, 7, 11, 15);
|
||||
|
||||
// Diagonal round.
|
||||
quarterRound(output, 0, 5, 10, 15);
|
||||
quarterRound(output, 1, 6, 11, 12);
|
||||
quarterRound(output, 2, 7, 8, 13);
|
||||
quarterRound(output, 3, 4, 9, 14);
|
||||
}
|
||||
|
||||
// Add the input block to the output.
|
||||
for (index = 0; index < 16; ++index)
|
||||
output[index] += input[index];
|
||||
}
|
||||
|
||||
private static int char4(char c1, char c2, char c3, char c4)
|
||||
{
|
||||
return (((int)c1) & 0xFF) | ((((int)c2) & 0xFF) << 8) | ((((int)c3) & 0xFF) << 16) | ((((int)c4) & 0xFF) << 24);
|
||||
}
|
||||
|
||||
private static int fromLittleEndian(byte[] key, int offset)
|
||||
{
|
||||
return (key[offset] & 0xFF) | ((key[offset + 1] & 0xFF) << 8) | ((key[offset + 2] & 0xFF) << 16) | ((key[offset + 3] & 0xFF) << 24);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a ChaCha20 block with a 128-bit key.
|
||||
*
|
||||
* @param output The output block, which must consist of at
|
||||
* least 16 words.
|
||||
* @param key The buffer containing the key.
|
||||
* @param offset Offset of the key in the buffer.
|
||||
*/
|
||||
public static void initKey128(int[] output, byte[] key, int offset)
|
||||
{
|
||||
output[0] = char4('e', 'x', 'p', 'a');
|
||||
output[1] = char4('n', 'd', ' ', '1');
|
||||
output[2] = char4('6', '-', 'b', 'y');
|
||||
output[3] = char4('t', 'e', ' ', 'k');
|
||||
output[4] = fromLittleEndian(key, offset);
|
||||
output[5] = fromLittleEndian(key, offset + 4);
|
||||
output[6] = fromLittleEndian(key, offset + 8);
|
||||
output[7] = fromLittleEndian(key, offset + 12);
|
||||
output[8] = output[4];
|
||||
output[9] = output[5];
|
||||
output[10] = output[6];
|
||||
output[11] = output[7];
|
||||
output[12] = 0;
|
||||
output[13] = 0;
|
||||
output[14] = 0;
|
||||
output[15] = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a ChaCha20 block with a 256-bit key.
|
||||
*
|
||||
* @param output The output block, which must consist of at
|
||||
* least 16 words.
|
||||
* @param key The buffer containing the key.
|
||||
* @param offset Offset of the key in the buffer.
|
||||
*/
|
||||
public static void initKey256(int[] output, byte[] key, int offset)
|
||||
{
|
||||
output[0] = char4('e', 'x', 'p', 'a');
|
||||
output[1] = char4('n', 'd', ' ', '3');
|
||||
output[2] = char4('2', '-', 'b', 'y');
|
||||
output[3] = char4('t', 'e', ' ', 'k');
|
||||
output[4] = fromLittleEndian(key, offset);
|
||||
output[5] = fromLittleEndian(key, offset + 4);
|
||||
output[6] = fromLittleEndian(key, offset + 8);
|
||||
output[7] = fromLittleEndian(key, offset + 12);
|
||||
output[8] = fromLittleEndian(key, offset + 16);
|
||||
output[9] = fromLittleEndian(key, offset + 20);
|
||||
output[10] = fromLittleEndian(key, offset + 24);
|
||||
output[11] = fromLittleEndian(key, offset + 28);
|
||||
output[12] = 0;
|
||||
output[13] = 0;
|
||||
output[14] = 0;
|
||||
output[15] = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the 64-bit initialization vector in a ChaCha20 block.
|
||||
*
|
||||
* @param output The output block, which must consist of at
|
||||
* least 16 words and must have been initialized by initKey256()
|
||||
* or initKey128().
|
||||
* @param iv The 64-bit initialization vector value.
|
||||
*
|
||||
* The counter portion of the output block is set to zero.
|
||||
*/
|
||||
public static void initIV(int[] output, long iv)
|
||||
{
|
||||
output[12] = 0;
|
||||
output[13] = 0;
|
||||
output[14] = (int)iv;
|
||||
output[15] = (int)(iv >> 32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the 64-bit initialization vector and counter in a ChaCha20 block.
|
||||
*
|
||||
* @param output The output block, which must consist of at
|
||||
* least 16 words and must have been initialized by initKey256()
|
||||
* or initKey128().
|
||||
* @param iv The 64-bit initialization vector value.
|
||||
* @param counter The 64-bit counter value.
|
||||
*/
|
||||
public static void initIV(int[] output, long iv, long counter)
|
||||
{
|
||||
output[12] = (int)counter;
|
||||
output[13] = (int)(counter >> 32);
|
||||
output[14] = (int)iv;
|
||||
output[15] = (int)(iv >> 32);
|
||||
}
|
||||
|
||||
private static int leftRotate16(int v)
|
||||
{
|
||||
return v << 16 | (v >>> 16);
|
||||
}
|
||||
|
||||
private static int leftRotate12(int v)
|
||||
{
|
||||
return v << 12 | (v >>> 20);
|
||||
}
|
||||
|
||||
private static int leftRotate8(int v)
|
||||
{
|
||||
return v << 8 | (v >>> 24);
|
||||
}
|
||||
|
||||
private static int leftRotate7(int v)
|
||||
{
|
||||
return v << 7 | (v >>> 25);
|
||||
}
|
||||
|
||||
private static void quarterRound(int[] v, int a, int b, int c, int d)
|
||||
{
|
||||
v[a] += v[b];
|
||||
v[d] = leftRotate16(v[d] ^ v[a]);
|
||||
v[c] += v[d];
|
||||
v[b] = leftRotate12(v[b] ^ v[c]);
|
||||
v[a] += v[b];
|
||||
v[d] = leftRotate8(v[d] ^ v[a]);
|
||||
v[c] += v[d];
|
||||
v[b] = leftRotate7(v[b] ^ v[c]);
|
||||
}
|
||||
}
|
||||
327
NoiseJava/src/com/southernstorm/noise/crypto/Poly1305.java
Normal file
327
NoiseJava/src/com/southernstorm/noise/crypto/Poly1305.java
Normal file
@ -0,0 +1,327 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.southernstorm.noise.crypto;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.southernstorm.noise.protocol.Destroyable;
|
||||
|
||||
/**
|
||||
* Simple implementation of the Poly1305 message authenticator.
|
||||
*/
|
||||
public final class Poly1305 implements Destroyable {
|
||||
|
||||
// The 130-bit intermediate values are broken up into five 26-bit words.
|
||||
private byte[] nonce;
|
||||
private byte[] block;
|
||||
private int[] h;
|
||||
private int[] r;
|
||||
private int[] c;
|
||||
private long[] t;
|
||||
private int posn;
|
||||
|
||||
/**
|
||||
* Constructs a new Poly1305 message authenticator.
|
||||
*/
|
||||
public Poly1305()
|
||||
{
|
||||
nonce = new byte [16];
|
||||
block = new byte [16];
|
||||
h = new int [5];
|
||||
r = new int [5];
|
||||
c = new int [5];
|
||||
t = new long [10];
|
||||
posn = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the message authenticator with a new key.
|
||||
*
|
||||
* @param key The buffer containing the 32 byte key.
|
||||
* @param offset The offset into the buffer of the first key byte.
|
||||
*/
|
||||
public void reset(byte[] key, int offset)
|
||||
{
|
||||
System.arraycopy(key, offset + 16, nonce, 0, 16);
|
||||
Arrays.fill(h, 0);
|
||||
posn = 0;
|
||||
|
||||
// Convert the first 16 bytes of the key into a 130-bit
|
||||
// "r" value while masking off the bits that we don't need.
|
||||
r[0] = ((key[offset] & 0xFF)) |
|
||||
((key[offset + 1] & 0xFF) << 8) |
|
||||
((key[offset + 2] & 0xFF) << 16) |
|
||||
((key[offset + 3] & 0x03) << 24);
|
||||
r[1] = ((key[offset + 3] & 0x0C) >> 2) |
|
||||
((key[offset + 4] & 0xFC) << 6) |
|
||||
((key[offset + 5] & 0xFF) << 14) |
|
||||
((key[offset + 6] & 0x0F) << 22);
|
||||
r[2] = ((key[offset + 6] & 0xF0) >> 4) |
|
||||
((key[offset + 7] & 0x0F) << 4) |
|
||||
((key[offset + 8] & 0xFC) << 12) |
|
||||
((key[offset + 9] & 0x3F) << 20);
|
||||
r[3] = ((key[offset + 9] & 0xC0) >> 6) |
|
||||
((key[offset + 10] & 0xFF) << 2) |
|
||||
((key[offset + 11] & 0x0F) << 10) |
|
||||
((key[offset + 12] & 0xFC) << 18);
|
||||
r[4] = ((key[offset + 13] & 0xFF)) |
|
||||
((key[offset + 14] & 0xFF) << 8) |
|
||||
((key[offset + 15] & 0x0F) << 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the message authenticator with more input data.
|
||||
*
|
||||
* @param data The buffer containing the input data.
|
||||
* @param offset The offset of the first byte of input.
|
||||
* @param length The number of bytes of input.
|
||||
*/
|
||||
public void update(byte[] data, int offset, int length)
|
||||
{
|
||||
while (length > 0) {
|
||||
if (posn == 0 && length >= 16) {
|
||||
// We can process the chunk directly out of the input buffer.
|
||||
processChunk(data, offset, false);
|
||||
offset += 16;
|
||||
length -= 16;
|
||||
} else {
|
||||
// Collect up partial bytes in the block buffer.
|
||||
int temp = 16 - posn;
|
||||
if (temp > length)
|
||||
temp = length;
|
||||
System.arraycopy(data, offset, block, posn, temp);
|
||||
offset += temp;
|
||||
length -= temp;
|
||||
posn += temp;
|
||||
if (posn >= 16) {
|
||||
processChunk(block, 0, false);
|
||||
posn = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pads the input with zeroes to a multiple of 16 bytes.
|
||||
*/
|
||||
public void pad()
|
||||
{
|
||||
if (posn != 0) {
|
||||
Arrays.fill(block, posn, 16, (byte)0);
|
||||
processChunk(block, 0, false);
|
||||
posn = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finishes the message authenticator and returns the 16-byte token.
|
||||
*
|
||||
* @param token The buffer to receive the token.
|
||||
* @param offset The offset of the token in the buffer.
|
||||
*/
|
||||
public void finish(byte[] token, int offset)
|
||||
{
|
||||
// Pad and flush the final chunk.
|
||||
if (posn != 0) {
|
||||
block[posn] = (byte)1;
|
||||
Arrays.fill(block, posn + 1, 16, (byte)0);
|
||||
processChunk(block, 0, true);
|
||||
}
|
||||
|
||||
// At this point, processChunk() has left h as a partially reduced
|
||||
// result that is less than (2^130 - 5) * 6. Perform one more
|
||||
// reduction and a trial subtraction to produce the final result.
|
||||
|
||||
// Multiply the high bits of h by 5 and add them to the 130 low bits.
|
||||
int carry = (h[4] >> 26) * 5 + h[0];
|
||||
h[0] = carry & 0x03FFFFFF;
|
||||
carry = (carry >> 26) + h[1];
|
||||
h[1] = carry & 0x03FFFFFF;
|
||||
carry = (carry >> 26) + h[2];
|
||||
h[2] = carry & 0x03FFFFFF;
|
||||
carry = (carry >> 26) + h[3];
|
||||
h[3] = carry & 0x03FFFFFF;
|
||||
h[4] = (carry >> 26) + (h[4] & 0x03FFFFFF);
|
||||
|
||||
// Subtract (2^130 - 5) from h by computing c = h + 5 - 2^130.
|
||||
// The "minus 2^130" step is implicit.
|
||||
carry = 5 + h[0];
|
||||
c[0] = carry & 0x03FFFFFF;
|
||||
carry = (carry >> 26) + h[1];
|
||||
c[1] = carry & 0x03FFFFFF;
|
||||
carry = (carry >> 26) + h[2];
|
||||
c[2] = carry & 0x03FFFFFF;
|
||||
carry = (carry >> 26) + h[3];
|
||||
c[3] = carry & 0x03FFFFFF;
|
||||
c[4] = (carry >> 26) + h[4];
|
||||
|
||||
// Borrow occurs if bit 2^130 of the previous c result is zero.
|
||||
// Carefully turn this into a selection mask so we can select either
|
||||
// h or c as the final result.
|
||||
int mask = -((c[4] >> 26) & 0x01);
|
||||
int nmask = ~mask;
|
||||
h[0] = (h[0] & nmask) | (c[0] & mask);
|
||||
h[1] = (h[1] & nmask) | (c[1] & mask);
|
||||
h[2] = (h[2] & nmask) | (c[2] & mask);
|
||||
h[3] = (h[3] & nmask) | (c[3] & mask);
|
||||
h[4] = (h[4] & nmask) | (c[4] & mask);
|
||||
|
||||
// Convert h into little-endian in the block buffer.
|
||||
block[0] = (byte)(h[0]);
|
||||
block[1] = (byte)(h[0] >> 8);
|
||||
block[2] = (byte)(h[0] >> 16);
|
||||
block[3] = (byte)((h[0] >> 24) | (h[1] << 2));
|
||||
block[4] = (byte)(h[1] >> 6);
|
||||
block[5] = (byte)(h[1] >> 14);
|
||||
block[6] = (byte)((h[1] >> 22) | (h[2] << 4));
|
||||
block[7] = (byte)(h[2] >> 4);
|
||||
block[8] = (byte)(h[2] >> 12);
|
||||
block[9] = (byte)((h[2] >> 20) | (h[3] << 6));
|
||||
block[10] = (byte)(h[3] >> 2);
|
||||
block[11] = (byte)(h[3] >> 10);
|
||||
block[12] = (byte)(h[3] >> 18);
|
||||
block[13] = (byte)(h[4]);
|
||||
block[14] = (byte)(h[4] >> 8);
|
||||
block[15] = (byte)(h[4] >> 16);
|
||||
|
||||
// Add the nonce and write the final result to the token.
|
||||
carry = (nonce[0] & 0xFF) + (block[0] & 0xFF);
|
||||
token[offset] = (byte)carry;
|
||||
for (int x = 1; x < 16; ++x) {
|
||||
carry = (carry >> 8) + (nonce[x] & 0xFF) + (block[x] & 0xFF);
|
||||
token[offset + x] = (byte)carry;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the next chunk of input data.
|
||||
*
|
||||
* @param chunk Buffer containing the input data chunk.
|
||||
* @param offset Offset of the first byte of the 16-byte chunk.
|
||||
* @param finalChunk Set to true if this is the final chunk.
|
||||
*/
|
||||
private void processChunk(byte[] chunk, int offset, boolean finalChunk)
|
||||
{
|
||||
int x;
|
||||
|
||||
// Unpack the 128-bit chunk into a 130-bit value in "c".
|
||||
c[0] = ((chunk[offset] & 0xFF)) |
|
||||
((chunk[offset + 1] & 0xFF) << 8) |
|
||||
((chunk[offset + 2] & 0xFF) << 16) |
|
||||
((chunk[offset + 3] & 0x03) << 24);
|
||||
c[1] = ((chunk[offset + 3] & 0xFC) >> 2) |
|
||||
((chunk[offset + 4] & 0xFF) << 6) |
|
||||
((chunk[offset + 5] & 0xFF) << 14) |
|
||||
((chunk[offset + 6] & 0x0F) << 22);
|
||||
c[2] = ((chunk[offset + 6] & 0xF0) >> 4) |
|
||||
((chunk[offset + 7] & 0xFF) << 4) |
|
||||
((chunk[offset + 8] & 0xFF) << 12) |
|
||||
((chunk[offset + 9] & 0x3F) << 20);
|
||||
c[3] = ((chunk[offset + 9] & 0xC0) >> 6) |
|
||||
((chunk[offset + 10] & 0xFF) << 2) |
|
||||
((chunk[offset + 11] & 0xFF) << 10) |
|
||||
((chunk[offset + 12] & 0xFF) << 18);
|
||||
c[4] = ((chunk[offset + 13] & 0xFF)) |
|
||||
((chunk[offset + 14] & 0xFF) << 8) |
|
||||
((chunk[offset + 15] & 0xFF) << 16);
|
||||
if (!finalChunk)
|
||||
c[4] |= (1 << 24);
|
||||
|
||||
// Compute h = ((h + c) * r) mod (2^130 - 5)
|
||||
|
||||
// Start with h += c. We assume that h is less than (2^130 - 5) * 6
|
||||
// and that c is less than 2^129, so the result will be less than 2^133.
|
||||
h[0] += c[0];
|
||||
h[1] += c[1];
|
||||
h[2] += c[2];
|
||||
h[3] += c[3];
|
||||
h[4] += c[4];
|
||||
|
||||
// Multiply h by r. We know that r is less than 2^124 because the
|
||||
// top 4 bits were AND-ed off by reset(). That makes h * r less
|
||||
// than 2^257. Which is less than the (2^130 - 6)^2 we want for
|
||||
// the modulo reduction step that follows. The intermediate limbs
|
||||
// are 52 bits in size, which allows us to collect up carries in the
|
||||
// extra bits of the 64 bit longs and propagate them later.
|
||||
long hv = h[0];
|
||||
t[0] = hv * r[0];
|
||||
t[1] = hv * r[1];
|
||||
t[2] = hv * r[2];
|
||||
t[3] = hv * r[3];
|
||||
t[4] = hv * r[4];
|
||||
for (x = 1; x < 5; ++x) {
|
||||
hv = h[x];
|
||||
t[x] += hv * r[0];
|
||||
t[x + 1] += hv * r[1];
|
||||
t[x + 2] += hv * r[2];
|
||||
t[x + 3] += hv * r[3];
|
||||
t[x + 4] = hv * r[4];
|
||||
}
|
||||
|
||||
// Propagate carries to convert the t limbs from 52-bit back to 26-bit.
|
||||
// The low bits are placed into h and the high bits are placed into c.
|
||||
h[0] = ((int)t[0]) & 0x03FFFFFF;
|
||||
hv = t[1] + (t[0] >> 26);
|
||||
h[1] = ((int)hv) & 0x03FFFFFF;
|
||||
hv = t[2] + (hv >> 26);
|
||||
h[2] = ((int)hv) & 0x03FFFFFF;
|
||||
hv = t[3] + (hv >> 26);
|
||||
h[3] = ((int)hv) & 0x03FFFFFF;
|
||||
hv = t[4] + (hv >> 26);
|
||||
h[4] = ((int)hv) & 0x03FFFFFF;
|
||||
hv = t[5] + (hv >> 26);
|
||||
c[0] = ((int)hv) & 0x03FFFFFF;
|
||||
hv = t[6] + (hv >> 26);
|
||||
c[1] = ((int)hv) & 0x03FFFFFF;
|
||||
hv = t[7] + (hv >> 26);
|
||||
c[2] = ((int)hv) & 0x03FFFFFF;
|
||||
hv = t[8] + (hv >> 26);
|
||||
c[3] = ((int)hv) & 0x03FFFFFF;
|
||||
hv = t[9] + (hv >> 26);
|
||||
c[4] = ((int)hv);
|
||||
|
||||
// Reduce h * r modulo (2^130 - 5) by multiplying the high 130 bits by 5
|
||||
// and adding them to the low 130 bits. This will leave the result at
|
||||
// most 5 subtractions away from the answer we want.
|
||||
int carry = h[0] + c[0] * 5;
|
||||
h[0] = carry & 0x03FFFFFF;
|
||||
carry = (carry >> 26) + h[1] + c[1] * 5;
|
||||
h[1] = carry & 0x03FFFFFF;
|
||||
carry = (carry >> 26) + h[2] + c[2] * 5;
|
||||
h[2] = carry & 0x03FFFFFF;
|
||||
carry = (carry >> 26) + h[3] + c[3] * 5;
|
||||
h[3] = carry & 0x03FFFFFF;
|
||||
carry = (carry >> 26) + h[4] + c[4] * 5;
|
||||
h[4] = carry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
Arrays.fill(nonce, (byte)0);
|
||||
Arrays.fill(block, (byte)0);
|
||||
Arrays.fill(h, (int)0);
|
||||
Arrays.fill(r, (int)0);
|
||||
Arrays.fill(c, (int)0);
|
||||
Arrays.fill(t, (long)0);
|
||||
}
|
||||
}
|
||||
@ -91,7 +91,7 @@ class AESGCMCipherState implements CipherState {
|
||||
|
||||
@Override
|
||||
public int getMACLength() {
|
||||
return keySpec != null ? 32 : 0;
|
||||
return keySpec != null ? 16 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -0,0 +1,255 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.southernstorm.noise.protocol;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.southernstorm.noise.crypto.ChaChaCore;
|
||||
import com.southernstorm.noise.crypto.Poly1305;
|
||||
|
||||
/**
|
||||
* Implements the ChaChaPoly cipher for Noise.
|
||||
*/
|
||||
class ChaChaPolyCipherState implements CipherState {
|
||||
|
||||
private Poly1305 poly;
|
||||
private int[] input;
|
||||
private int[] output;
|
||||
private byte[] polyKey;
|
||||
long n;
|
||||
private boolean haskey;
|
||||
|
||||
/**
|
||||
* Constructs a new cipher state for the "ChaChaPoly" algorithm.
|
||||
*/
|
||||
public ChaChaPolyCipherState()
|
||||
{
|
||||
poly = new Poly1305();
|
||||
input = new int [16];
|
||||
output = new int [16];
|
||||
polyKey = new byte [32];
|
||||
n = 0;
|
||||
haskey = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
poly.destroy();
|
||||
Arrays.fill(input, 0);
|
||||
Arrays.fill(output, 0);
|
||||
Noise.destroy(polyKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCipherName() {
|
||||
return "ChaChaPoly";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getKeyLength() {
|
||||
return 32;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMACLength() {
|
||||
return haskey ? 16 : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeKey(byte[] key, int offset) {
|
||||
ChaChaCore.initKey256(input, key, offset);
|
||||
n = 0;
|
||||
haskey = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
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.
|
||||
*
|
||||
* @param ad The associated data for the packet.
|
||||
*/
|
||||
private void setup(byte[] ad)
|
||||
{
|
||||
ChaChaCore.initIV(input, n++);
|
||||
ChaChaCore.hash(output, input);
|
||||
Arrays.fill(polyKey, (byte)0);
|
||||
xorBlock(polyKey, 0, polyKey, 0, 32, output);
|
||||
poly.reset(polyKey, 0);
|
||||
if (ad != null) {
|
||||
poly.update(ad, 0, ad.length);
|
||||
poly.pad();
|
||||
}
|
||||
if (++(input[12]) == 0)
|
||||
++(input[13]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts a 64-bit integer into a buffer in little-endian order.
|
||||
*
|
||||
* @param output The output buffer.
|
||||
* @param offset The offset into the output buffer.
|
||||
* @param value The 64-bit integer value.
|
||||
*/
|
||||
private static void putLittleEndian64(byte[] output, int offset, long value)
|
||||
{
|
||||
output[offset] = (byte)value;
|
||||
output[offset + 1] = (byte)(value >> 8);
|
||||
output[offset + 2] = (byte)(value >> 16);
|
||||
output[offset + 3] = (byte)(value >> 24);
|
||||
output[offset + 4] = (byte)(value >> 32);
|
||||
output[offset + 5] = (byte)(value >> 40);
|
||||
output[offset + 6] = (byte)(value >> 48);
|
||||
output[offset + 7] = (byte)(value >> 56);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finishes up the authentication tag for a packet.
|
||||
*
|
||||
* @param ad The associated data.
|
||||
* @param length The length of the plaintext data.
|
||||
*/
|
||||
private void finish(byte[] ad, int length)
|
||||
{
|
||||
poly.pad();
|
||||
putLittleEndian64(polyKey, 0, ad != null ? ad.length : 0);
|
||||
putLittleEndian64(polyKey, 8, length);
|
||||
poly.update(polyKey, 0, 16);
|
||||
poly.finish(polyKey, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts or decrypts a buffer of bytes for the active packet.
|
||||
*
|
||||
* @param plaintext The plaintext data to be encrypted.
|
||||
* @param plaintextOffset The offset to the first plaintext byte.
|
||||
* @param ciphertext The ciphertext data that results from encryption.
|
||||
* @param ciphertextOffset The offset to the first ciphertext byte.
|
||||
* @param length The number of bytes to encrypt.
|
||||
*/
|
||||
private void encrypt(byte[] plaintext, int plaintextOffset,
|
||||
byte[] ciphertext, int ciphertextOffset, int length) {
|
||||
while (length > 0) {
|
||||
int tempLen = 64;
|
||||
if (tempLen > length)
|
||||
tempLen = length;
|
||||
ChaChaCore.hash(output, input);
|
||||
xorBlock(plaintext, plaintextOffset, ciphertext, ciphertextOffset, tempLen, output);
|
||||
if (++(input[12]) == 0)
|
||||
++(input[13]);
|
||||
plaintextOffset += tempLen;
|
||||
ciphertextOffset += tempLen;
|
||||
length -= tempLen;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int encryptWithAd(byte[] ad, byte[] plaintext, int plaintextOffset,
|
||||
byte[] ciphertext, int ciphertextOffset, int length) {
|
||||
if (!haskey) {
|
||||
// The key is not set yet - return the plaintext as-is.
|
||||
if (plaintext != ciphertext || plaintextOffset != ciphertextOffset)
|
||||
System.arraycopy(plaintext, plaintextOffset, ciphertext, ciphertextOffset, length);
|
||||
return length;
|
||||
}
|
||||
setup(ad);
|
||||
encrypt(plaintext, plaintextOffset, ciphertext, ciphertextOffset, length);
|
||||
finish(ad, length);
|
||||
System.arraycopy(polyKey, 0, ciphertext, ciphertextOffset + length, 16);
|
||||
return length + 16;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int decryptWithAd(byte[] ad, byte[] ciphertext,
|
||||
int ciphertextOffset, byte[] plaintext, int plaintextOffset,
|
||||
int length) {
|
||||
if (!haskey) {
|
||||
// The key is not set yet - return the ciphertext as-is.
|
||||
if (plaintext != ciphertext || plaintextOffset != ciphertextOffset)
|
||||
System.arraycopy(ciphertext, ciphertextOffset, plaintext, plaintextOffset, length);
|
||||
return length;
|
||||
}
|
||||
if (length < 16)
|
||||
return -1;
|
||||
int dataLen = length = 16;
|
||||
setup(ad);
|
||||
poly.update(ciphertext, ciphertextOffset, dataLen);
|
||||
finish(ad, dataLen);
|
||||
int temp = 0;
|
||||
for (int index = 0; index < 16; ++index)
|
||||
temp |= (polyKey[index] ^ ciphertext[ciphertextOffset + dataLen + index]);
|
||||
if (temp != 0)
|
||||
return -1;
|
||||
encrypt(ciphertext, ciphertextOffset, plaintext, plaintextOffset, dataLen);
|
||||
return dataLen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CipherState fork(byte[] key, int offset) {
|
||||
CipherState cipher = new ChaChaPolyCipherState();
|
||||
cipher.initializeKey(key, offset);
|
||||
return cipher;
|
||||
}
|
||||
}
|
||||
@ -59,6 +59,8 @@ public final class Noise {
|
||||
{
|
||||
if (name.equals("AESGCM"))
|
||||
return new AESGCMCipherState();
|
||||
else if (name.equals("ChaChaPoly"))
|
||||
return new ChaChaPolyCipherState();
|
||||
throw new NoSuchAlgorithmException("Unknown Noise cipher algorithm name: " + name);
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.southernstorm.noise.tests;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.southernstorm.noise.crypto.Poly1305;
|
||||
|
||||
/**
|
||||
* Perform tests on the Poly1305 implementation in isolation from ChaChaPoly.
|
||||
*/
|
||||
public class Poly1305Tests {
|
||||
|
||||
private void testPoly1305(String key, String data, String hash)
|
||||
{
|
||||
byte[] keyBytes = TestUtils.stringToData(key);
|
||||
byte[] dataBytes = TestUtils.stringToData(data);
|
||||
byte[] hashBytes = TestUtils.stringToData(hash);
|
||||
byte[] token = new byte [16];
|
||||
|
||||
// Authenticate the data in one hit.
|
||||
Poly1305 poly = new Poly1305();
|
||||
poly.reset(keyBytes, 0);
|
||||
poly.update(dataBytes, 0, dataBytes.length);
|
||||
poly.finish(token, 0);
|
||||
assertTrue(Arrays.equals(token, hashBytes));
|
||||
|
||||
// Break the data up into chunks to test multiple calls to update().
|
||||
Arrays.fill(token, (byte)0xDD);
|
||||
poly.reset(keyBytes, 0);
|
||||
poly.update(dataBytes, 0, dataBytes.length / 2);
|
||||
poly.update(dataBytes, dataBytes.length / 2, dataBytes.length - (dataBytes.length / 2));
|
||||
poly.finish(token, 0);
|
||||
assertTrue(Arrays.equals(token, hashBytes));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void poly1305() {
|
||||
// Test vectors from the Poly1305 specification.
|
||||
testPoly1305("0x851fc40c3467ac0be05cc20404f3f700580b3b0f9447bb1e69d095b5928b6dbc", "0xf3f6", "0xf4c633c3044fc145f84f335cb81953de");
|
||||
testPoly1305("0xa0f3080000f46400d0c7e9076c834403dd3fab2251f11ac759f0887129cc2ee7", "", "0xdd3fab2251f11ac759f0887129cc2ee7");
|
||||
testPoly1305("0x48443d0bb0d21109c89a100b5ce2c20883149c69b561dd88298a1798b10716ef", "0x663cea190ffb83d89593f3f476b6bc24d7e679107ea26adb8caf6652d0656136", "0x0ee1c16bb73f0f4fd19881753c01cdbe");
|
||||
testPoly1305("0x12976a08c4426d0ce8a82407c4f4820780f8c20aa71202d1e29179cbcb555a57", "0xab0812724a7f1e342742cbed374d94d136c6b8795d45b3819830f2c04491faf0990c62e48b8018b2c3e4a0fa3134cb67fa83e158c994d961c4cb21095c1bf9", "0x5154ad0d2cb26e01274fc51148491f1b");
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user