Implement hash algorithms using JCA/JCE

This commit is contained in:
Rhys Weatherley 2016-06-17 10:24:53 +10:00
parent 85b163413f
commit a2756f72f0
3 changed files with 62 additions and 89 deletions

View File

@ -1,67 +0,0 @@
/*
* 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;
/**
* Interface to a cryptographic hash algorithm.
*/
public interface HashState extends Destroyable {
/**
* Gets the length of the hash output for this algorithm.
*
* @return The length of the hash in bytes.
*/
int getHashLength();
/**
* Gets the block length for this algorithm.
*
* @return The length of the block in bytes.
*/
int getBlockLength();
/**
* Resets the hash for a new hashing session.
*/
void reset();
/**
* Updates the hash state with more data.
*
* @param data Buffer containing the data.
* @param offset Offset into the data buffer of the first
* byte to be hashed.
* @param length Length of the region to be hashed.
*/
void update(byte[] data, int offset, int length);
/**
* Finishes a hashing session and returns the output hash.
*
* @param output Buffer to put the hash into.
* @param offset Offset of the first byte of the hash
* in the output buffer.
*/
void finish(byte[] output, int offset);
}

View File

@ -22,6 +22,8 @@
package com.southernstorm.noise.protocol;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
/**
@ -118,9 +120,30 @@ public final class Noise {
return null;
}
public static HashState createHash(String name)
/**
* Creates a hash object from its Noise protocol name.
*
* @param name The name of the hash algorithm; e.g. "SHA256", "BLAKE2s", etc.
*
* @return The hash object if the name is recognized.
*
* @throws NoSuchAlgorithmException The name is not recognized as a
* valid Noise protocol name, or there is no cryptography provider
* in the system that implements the algorithm.
*/
public static MessageDigest createHash(String name) throws NoSuchAlgorithmException
{
// TODO
return null;
// The SHA-256 and SHA-512 names are fairly common in standard JDK's.
// The BLAKE2 algorithms will need the installation of a third-party
// cryptography provider like Bouncy Castle.
if (name.equals("SHA256"))
return MessageDigest.getInstance("SHA-256");
else if (name.equals("SHA512"))
return MessageDigest.getInstance("SHA-512");
else if (name.equals("BLAKE2b"))
return MessageDigest.getInstance("Blake2b");
else if (name.equals("BLAKE2s"))
return MessageDigest.getInstance("Blake2s");
throw new NoSuchAlgorithmException("Unknown Noise hash algorithm name: " + name);
}
}

View File

@ -23,6 +23,8 @@
package com.southernstorm.noise.protocol;
import java.io.UnsupportedEncodingException;
import java.security.DigestException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
@ -33,7 +35,7 @@ public class SymmetricState implements Destroyable {
private String name;
private CipherState cipher;
private HashState hash;
private MessageDigest hash;
private byte[] ck;
private byte[] h;
@ -53,7 +55,7 @@ public class SymmetricState implements Destroyable {
hash = Noise.createHash(components[4]);
int keyLength = cipher.getKeyLength();
ck = new byte [keyLength];
int hashLength = hash.getHashLength();
int hashLength = hash.getDigestLength();
h = new byte [hashLength];
byte[] protocolNameBytes;
@ -68,7 +70,7 @@ public class SymmetricState implements Destroyable {
System.arraycopy(protocolNameBytes, 0, h, 0, protocolNameBytes.length);
Arrays.fill(h, protocolNameBytes.length, h.length, (byte)0);
} else {
hashOne(protocolNameBytes, 0, protocolNameBytes.length, h, 0);
hashOne(protocolNameBytes, 0, protocolNameBytes.length, h, 0, h.length);
}
System.arraycopy(h, 0, ck, 0, keyLength);
@ -123,7 +125,7 @@ public class SymmetricState implements Destroyable {
*/
public void mixHash(byte[] data, int offset, int length)
{
hashTwo(h, 0, h.length, data, offset, length, h, 0);
hashTwo(h, 0, h.length, data, offset, length, h, 0, h.length);
}
/**
@ -253,7 +255,7 @@ public class SymmetricState implements Destroyable {
cipher = null;
}
if (hash != null) {
hash.destroy();
hash.reset();
hash = null;
}
if (ck != null) {
@ -274,14 +276,19 @@ public class SymmetricState implements Destroyable {
* @param length Length of the data to be hashed.
* @param output The buffer to receive the output hash value.
* @param outputOffset Offset into the output buffer to place the hash value.
* @param outputLength The length of the hash output.
*
* The output buffer can be the same as the input data buffer.
*/
private void hashOne(byte[] data, int offset, int length, byte[] output, int outputOffset)
private void hashOne(byte[] data, int offset, int length, byte[] output, int outputOffset, int outputLength)
{
hash.reset();
hash.update(data, offset, length);
hash.finish(output, outputOffset);
try {
hash.digest(output, outputOffset, outputLength);
} catch (DigestException e) {
Arrays.fill(output, outputOffset, outputLength, (byte)0);
}
}
/**
@ -295,17 +302,22 @@ public class SymmetricState implements Destroyable {
* @param length2 Length of the second data to be hashed.
* @param output The buffer to receive the output hash value.
* @param outputOffset Offset into the output buffer to place the hash value.
* @param outputLength The length of the hash output.
*
* The output buffer can be same as either of the input buffers.
*/
private void hashTwo(byte[] data1, int offset1, int length1,
byte[] data2, int offset2, int length2,
byte[] output, int outputOffset)
byte[] output, int outputOffset, int outputLength)
{
hash.reset();
hash.update(data1, offset1, length1);
hash.update(data2, offset2, length2);
hash.finish(output, outputOffset);
try {
hash.digest(output, outputOffset, outputLength);
} catch (DigestException e) {
Arrays.fill(output, outputOffset, outputLength, (byte)0);
}
}
/**
@ -319,13 +331,16 @@ public class SymmetricState implements Destroyable {
* @param dataLength The length of the data in bytes.
* @param output The output buffer to place the HMAC value in.
* @param outputOffset Offset into the output buffer for the HMAC value.
* @param outputLength The length of the HMAC output.
*/
private void hmac(byte[] key, int keyOffset, int keyLength,
byte[] data, int dataOffset, int dataLength,
byte[] output, int outputOffset)
byte[] output, int outputOffset, int outputLength)
{
int blockLength = hash.getBlockLength();
int hashLength = hash.getHashLength();
// In all of the algorithms of interest to us, the block length
// is twice the size of the hash length.
int hashLength = hash.getDigestLength();
int blockLength = hashLength * 2;
byte[] block = new byte [blockLength];
int index;
try {
@ -335,7 +350,7 @@ public class SymmetricState implements Destroyable {
} else {
hash.reset();
hash.update(key, keyOffset, keyLength);
hash.finish(block, 0);
hash.digest(block, 0, hashLength);
Arrays.fill(block, hashLength, blockLength, (byte)0);
}
for (index = 0; index < blockLength; ++index)
@ -343,13 +358,15 @@ public class SymmetricState implements Destroyable {
hash.reset();
hash.update(block, 0, blockLength);
hash.update(data, dataOffset, dataLength);
hash.finish(output, outputOffset);
hash.digest(output, outputOffset, outputLength);
for (index = 0; index < blockLength; ++index)
block[index] ^= (byte)(0x36 ^ 0x5C);
hash.reset();
hash.update(block, 0, blockLength);
hash.update(output, outputOffset, hashLength);
hash.finish(output, outputOffset);
hash.digest(output, outputOffset, outputLength);
} catch (DigestException e) {
Arrays.fill(output, outputOffset, outputLength, (byte)0);
} finally {
Noise.destroy(block);
}
@ -378,16 +395,16 @@ public class SymmetricState implements Destroyable {
byte[] output1, int output1Offset, int output1Length,
byte[] output2, int output2Offset, int output2Length)
{
int hashLength = hash.getHashLength();
int hashLength = hash.getDigestLength();
byte[] tempKey = new byte [hashLength];
byte[] tempHash = new byte [hashLength + 1];
try {
hmac(key, keyOffset, keyLength, data, dataOffset, dataLength, tempKey, 0);
hmac(key, keyOffset, keyLength, data, dataOffset, dataLength, tempKey, 0, hashLength);
tempHash[0] = (byte)0x01;
hmac(tempKey, 0, hashLength, tempHash, 0, 1, tempHash, 0);
hmac(tempKey, 0, hashLength, tempHash, 0, 1, tempHash, 0, hashLength);
System.arraycopy(tempHash, 0, output1, output1Offset, output1Length);
tempHash[hashLength] = (byte)0x02;
hmac(tempKey, 0, hashLength, tempHash, 0, hashLength + 1, tempHash, 0);
hmac(tempKey, 0, hashLength, tempHash, 0, hashLength + 1, tempHash, 0, hashLength);
System.arraycopy(tempHash, 0, output2, output2Offset, output2Length);
} finally {
Noise.destroy(tempKey);