Added build script for Ant.
[fast-key-erasure-rng-java.git] / src / io / github / deajl3ka / fast_key_erasure / FastKeyErasureRNG.java
blobb2fd4fd87688080d81c192e27d2d00a7fe937165
1 /*
2 * FastKeyErasureRNG: Fast-key-erasure random-number generator for Java
3 * Copyright (c) 2023 "dEajL3kA" <Cumpoing79@web.de>
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
6 * associated documentation files (the "Software"), to deal in the Software without restriction,
7 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
8 * sub license, and/or sell copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions: The above copyright notice and this
10 * permission notice shall be included in all copies or substantial portions of the Software.
12 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
13 * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
14 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
15 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
16 * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18 package io.github.deajl3ka.fast_key_erasure;
20 import java.io.IOException;
21 import java.io.NotSerializableException;
22 import java.io.ObjectOutputStream;
23 import java.security.GeneralSecurityException;
24 import java.security.KeyException;
25 import java.security.SecureRandom;
26 import java.util.Arrays;
27 import java.util.Random;
28 import java.util.UUID;
30 import javax.crypto.Cipher;
31 import javax.crypto.SecretKey;
33 @SuppressWarnings("serial")
34 public class FastKeyErasureRNG extends Random {
36 private static final int KEY_SIZE = 32, OUT_SIZE = 64, RESEED_INTERVAL = 257;
38 private static final byte[] PLAINTEXT_K = new byte[] {
39 (byte)0x57, (byte)0xC3, (byte)0xEB, (byte)0xFC, (byte)0x21, (byte)0xE4, (byte)0x7F, (byte)0xE0, (byte)0xCD, (byte)0x1E, (byte)0x21, (byte)0x59, (byte)0x5C, (byte)0x92, (byte)0x3C, (byte)0x19,
40 (byte)0xA0, (byte)0xBD, (byte)0xC8, (byte)0xB0, (byte)0x94, (byte)0x9A, (byte)0xB0, (byte)0x9E, (byte)0x7C, (byte)0xE0, (byte)0xA6, (byte)0xB7, (byte)0x4F, (byte)0x4C, (byte)0x61, (byte)0x9D
43 private static final byte[] PLAINTEXT_V = new byte[] {
44 (byte)0x92, (byte)0x12, (byte)0xDC, (byte)0xD8, (byte)0xDB, (byte)0x73, (byte)0x47, (byte)0x49, (byte)0x82, (byte)0xC3, (byte)0xF3, (byte)0xC0, (byte)0x6A, (byte)0x6D, (byte)0x90, (byte)0xFC,
45 (byte)0x2C, (byte)0x37, (byte)0x97, (byte)0x61, (byte)0xA0, (byte)0xD2, (byte)0x05, (byte)0xD7, (byte)0xAE, (byte)0x2F, (byte)0x95, (byte)0x68, (byte)0xA5, (byte)0xA0, (byte)0xA6, (byte)0xC5,
46 (byte)0x63, (byte)0xF4, (byte)0x79, (byte)0x45, (byte)0x13, (byte)0x8C, (byte)0x89, (byte)0x62, (byte)0xB8, (byte)0xF7, (byte)0xBE, (byte)0xA6, (byte)0x78, (byte)0xF9, (byte)0xDF, (byte)0x6A,
47 (byte)0xFD, (byte)0x4C, (byte)0x2E, (byte)0x2A, (byte)0x76, (byte)0x0B, (byte)0xA2, (byte)0xC3, (byte)0x0F, (byte)0x71, (byte)0x7A, (byte)0x0B, (byte)0xE0, (byte)0xD6, (byte)0x58, (byte)0xB7
50 private static final SecureRandom strongRandom;
51 static {
52 assert PLAINTEXT_K.length == KEY_SIZE : "Inconsistent plaintext size!";
53 assert PLAINTEXT_V.length == OUT_SIZE : "Inconsistent plaintext size!";
54 try {
55 strongRandom = SecureRandom.getInstanceStrong();
56 } catch (final GeneralSecurityException e) {
57 throw new RuntimeException("Failed to create secure random number generator!", e);
61 // ======================================================================
62 // Constructor
63 // ======================================================================
65 private final Cipher cipher;
67 private final KeyWrapper wrappedKey = new KeyWrapper();
69 private final byte[] keyData = new byte[KEY_SIZE], outData = new byte[OUT_SIZE];
71 private int reseedCounter = RESEED_INTERVAL, nextPos = OUT_SIZE;
73 protected FastKeyErasureRNG() {
74 super(0);
75 try {
76 cipher = Cipher.getInstance("AES/ECB/NoPadding");
77 emplaceKey();
78 } catch (final GeneralSecurityException e) {
79 throw new RuntimeException("Failed to create the required AES cipher!", e);
83 // ======================================================================
84 // Key wrapper class
85 // ======================================================================
87 private class KeyWrapper implements SecretKey {
88 @Override
89 public String getAlgorithm() {
90 return "AES";
93 @Override
94 public String getFormat() {
95 return "RAW";
98 @Override
99 public byte[] getEncoded() {
100 return keyData;
104 // ======================================================================
105 // Public methods
106 // ======================================================================
108 public byte[] nextBytes(final int length) {
109 if (length < 0) {
110 throw new IllegalArgumentException("Length must be a positive value!");
113 final byte[] output = new byte[length];
114 if (length > 0) {
115 nextBytes(output, 0, length);
118 return output;
121 public void nextBytes(final byte[] bytes, final int offset, final int length) {
122 if (bytes == null) {
123 throw new IllegalArgumentException("Output array must not be null!");
125 if ((offset < 0) || (length < 0) || (offset > bytes.length) || (bytes.length - offset < length)) {
126 throw new IllegalArgumentException("Invalid offset and/or length!");
129 for (int copyCount, done = 0; done < length; done += copyCount) {
130 ensureBufferAvailable();
131 System.arraycopy(outData, nextPos, bytes, offset + done, copyCount = Math.min(OUT_SIZE - nextPos, length - done));
132 Arrays.fill(outData, nextPos, nextPos += copyCount, (byte)0);
136 @Override
137 public void nextBytes(final byte[] bytes) {
138 nextBytes(bytes, 0, (bytes != null) ? bytes.length : 0);
141 public UUID nextUuid() {
142 return new UUID(nextLong(), nextLong());
145 @Override
146 public void setSeed(final long seed) {
147 if (seed != 0) {
148 setSeed(longToByteArray(seed));
149 nextPos = OUT_SIZE;
153 public void reseed() {
154 doReseed();
155 nextPos = OUT_SIZE;
158 // ======================================================================
159 // Protected methods
160 // ======================================================================
162 @Override
163 protected int next(final int numBits) {
164 final int numBytes = (numBits + 7) / 8;
165 int value = 0;
166 for (int i = 0; i < numBytes; ++i) {
167 ensureBufferAvailable();
168 value = (value << 8) + (outData[nextPos] & 0xFF);
169 outData[nextPos++] = (byte)0;
171 return value >>> ((numBytes * 8) - numBits);
174 // ======================================================================
175 // Internal methods
176 // ======================================================================
178 private void ensureBufferAvailable() {
179 if (nextPos >= OUT_SIZE) {
180 nextBlock();
181 nextPos = 0;
185 protected void nextBlock() {
186 if (++reseedCounter >= RESEED_INTERVAL) {
187 doReseed();
190 try {
191 cipher.update(PLAINTEXT_K, 0, KEY_SIZE, keyData);
192 cipher.update(PLAINTEXT_V, 0, OUT_SIZE, outData);
193 emplaceKey();
194 } catch (final GeneralSecurityException e) {
195 throw new RuntimeException("Failed to update CRNG state!", e);
199 private final void doReseed() {
200 setSeed(strongRandom.generateSeed(KEY_SIZE));
201 reseedCounter = 0;
204 protected void setSeed(final byte[] seed) {
205 assert (seed != null) && (seed.length > 0) && (seed.length <= KEY_SIZE);
206 try {
207 for (int i = 0; i < 2; ++i) {
208 cipher.update(PLAINTEXT_K, 0, KEY_SIZE, keyData);
209 xorBytes(keyData, seed);
210 emplaceKey();
212 } catch (final GeneralSecurityException e) {
213 throw new RuntimeException("Failed to re-seed the cipher!", e);
214 } finally {
215 Arrays.fill(seed, (byte)0);
219 private void emplaceKey() throws KeyException {
220 try {
221 cipher.init(Cipher.ENCRYPT_MODE, wrappedKey);
222 } finally {
223 Arrays.fill(keyData, (byte)0);
227 private void writeObject(ObjectOutputStream out) throws IOException {
228 throw new NotSerializableException();
231 private static void xorBytes(final byte[] target, final byte[] source) {
232 assert (target != null) && (source != null) && (target.length >= source.length);
233 for (int pos = 0; pos < source.length; ++pos) {
234 target[pos] ^= source[pos];
238 private static byte[] longToByteArray(long value) {
239 final byte[] result = new byte[Long.BYTES];
240 for (int pos = Long.BYTES - 1; pos >= 0; --pos, value >>= Byte.SIZE) {
241 result[pos] = (byte) (value & 0xffL);
243 return result;
246 // ======================================================================
247 // Factory methods
248 // ======================================================================
250 private static final ThreadLocal<FastKeyErasureRNG> INSTANCES = ThreadLocal.withInitial(FastKeyErasureRNG::new);
252 public static FastKeyErasureRNG current() {
253 return INSTANCES.get();
256 // ======================================================================
257 // Version information
258 // ======================================================================
260 public static short[] getVersion() {
261 try {
262 final String version = FastKeyErasureRNG.class.getPackage().getImplementationVersion();
263 if ((version != null) && (!version.isEmpty())) {
264 final String[] versionParts = version.split("\\.");
265 if (versionParts.length > 1) {
266 return new short[] { Short.parseShort(versionParts[0].trim(), 10), Short.parseShort(versionParts[1].trim(), 10) };
269 } catch (Exception e) { }
270 return new short[] { (short)0, (short)0 };