2 * FastKeyErasureRNG: Fast-key-erasure random-number generator for Java
3 * Copyright (c) 2023 "dEajL3kA" <Cumpoing79@web.de>
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
;
52 assert PLAINTEXT_K
.length
== KEY_SIZE
: "Inconsistent plaintext size!";
53 assert PLAINTEXT_V
.length
== OUT_SIZE
: "Inconsistent plaintext size!";
55 strongRandom
= SecureRandom
.getInstanceStrong();
56 } catch (final GeneralSecurityException e
) {
57 throw new RuntimeException("Failed to create secure random number generator!", e
);
61 // ======================================================================
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() {
76 cipher
= Cipher
.getInstance("AES/ECB/NoPadding");
78 } catch (final GeneralSecurityException e
) {
79 throw new RuntimeException("Failed to create the required AES cipher!", e
);
83 // ======================================================================
85 // ======================================================================
87 private class KeyWrapper
implements SecretKey
{
89 public String
getAlgorithm() {
94 public String
getFormat() {
99 public byte[] getEncoded() {
104 // ======================================================================
106 // ======================================================================
108 public byte[] nextBytes(final int length
) {
110 throw new IllegalArgumentException("Length must be a positive value!");
113 final byte[] output
= new byte[length
];
115 nextBytes(output
, 0, length
);
121 public void nextBytes(final byte[] bytes
, final int offset
, final int length
) {
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);
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());
146 public void setSeed(final long seed
) {
148 setSeed(longToByteArray(seed
));
153 public void reseed() {
158 // ======================================================================
160 // ======================================================================
163 protected int next(final int numBits
) {
164 final int numBytes
= (numBits
+ 7) / 8;
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 // ======================================================================
176 // ======================================================================
178 private void ensureBufferAvailable() {
179 if (nextPos
>= OUT_SIZE
) {
185 protected void nextBlock() {
186 if (++reseedCounter
>= RESEED_INTERVAL
) {
191 cipher
.update(PLAINTEXT_K
, 0, KEY_SIZE
, keyData
);
192 cipher
.update(PLAINTEXT_V
, 0, OUT_SIZE
, outData
);
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
));
204 protected void setSeed(final byte[] seed
) {
205 assert (seed
!= null) && (seed
.length
> 0) && (seed
.length
<= KEY_SIZE
);
207 for (int i
= 0; i
< 2; ++i
) {
208 cipher
.update(PLAINTEXT_K
, 0, KEY_SIZE
, keyData
);
209 xorBytes(keyData
, seed
);
212 } catch (final GeneralSecurityException e
) {
213 throw new RuntimeException("Failed to re-seed the cipher!", e
);
215 Arrays
.fill(seed
, (byte)0);
219 private void emplaceKey() throws KeyException
{
221 cipher
.init(Cipher
.ENCRYPT_MODE
, wrappedKey
);
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
);
246 // ======================================================================
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() {
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 };