com.codahale.timeid.PRNG Maven / Gradle / Ivy
Show all versions of time-id Show documentation
/*
* Copyright © 2019 Coda Hale ([email protected])
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.codahale.timeid;
import java.security.SecureRandom;
/**
* PRNG generates pseudo-random data using ChaCha20 in a fast-key-erasure construction. For each ID,
* it runs a ChaCha20 block transform using a 256-bit key, zero counter, and zero nonce. The first
* 256 bits of the 512-bit results are used as the new key; the next 128 bits are used for the ID;
* the remaining state is discarded.
*
* This construction is an order of magnitude faster than the fastest {@link SecureRandom}
* implementation, is nonblocking, has a very small memory footprint, operates in constant time,
* offers forward secrecy, requires no hardware support, and has performance characteristics
* independent of JVM configuration.
*
* @see Fast-key-erasure random-number
* generators
*/
class PRNG {
private static final int SIGMA0 = 0x61707865;
private static final int SIGMA1 = 0x3320646e;
private static final int SIGMA2 = 0x79622d32;
private static final int SIGMA3 = 0x6b206574;
private int k0, k1, k2, k3, k4, k5, k6, k7;
PRNG(SecureRandom random) {
// Pick a random 256-bit key for ChaCha20.
this.k0 = random.nextInt();
this.k1 = random.nextInt();
this.k2 = random.nextInt();
this.k3 = random.nextInt();
this.k4 = random.nextInt();
this.k5 = random.nextInt();
this.k6 = random.nextInt();
this.k7 = random.nextInt();
}
@SuppressWarnings("Duplicates")
void append(byte[] b) {
// Initialize the block transform's state.
int x00 = SIGMA0;
int x01 = SIGMA1;
int x02 = SIGMA2;
int x03 = SIGMA3;
int x04 = k0;
int x05 = k1;
int x06 = k2;
int x07 = k3;
int x08 = k4;
int x09 = k5;
int x10 = k6;
int x11 = k7;
int x12 = 1; // counter = 0x00000001
int x13 = 0; // nonce = 0x000000000000000000000000
int x14 = 0;
int x15 = 0;
// Perform 10 double rounds.
for (int round = 0; round < 10; round++) {
x00 += x04;
x12 = Integer.rotateLeft(x12 ^ x00, 16);
x08 += x12;
x04 = Integer.rotateLeft(x04 ^ x08, 12);
x00 += x04;
x12 = Integer.rotateLeft(x12 ^ x00, 8);
x08 += x12;
x04 = Integer.rotateLeft(x04 ^ x08, 7);
x01 += x05;
x13 = Integer.rotateLeft(x13 ^ x01, 16);
x09 += x13;
x05 = Integer.rotateLeft(x05 ^ x09, 12);
x01 += x05;
x13 = Integer.rotateLeft(x13 ^ x01, 8);
x09 += x13;
x05 = Integer.rotateLeft(x05 ^ x09, 7);
x02 += x06;
x14 = Integer.rotateLeft(x14 ^ x02, 16);
x10 += x14;
x06 = Integer.rotateLeft(x06 ^ x10, 12);
x02 += x06;
x14 = Integer.rotateLeft(x14 ^ x02, 8);
x10 += x14;
x06 = Integer.rotateLeft(x06 ^ x10, 7);
x03 += x07;
x15 = Integer.rotateLeft(x15 ^ x03, 16);
x11 += x15;
x07 = Integer.rotateLeft(x07 ^ x11, 12);
x03 += x07;
x15 = Integer.rotateLeft(x15 ^ x03, 8);
x11 += x15;
x07 = Integer.rotateLeft(x07 ^ x11, 7);
x00 += x05;
x15 = Integer.rotateLeft(x15 ^ x00, 16);
x10 += x15;
x05 = Integer.rotateLeft(x05 ^ x10, 12);
x00 += x05;
x15 = Integer.rotateLeft(x15 ^ x00, 8);
x10 += x15;
x05 = Integer.rotateLeft(x05 ^ x10, 7);
x01 += x06;
x12 = Integer.rotateLeft(x12 ^ x01, 16);
x11 += x12;
x06 = Integer.rotateLeft(x06 ^ x11, 12);
x01 += x06;
x12 = Integer.rotateLeft(x12 ^ x01, 8);
x11 += x12;
x06 = Integer.rotateLeft(x06 ^ x11, 7);
x02 += x07;
x13 = Integer.rotateLeft(x13 ^ x02, 16);
x08 += x13;
x07 = Integer.rotateLeft(x07 ^ x08, 12);
x02 += x07;
x13 = Integer.rotateLeft(x13 ^ x02, 8);
x08 += x13;
x07 = Integer.rotateLeft(x07 ^ x08, 7);
x03 += x04;
x14 = Integer.rotateLeft(x14 ^ x03, 16);
x09 += x14;
x04 = Integer.rotateLeft(x04 ^ x09, 12);
x03 += x04;
x14 = Integer.rotateLeft(x14 ^ x03, 8);
x09 += x14;
x04 = Integer.rotateLeft(x04 ^ x09, 7);
}
// Use words 8-11 as the output.
littleEndian(x08 + k4, b, 4);
littleEndian(x09 + k5, b, 8);
littleEndian(x10 + k6, b, 12);
littleEndian(x11 + k7, b, 16);
// Use words 0-7 as the new key. This is out-of-order to allow for in-place modification.
this.k4 = x04 + k0;
this.k5 = x05 + k1;
this.k6 = x06 + k2;
this.k7 = x07 + k3;
this.k0 = x00 + SIGMA0;
this.k1 = x01 + SIGMA1;
this.k2 = x02 + SIGMA2;
this.k3 = x03 + SIGMA3;
// Discard words 12-15.
}
private static void littleEndian(int v, byte[] b, int pos) {
// post-Java 9, this is replaceable with a VarHandle for a minor performance boost
b[pos++] = (byte) v;
b[pos++] = (byte) (v >> 8);
b[pos++] = (byte) (v >> 16);
b[pos] = (byte) (v >> 24);
}
}