Added link to Weblate from translatable strings once the translator mode

is activated in the workspace settings. This required the addition of a
new lib (in source form) for a SipHash implementation.
This commit is contained in:
Zukero
2017-04-10 18:16:17 +02:00
parent bd8576df0c
commit 0a7cb40dbc
31 changed files with 1485 additions and 8 deletions

View File

@@ -0,0 +1,106 @@
package com.zackehh.siphash;
import static com.zackehh.siphash.SipHashConstants.*;
/**
* Main entry point for SipHash, providing a basic hash
* interface. Assuming you have your full String to hash,
* you can simply provide it to ${@link SipHash#hash(byte[])}.
*
* This class can be initialized and stored in case the
* developer wishes to use the same key over and over again.
*
* This avoids the overhead of having to create the initial
* key over and over again.
*
* <pre>
* {@code
* List<String> inputs = Arrays.asList("input1", "input2", "input3");
* SipHash hasher = new SipHash("this key is mine".getBytes());
* for (int i = 0; i < inputs.size(); i++) {
* hasher.hash(inputs.get(i));
* }
* }
* </pre>
*/
public class SipHash {
/**
* The values of SipHash-c-d, to determine which of the SipHash
* family we're using for this hash.
*/
private final int c, d;
/**
* Initial seeded value of v0.
*/
private final long v0;
/**
* Initial seeded value of v1.
*/
private final long v1;
/**
* Initial seeded value of v2.
*/
private final long v2;
/**
* Initial seeded value of v3.
*/
private final long v3;
/**
* Accepts a 16 byte key input, and uses it to initialize
* the state of the hash. This uses the default values of
* c/d, meaning that we default to SipHash-2-4.
*
* @param key a 16 byte key input
*/
public SipHash(byte[] key){
this(key, DEFAULT_C, DEFAULT_D);
}
/**
* Accepts a 16 byte key input, and uses it to initialize
* the state of the hash. This constructor allows for
* providing the c/d values, allowing the developer to
* select any of the SipHash family to use for hashing.
*
* @param key a 16 byte key input
* @param c the number of compression rounds
* @param d the number of finalization rounds
*/
public SipHash(byte[] key, int c, int d){
this.c = c;
this.d = d;
SipHashKey hashKey = new SipHashKey(key);
this.v0 = (INITIAL_V0 ^ hashKey.k0);
this.v1 = (INITIAL_V1 ^ hashKey.k1);
this.v2 = (INITIAL_V2 ^ hashKey.k0);
this.v3 = (INITIAL_V3 ^ hashKey.k1);
}
/**
* The basic hash implementation provided in the library.
* Assuming you have your full input, you can provide it and
* it will be hashed based on the values which were provided
* to the constructor of this class.
*
* @param data the bytes to hash
* @return a ${@link SipHashResult} instance
*/
public SipHashResult hash(byte[] data) {
SipHashDigest digest = new SipHashDigest(v0, v1, v2, v3, c, d);
for (byte aData : data) {
digest.update(aData);
}
return digest.finish();
}
}

View File

@@ -0,0 +1,8 @@
package com.zackehh.siphash;
/**
* A basic enum to determine the case of a String.
*
* A String can either be UPPER or LOWER case.
*/
public enum SipHashCase { UPPER, LOWER }

View File

@@ -0,0 +1,63 @@
package com.zackehh.siphash;
/**
* Class containing several constants for use alongside
* hashing. Fields such as initial states and defaults,
* as they will not change throughout hashing.
*/
class SipHashConstants {
/**
* This constructor is private, nobody should be
* accessing it!
*
* @throws IllegalAccessException
*/
private SipHashConstants() throws IllegalAccessException {
throw new IllegalAccessException();
}
/**
* Initial magic number for v0.
*/
static final long INITIAL_V0 = 0x736f6d6570736575L;
/**
* Initial magic number for v1.
*/
static final long INITIAL_V1 = 0x646f72616e646f6dL;
/**
* Initial magic number for v2.
*/
static final long INITIAL_V2 = 0x6c7967656e657261L;
/**
* Initial magic number for v3.
*/
static final long INITIAL_V3 = 0x7465646279746573L;
/**
* The default number of rounds of compression during per block.
* This defaults to 2 as the default implementation is SipHash-2-4.
*/
static final int DEFAULT_C = 2;
/**
* The default number of rounds of compression during finalization.
* This defaults to 4 as the default implementation is SipHash-2-4.
*/
static final int DEFAULT_D = 4;
/**
* Whether or not we should pad any hashes by default.
*/
static final boolean DEFAULT_PADDING = false;
/**
* The default String casing for any output Hex Strings. We default
* to lower case as it's the least expensive path.
*/
static final SipHashCase DEFAULT_CASE = SipHashCase.LOWER;
}

View File

@@ -0,0 +1,225 @@
package com.zackehh.siphash;
import static com.zackehh.siphash.SipHashConstants.DEFAULT_C;
import static com.zackehh.siphash.SipHashConstants.DEFAULT_D;
/**
* A streaming implementation of SipHash, to be used when
* you don't have all input available at the same time. Chunks
* of bytes can be applied as they're received, and will be hashed
* accordingly.
*
* As with ${@link SipHash}, the compression and finalization rounds
* can be customized.
*/
public class SipHashDigest {
/**
* The values of SipHash-c-d, to determine which of the SipHash
* family we're using for this hash.
*/
private final int c, d;
/**
* Initial seeded value of v0.
*/
private long v0;
/**
* Initial seeded value of v1.
*/
private long v1;
/**
* Initial seeded value of v2.
*/
private long v2;
/**
* Initial seeded value of v3.
*/
private long v3;
/**
* A counter to keep track of the length of the input.
*/
private byte input_len = 0;
/**
* A counter to keep track of the current position inside
* of a chunk of bytes. Seeing as bytes are applied in chunks
* of 8, this is necessary.
*/
private int m_idx = 0;
/**
* The `m` value from the SipHash algorithm. Every 8 bytes, this
* value will be applied to the current state of the hash.
*/
private long m;
/**
* Accepts a 16 byte key input, and uses it to initialize
* the state of the hash. This uses the default values of
* c/d, meaning that we default to SipHash-2-4.
*
* @param key a 16 byte key input
*/
public SipHashDigest(byte[] key) {
this(key, DEFAULT_C, DEFAULT_D);
}
/**
* Accepts a 16 byte key input, and uses it to initialize
* the state of the hash. This constructor allows for
* providing the c/d values, allowing the developer to
* select any of the SipHash family to use for hashing.
*
* @param key a 16 byte key input
* @param c the number of compression rounds
* @param d the number of finalization rounds
*/
public SipHashDigest(byte[] key, int c, int d) {
this.c = c;
this.d = d;
SipHashKey hashKey = new SipHashKey(key);
this.v0 = SipHashConstants.INITIAL_V0 ^ hashKey.k0;
this.v1 = SipHashConstants.INITIAL_V1 ^ hashKey.k1;
this.v2 = SipHashConstants.INITIAL_V2 ^ hashKey.k0;
this.v3 = SipHashConstants.INITIAL_V3 ^ hashKey.k1;
}
/**
* This constructor is used by the ${@link SipHash} implementation,
* and takes an initial (seeded) value of v0/v1/v2/v3. This is used
* when the key has been pre-calculated. This constructor also
* receives the values of `c` and `d` to use in this hash.
*
* @param v0 an initial seeded v0
* @param v1 an initial seeded v1
* @param v2 an initial seeded v2
* @param v3 an initial seeded v3
* @param c the number of compression rounds
* @param d the number of finalization rounds
*/
SipHashDigest(long v0, long v1, long v2, long v3, int c, int d) {
this.c = c;
this.d = d;
this.v0 = v0;
this.v1 = v1;
this.v2 = v2;
this.v3 = v3;
}
/**
* Updates the current state of the hash with a single byte. This
* is the streaming implementation which shifts as required to ensure
* we can take an arbitrary number of bytes at any given time. We only
* apply the block once the index (`m_idx`) has reached 8. The number
* of compression rounds is determined by the `c` value passed in by
* the developer.
*
* This method returns this instance, as a way of allowing the developer
* to chain.
*
* @return a ${@link SipHashDigest} instance
*/
public SipHashDigest update(byte b) {
input_len++;
m |= (((long) b & 0xff) << (m_idx * 8));
m_idx++;
if (m_idx >= 8) {
v3 ^= m;
for (int i = 0; i < c; i++) {
round();
}
v0 ^= m;
m_idx = 0;
m = 0;
}
return this;
}
/**
* A convenience method to allow passing a chunk of bytes at once, rather
* than a byte at a time.
*
* @return a ${@link SipHashDigest} instance
*/
public SipHashDigest update(byte[] bytes) {
for (byte b : bytes) {
update(b);
}
return this;
}
/**
* Finalizes the hash by padding 0s until the next multiple of
* 8 (as we operate in 8 byte chunks). The last byte added to
* the hash is the length of the input, which we keep inside the
* `input_len` counter. The number of rounds is based on the value
* of `d` as specified by the developer.
*
* This method returns a ${@link SipHashResult}, as no further updates
* should occur (i.e. the lack of chaining here shows we're done).
*
* @return a ${@link SipHashResult} instance
*/
public SipHashResult finish() {
byte msgLenMod256 = input_len;
while (m_idx < 7) {
update((byte) 0);
}
update(msgLenMod256);
v2 ^= 0xff;
for (int i = 0; i < d; i++) {
round();
}
return new SipHashResult(v0 ^ v1 ^ v2 ^ v3);
}
/**
* Performs the equivalent of SipRound on the provided state.
* This method affects the state of this digest, in that it
* mutates the v states directly.
*/
private void round() {
v0 += v1;
v2 += v3;
v1 = rotateLeft(v1, 13);
v3 = rotateLeft(v3, 16);
v1 ^= v0;
v3 ^= v2;
v0 = rotateLeft(v0, 32);
v2 += v1;
v0 += v3;
v1 = rotateLeft(v1, 17);
v3 = rotateLeft(v3, 21);
v1 ^= v2;
v3 ^= v0;
v2 = rotateLeft(v2, 32);
}
/**
* Rotates an input number `val` left by `shift` number of bits. Bits which are
* pushed off to the left are rotated back onto the right, making this a left
* rotation (a circular shift).
*
* @param val the value to be shifted
* @param shift how far left to shift
* @return a long value once shifted
*/
private long rotateLeft(long val, int shift) {
return (val << shift) | val >>> (64 - shift);
}
}

View File

@@ -0,0 +1,53 @@
package com.zackehh.siphash;
/**
* A container class to store both k0 and k1. These
* values are created from a 16 byte key passed into
* the constructor. This isn't ideal as it's another
* alloc, but it'll do for now.
*/
class SipHashKey {
/**
* The value of k0.
*/
final long k0;
/**
* The value of k1.
*/
final long k1;
/**
* Accepts a 16 byte input key and converts the
* first and last 8 byte chunks to little-endian.
* These values become k0 and k1.
*
* @param key the 16 byte key input
*/
public SipHashKey(byte[] key) {
if (key.length != 16) {
throw new IllegalArgumentException("Key must be exactly 16 bytes!");
}
this.k0 = bytesToLong(key, 0);
this.k1 = bytesToLong(key, 8);
}
/**
* Converts a chunk of 8 bytes to a number in little-endian
* format. Accepts an offset to determine where the chunk
* begins in the byte array.
*
* @param b our byte array
* @param offset the index to start at
* @return a little-endian long representation
*/
private static long bytesToLong(byte[] b, int offset) {
long m = 0;
for (int i = 0; i < 8; i++) {
m |= ((((long) b[i + offset]) & 0xff) << (8 * i));
}
return m;
}
}

View File

@@ -0,0 +1,121 @@
package com.zackehh.siphash;
import static com.zackehh.siphash.SipHashConstants.DEFAULT_CASE;
import static com.zackehh.siphash.SipHashConstants.DEFAULT_PADDING;
/**
* A container class of the result of a hash. This class exists
* to allow the developer to retrieve the result in any format
* they like. Currently available formats are `long` and ${@link java.lang.String}.
* When retrieving as a String, the developer can specify the case
* they want it in, and whether or not we should pad the left side
* to 16 characters with 0s.
*/
public class SipHashResult {
/**
* The internal hash result.
*/
private final long result;
/**
* A package-private constructor, as only
* SipHash should be creating results.
*
* @param result the result of a hash
*/
SipHashResult(long result){
this.result = result;
}
/**
* Simply returns the hash result as a long.
*
* @return the hash value as a long
*/
public long get(){
return result;
}
/**
* Returns the result as a Hex String, using
* the default padding and casing values.
*
* @return the hash value as a Hex String
*/
public String getHex(){
return getHex(DEFAULT_PADDING, DEFAULT_CASE);
}
/**
* Returns the result as a Hex String, using
* a custom padding value and default casing value.
*
* @param padding whether or not to pad the string
* @return the hash value as a Hex String
*/
public String getHex(boolean padding){
return getHex(padding, DEFAULT_CASE);
}
/**
* Returns the result as a Hex String, using
* a default padding value and custom casing value.
*
* @param s_case the case to convert the output to
* @return the hash value as a Hex String
*/
public String getHex(SipHashCase s_case){
return getHex(DEFAULT_PADDING, s_case);
}
/**
* Returns the result as a Hex String, taking in
* various arguments to customize the output further,
* such as casing and padding.
*
* @param padding whether or not to pad the string
* @param s_case the case to convert the output to
* @return a Hex String in the custom format
*/
public String getHex(boolean padding, SipHashCase s_case){
String str = Long.toHexString(get());
if (padding) {
str = leftPad(str, 16, "0");
}
if (s_case == SipHashCase.UPPER) {
str = str.toUpperCase();
}
return str;
}
/**
* Modified for https://github.com/Zukero/ATCS
* Replaces the StringUtils.leftPad from apache commons, to remove dependency.
*
* @param str the string to pad
* @param len the total desired length
* @param pad the padding string
* @return str prefixed with enough repetitions of the pad to have a total length matching len
*/
public String leftPad(String str, int len, String pad) {
StringBuilder sb = new StringBuilder(len);
int padlen = len - str.length();
int partialPadLen = padlen % pad.length();
int padCount = padlen / pad.length();
while (padCount >= 0) {
sb.append(pad);
padCount--;
}
if (partialPadLen > 0) {
sb.append(pad.substring(0, partialPadLen));
}
sb.append(str);
return sb.toString();
}
}