├── docs ├── Fortuna.pdf ├── Makefile └── Fortuna.md ├── src ├── main │ ├── resources │ │ └── META-INF │ │ │ └── services │ │ │ └── java.util.Random │ └── java │ │ └── com │ │ └── grunka │ │ ├── random │ │ └── fortuna │ │ │ ├── accumulator │ │ │ ├── EventAdder.java │ │ │ ├── EntropySource.java │ │ │ ├── EventScheduler.java │ │ │ ├── EventAdderImpl.java │ │ │ └── Accumulator.java │ │ │ ├── Util.java │ │ │ ├── Encryption.java │ │ │ ├── entropy │ │ │ ├── FreeMemoryEntropySource.java │ │ │ ├── SchedulingEntropySource.java │ │ │ ├── UptimeEntropySource.java │ │ │ ├── ThreadTimeEntropySource.java │ │ │ ├── BufferPoolEntropySource.java │ │ │ ├── URandomEntropySource.java │ │ │ ├── LoadAverageEntropySource.java │ │ │ ├── GarbageCollectorEntropySource.java │ │ │ └── MemoryPoolEntropySource.java │ │ │ ├── Counter.java │ │ │ ├── PrefetchingSupplier.java │ │ │ ├── Pool.java │ │ │ ├── Generator.java │ │ │ ├── RandomDataBuffer.java │ │ │ ├── tests │ │ │ └── Dump.java │ │ │ └── Fortuna.java │ │ └── encryption │ │ └── rijndael │ │ └── Rijndael.java └── test │ └── java │ └── com │ └── grunka │ ├── random │ └── fortuna │ │ ├── entropy │ │ ├── UptimeEntropySourceTest.java │ │ ├── URandomEntropySourceTest.java │ │ ├── ThreadTimeEntropySourceTest.java │ │ ├── FreeMemoryEntropySourceTest.java │ │ ├── LoadAverageEntropySourceTest.java │ │ ├── MemoryPoolEntropySourceTest.java │ │ ├── SchedulingEntropySourceTest.java │ │ ├── GarbageCollectorEntropySourceTest.java │ │ └── BufferPoolEntropySourceTest.java │ │ ├── RandomDataBufferTest.java │ │ ├── PoolTest.java │ │ ├── PrefetchingSupplierTest.java │ │ ├── CounterTest.java │ │ └── FortunaTest.java │ └── encryption │ └── rijndael │ └── RijndaelTest.java ├── .gitignore ├── .github ├── dependabot.yml ├── FUNDING.yml └── workflows │ ├── maven.yml │ └── codeql-analysis.yml ├── LICENSE.txt ├── README.md ├── pom.xml └── reports └── fortuna.txt /docs/Fortuna.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grunka/fortuna/HEAD/docs/Fortuna.pdf -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/java.util.Random: -------------------------------------------------------------------------------- 1 | com.grunka.random.fortuna.Fortuna 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | *.iml 3 | .idea 4 | .DS_Store 5 | *.out 6 | 7 | .classpath 8 | .settings 9 | .project 10 | 11 | docs/Fortuna.html 12 | -------------------------------------------------------------------------------- /src/main/java/com/grunka/random/fortuna/accumulator/EventAdder.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna.accumulator; 2 | 3 | public interface EventAdder { 4 | void add(byte[] event); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/grunka/random/fortuna/accumulator/EntropySource.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna.accumulator; 2 | 3 | public interface EntropySource { 4 | void schedule(EventScheduler scheduler); 5 | 6 | void event(EventAdder adder); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/grunka/random/fortuna/accumulator/EventScheduler.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna.accumulator; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | public interface EventScheduler { 6 | void schedule(long delay, TimeUnit timeUnit); 7 | } 8 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | all: Fortuna.pdf 2 | 3 | clean: 4 | rm Fortuna.pdf 5 | rm Fortuna.html 6 | 7 | Fortuna.html: Fortuna.md 8 | markdown Fortuna.md > Fortuna.html 9 | 10 | Fortuna.pdf: Fortuna.html 11 | wkhtmltopdf --footer-center "Page [page] of [toPage]" Fortuna.html Fortuna.pdf 12 | 13 | -------------------------------------------------------------------------------- /src/main/java/com/grunka/random/fortuna/Util.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna; 2 | 3 | public class Util { 4 | public static byte[] twoLeastSignificantBytes(long value) { 5 | byte[] result = new byte[2]; 6 | result[0] = (byte) (value & 0xff); 7 | result[1] = (byte) ((value & 0xff00) >> 8); 8 | return result; 9 | } 10 | 11 | static int ceil(int value, int divisor) { 12 | return (value / divisor) + (value % divisor == 0 ? 0 : 1); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/grunka/random/fortuna/Encryption.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna; 2 | 3 | import com.grunka.encryption.rijndael.Rijndael; 4 | 5 | class Encryption { 6 | private final Rijndael rijndael = new Rijndael(); 7 | 8 | void setKey(byte[] key) { 9 | rijndael.makeKey(key, key.length * 8, Rijndael.DIR_ENCRYPT); 10 | } 11 | 12 | byte[] encrypt(byte[] data) { 13 | byte[] result = new byte[data.length]; 14 | rijndael.encrypt(data, result); 15 | return result; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "maven" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: grunka 4 | patreon: 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | -------------------------------------------------------------------------------- /src/main/java/com/grunka/random/fortuna/accumulator/EventAdderImpl.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna.accumulator; 2 | 3 | import com.grunka.random.fortuna.Pool; 4 | 5 | class EventAdderImpl implements EventAdder { 6 | private int pool; 7 | private final int sourceId; 8 | private final Pool[] pools; 9 | 10 | EventAdderImpl(int sourceId, Pool[] pools) { 11 | this.sourceId = sourceId; 12 | this.pools = pools; 13 | pool = 0; 14 | } 15 | 16 | @Override 17 | public void add(byte[] event) { 18 | pool = (pool + 1) % pools.length; 19 | pools[pool].add(sourceId, event); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test/java/com/grunka/random/fortuna/entropy/UptimeEntropySourceTest.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna.entropy; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import static org.junit.Assert.assertEquals; 7 | 8 | public class UptimeEntropySourceTest { 9 | 10 | private UptimeEntropySource target; 11 | private int adds; 12 | 13 | @Before 14 | public void before() { 15 | target = new UptimeEntropySource(); 16 | adds = 0; 17 | } 18 | 19 | @Test 20 | public void shouldAddUptime() { 21 | target.event(event -> { 22 | assertEquals(2, event.length); 23 | adds++; 24 | }); 25 | assertEquals(1, adds); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/com/grunka/random/fortuna/entropy/URandomEntropySourceTest.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna.entropy; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import static org.junit.Assert.assertEquals; 7 | 8 | public class URandomEntropySourceTest { 9 | 10 | private URandomEntropySource target; 11 | private int adds; 12 | 13 | @Before 14 | public void before() { 15 | target = new URandomEntropySource(); 16 | adds = 0; 17 | } 18 | 19 | @Test 20 | public void shouldAddUptime() { 21 | target.event(event -> { 22 | assertEquals(32, event.length); 23 | adds++; 24 | }); 25 | assertEquals(1, adds); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/com/grunka/random/fortuna/entropy/ThreadTimeEntropySourceTest.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna.entropy; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import static org.junit.Assert.assertEquals; 7 | 8 | public class ThreadTimeEntropySourceTest { 9 | 10 | private ThreadTimeEntropySource target; 11 | private int adds; 12 | 13 | @Before 14 | public void before() { 15 | target = new ThreadTimeEntropySource(); 16 | adds = 0; 17 | } 18 | 19 | @Test 20 | public void shouldAddBytes() { 21 | target.event(event -> { 22 | assertEquals(2, event.length); 23 | adds++; 24 | }); 25 | assertEquals(1, adds); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/com/grunka/random/fortuna/entropy/FreeMemoryEntropySourceTest.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna.entropy; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import static org.junit.Assert.assertEquals; 7 | 8 | public class FreeMemoryEntropySourceTest { 9 | 10 | private FreeMemoryEntropySource target; 11 | private int adds; 12 | 13 | @Before 14 | public void before() { 15 | target = new FreeMemoryEntropySource(); 16 | adds = 0; 17 | } 18 | 19 | @Test 20 | public void shouldReadFreeMemory() { 21 | target.event(event -> { 22 | assertEquals(2, event.length); 23 | adds++; 24 | }); 25 | assertEquals(1, adds); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/com/grunka/random/fortuna/entropy/LoadAverageEntropySourceTest.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna.entropy; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import static org.junit.Assert.assertEquals; 7 | 8 | public class LoadAverageEntropySourceTest { 9 | 10 | private LoadAverageEntropySource target; 11 | private int adds; 12 | 13 | @Before 14 | public void before() { 15 | target = new LoadAverageEntropySource(); 16 | adds = 0; 17 | } 18 | 19 | @Test 20 | public void shouldAddTwoBytesAndSchedule() { 21 | target.event(event -> { 22 | assertEquals(2, event.length); 23 | adds++; 24 | }); 25 | assertEquals(1, adds); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/com/grunka/random/fortuna/entropy/MemoryPoolEntropySourceTest.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna.entropy; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import static org.junit.Assert.assertEquals; 7 | 8 | public class MemoryPoolEntropySourceTest { 9 | 10 | private MemoryPoolEntropySource target; 11 | private int adds; 12 | 13 | @Before 14 | public void before() { 15 | target = new MemoryPoolEntropySource(); 16 | adds = 0; 17 | } 18 | 19 | @Test 20 | public void shouldGetMemoryPoolData() { 21 | target.event(event -> { 22 | assertEquals(2, event.length); 23 | adds++; 24 | }); 25 | assertEquals(1, adds); 26 | } 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/com/grunka/random/fortuna/entropy/SchedulingEntropySourceTest.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna.entropy; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import static org.junit.Assert.assertEquals; 7 | 8 | public class SchedulingEntropySourceTest { 9 | 10 | private SchedulingEntropySource target; 11 | private int adds; 12 | 13 | @Before 14 | public void before() { 15 | target = new SchedulingEntropySource(); 16 | adds = 0; 17 | } 18 | 19 | @Test 20 | public void shouldUseTimeBetweenCallsToCreateEvents() { 21 | target.event(event -> { 22 | assertEquals(2, event.length); 23 | adds++; 24 | }); 25 | assertEquals(1, adds); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/com/grunka/random/fortuna/entropy/GarbageCollectorEntropySourceTest.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna.entropy; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import static org.junit.Assert.assertEquals; 7 | 8 | public class GarbageCollectorEntropySourceTest { 9 | 10 | private GarbageCollectorEntropySource target; 11 | private int adds; 12 | 13 | @Before 14 | public void before() { 15 | target = new GarbageCollectorEntropySource(); 16 | adds = 0; 17 | } 18 | 19 | @Test 20 | public void shouldGetGarbageCollectionData() { 21 | target.event(event -> { 22 | assertEquals(2, event.length); 23 | adds++; 24 | }); 25 | assertEquals(1, adds); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/grunka/random/fortuna/entropy/FreeMemoryEntropySource.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna.entropy; 2 | 3 | import com.grunka.random.fortuna.Util; 4 | import com.grunka.random.fortuna.accumulator.EntropySource; 5 | import com.grunka.random.fortuna.accumulator.EventAdder; 6 | import com.grunka.random.fortuna.accumulator.EventScheduler; 7 | 8 | import java.util.concurrent.TimeUnit; 9 | 10 | public class FreeMemoryEntropySource implements EntropySource { 11 | @Override 12 | public void schedule(EventScheduler scheduler) { 13 | scheduler.schedule(100, TimeUnit.MILLISECONDS); 14 | } 15 | 16 | @Override 17 | public void event(EventAdder adder) { 18 | long freeMemory = Runtime.getRuntime().freeMemory(); 19 | adder.add(Util.twoLeastSignificantBytes(freeMemory)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test/java/com/grunka/random/fortuna/entropy/BufferPoolEntropySourceTest.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna.entropy; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import java.nio.ByteBuffer; 7 | 8 | import static org.junit.Assert.assertEquals; 9 | 10 | public class BufferPoolEntropySourceTest { 11 | 12 | private BufferPoolEntropySource target; 13 | private int adds; 14 | 15 | @Before 16 | public void before() { 17 | target = new BufferPoolEntropySource(); 18 | adds = 0; 19 | ByteBuffer.allocateDirect(300); 20 | } 21 | 22 | @Test 23 | public void shouldGetBufferPoolData() { 24 | target.event(event -> { 25 | assertEquals(2, event.length); 26 | adds++; 27 | }); 28 | assertEquals(1, adds); 29 | } 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/grunka/random/fortuna/entropy/SchedulingEntropySource.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna.entropy; 2 | 3 | import com.grunka.random.fortuna.Util; 4 | import com.grunka.random.fortuna.accumulator.EntropySource; 5 | import com.grunka.random.fortuna.accumulator.EventAdder; 6 | import com.grunka.random.fortuna.accumulator.EventScheduler; 7 | 8 | import java.util.concurrent.TimeUnit; 9 | 10 | public class SchedulingEntropySource implements EntropySource { 11 | private long lastTime = 0; 12 | 13 | @Override 14 | public void schedule(EventScheduler scheduler) { 15 | scheduler.schedule(10, TimeUnit.MILLISECONDS); 16 | } 17 | 18 | @Override 19 | public void event(EventAdder adder) { 20 | long now = System.nanoTime(); 21 | long elapsed = now - lastTime; 22 | lastTime = now; 23 | adder.add(Util.twoLeastSignificantBytes(elapsed)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/com/grunka/encryption/rijndael/RijndaelTest.java: -------------------------------------------------------------------------------- 1 | package com.grunka.encryption.rijndael; 2 | 3 | import org.junit.Test; 4 | 5 | import java.nio.charset.StandardCharsets; 6 | 7 | import static org.junit.Assert.assertArrayEquals; 8 | 9 | public class RijndaelTest { 10 | @Test 11 | public void shouldEncryptDecrypt() { 12 | Rijndael rijndael = new Rijndael(); 13 | byte[] input = "hello world01234".getBytes(StandardCharsets.UTF_8); 14 | byte[] encrypted = new byte[input.length]; 15 | byte[] output = new byte[encrypted.length]; 16 | rijndael.makeKey("012345678901234567890123456789ab".getBytes(StandardCharsets.UTF_8), 256, Rijndael.DIR_ENCRYPT); 17 | rijndael.encrypt(input, encrypted); 18 | rijndael.makeKey("012345678901234567890123456789ab".getBytes(StandardCharsets.UTF_8), 256, Rijndael.DIR_DECRYPT); 19 | rijndael.decrypt(encrypted, output); 20 | assertArrayEquals(input, output); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/grunka/random/fortuna/Counter.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna; 2 | 3 | class Counter { 4 | private final byte[] state; 5 | 6 | Counter(int bits) { 7 | if (bits < 8) { 8 | throw new IllegalArgumentException("Too few bits"); 9 | } 10 | if (bits % 8 != 0) { 11 | throw new IllegalArgumentException("Only even bytes allowed"); 12 | } 13 | state = new byte[bits / 8]; 14 | } 15 | 16 | void increment() { 17 | int position = 0; 18 | byte newValue; 19 | do { 20 | newValue = ++state[position++]; 21 | } 22 | while (newValue == 0 && position < state.length); 23 | } 24 | 25 | byte[] getState() { 26 | return state; 27 | } 28 | 29 | boolean isZero() { 30 | for (byte b : state) { 31 | if (b != 0) { 32 | return false; 33 | } 34 | } 35 | return true; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/grunka/random/fortuna/entropy/UptimeEntropySource.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna.entropy; 2 | 3 | import com.grunka.random.fortuna.Util; 4 | import com.grunka.random.fortuna.accumulator.EntropySource; 5 | import com.grunka.random.fortuna.accumulator.EventAdder; 6 | import com.grunka.random.fortuna.accumulator.EventScheduler; 7 | 8 | import java.lang.management.ManagementFactory; 9 | import java.lang.management.RuntimeMXBean; 10 | import java.util.concurrent.TimeUnit; 11 | 12 | public class UptimeEntropySource implements EntropySource { 13 | private final RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean(); 14 | 15 | @Override 16 | public void schedule(EventScheduler scheduler) { 17 | scheduler.schedule(1, TimeUnit.SECONDS); 18 | } 19 | 20 | @Override 21 | public void event(EventAdder adder) { 22 | long uptime = runtimeMXBean.getUptime(); 23 | adder.add(Util.twoLeastSignificantBytes(uptime)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: fortuna 10 | 11 | on: 12 | push: 13 | branches: [ "master" ] 14 | pull_request: 15 | branches: [ "master" ] 16 | 17 | jobs: 18 | build: 19 | 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - uses: actions/checkout@v3 24 | - name: Set up JDK 17 25 | uses: actions/setup-java@v3 26 | with: 27 | java-version: '17' 28 | distribution: 'temurin' 29 | cache: maven 30 | - name: Build with Maven 31 | run: mvn -B package --file pom.xml 32 | -------------------------------------------------------------------------------- /src/main/java/com/grunka/random/fortuna/entropy/ThreadTimeEntropySource.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna.entropy; 2 | 3 | import com.grunka.random.fortuna.Util; 4 | import com.grunka.random.fortuna.accumulator.EntropySource; 5 | import com.grunka.random.fortuna.accumulator.EventAdder; 6 | import com.grunka.random.fortuna.accumulator.EventScheduler; 7 | 8 | import java.lang.management.ManagementFactory; 9 | import java.lang.management.ThreadMXBean; 10 | import java.util.concurrent.TimeUnit; 11 | 12 | public class ThreadTimeEntropySource implements EntropySource { 13 | 14 | private final ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); 15 | 16 | @Override 17 | public void schedule(EventScheduler scheduler) { 18 | scheduler.schedule(100, TimeUnit.MILLISECONDS); 19 | } 20 | 21 | @Override 22 | public void event(EventAdder adder) { 23 | long threadTime = threadMXBean.getCurrentThreadCpuTime() + threadMXBean.getCurrentThreadUserTime(); 24 | adder.add(Util.twoLeastSignificantBytes(threadTime)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 John Wästerlund 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/java/com/grunka/random/fortuna/entropy/BufferPoolEntropySource.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna.entropy; 2 | 3 | import com.grunka.random.fortuna.Util; 4 | import com.grunka.random.fortuna.accumulator.EntropySource; 5 | import com.grunka.random.fortuna.accumulator.EventAdder; 6 | import com.grunka.random.fortuna.accumulator.EventScheduler; 7 | 8 | import java.lang.management.BufferPoolMXBean; 9 | import java.lang.management.ManagementFactory; 10 | import java.util.List; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | public class BufferPoolEntropySource implements EntropySource { 14 | 15 | @Override 16 | public void schedule(EventScheduler scheduler) { 17 | scheduler.schedule(5, TimeUnit.SECONDS); 18 | } 19 | 20 | @Override 21 | public void event(EventAdder adder) { 22 | long sum = 0; 23 | List bufferPoolMXBeans = ManagementFactory.getPlatformMXBeans(BufferPoolMXBean.class); 24 | for (BufferPoolMXBean bufferPoolMXBean : bufferPoolMXBeans) { 25 | sum += bufferPoolMXBean.getMemoryUsed(); 26 | } 27 | adder.add(Util.twoLeastSignificantBytes(sum)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/grunka/random/fortuna/entropy/URandomEntropySource.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna.entropy; 2 | 3 | import com.grunka.random.fortuna.accumulator.EntropySource; 4 | import com.grunka.random.fortuna.accumulator.EventAdder; 5 | import com.grunka.random.fortuna.accumulator.EventScheduler; 6 | 7 | import java.io.FileInputStream; 8 | import java.io.IOException; 9 | import java.util.concurrent.TimeUnit; 10 | 11 | public class URandomEntropySource implements EntropySource { 12 | private final byte[] bytes = new byte[32]; 13 | 14 | @Override 15 | public void schedule(EventScheduler scheduler) { 16 | scheduler.schedule(100, TimeUnit.MILLISECONDS); 17 | } 18 | 19 | @Override 20 | public void event(EventAdder adder) { 21 | try { 22 | try (FileInputStream inputStream = new FileInputStream("/dev/urandom")) { 23 | int bytesRead = inputStream.read(bytes); 24 | assert bytesRead == bytes.length; 25 | adder.add(bytes); 26 | } 27 | } catch (IOException e) { 28 | throw new UnsupportedOperationException("Could not open /dev/urandom", e); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/grunka/random/fortuna/entropy/LoadAverageEntropySource.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna.entropy; 2 | 3 | import com.grunka.random.fortuna.Util; 4 | import com.grunka.random.fortuna.accumulator.EntropySource; 5 | import com.grunka.random.fortuna.accumulator.EventAdder; 6 | import com.grunka.random.fortuna.accumulator.EventScheduler; 7 | 8 | import java.lang.management.ManagementFactory; 9 | import java.lang.management.OperatingSystemMXBean; 10 | import java.math.BigDecimal; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | public class LoadAverageEntropySource implements EntropySource { 14 | 15 | private final OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean(); 16 | 17 | @Override 18 | public void schedule(EventScheduler scheduler) { 19 | scheduler.schedule(1, TimeUnit.SECONDS); 20 | } 21 | 22 | @Override 23 | public void event(EventAdder adder) { 24 | double systemLoadAverage = operatingSystemMXBean.getSystemLoadAverage(); 25 | BigDecimal value = BigDecimal.valueOf(systemLoadAverage); 26 | long convertedValue = value.movePointRight(value.scale()).longValue(); 27 | adder.add(Util.twoLeastSignificantBytes(convertedValue)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/grunka/random/fortuna/entropy/GarbageCollectorEntropySource.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna.entropy; 2 | 3 | import com.grunka.random.fortuna.Util; 4 | import com.grunka.random.fortuna.accumulator.EntropySource; 5 | import com.grunka.random.fortuna.accumulator.EventAdder; 6 | import com.grunka.random.fortuna.accumulator.EventScheduler; 7 | 8 | import java.lang.management.GarbageCollectorMXBean; 9 | import java.lang.management.ManagementFactory; 10 | import java.util.List; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | public class GarbageCollectorEntropySource implements EntropySource { 14 | private final List garbageCollectorMXBeans = ManagementFactory.getGarbageCollectorMXBeans(); 15 | 16 | @Override 17 | public void schedule(EventScheduler scheduler) { 18 | scheduler.schedule(10, TimeUnit.SECONDS); 19 | } 20 | 21 | @Override 22 | public void event(EventAdder adder) { 23 | long sum = 0; 24 | for (GarbageCollectorMXBean garbageCollectorMXBean : garbageCollectorMXBeans) { 25 | sum += garbageCollectorMXBean.getCollectionCount() + garbageCollectorMXBean.getCollectionTime(); 26 | } 27 | adder.add(Util.twoLeastSignificantBytes(sum)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/grunka/random/fortuna/entropy/MemoryPoolEntropySource.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna.entropy; 2 | 3 | import com.grunka.random.fortuna.Util; 4 | import com.grunka.random.fortuna.accumulator.EntropySource; 5 | import com.grunka.random.fortuna.accumulator.EventAdder; 6 | import com.grunka.random.fortuna.accumulator.EventScheduler; 7 | 8 | import java.lang.management.ManagementFactory; 9 | import java.lang.management.MemoryPoolMXBean; 10 | import java.lang.management.MemoryUsage; 11 | import java.util.List; 12 | import java.util.concurrent.TimeUnit; 13 | 14 | public class MemoryPoolEntropySource implements EntropySource { 15 | 16 | @Override 17 | public void schedule(EventScheduler scheduler) { 18 | scheduler.schedule(5, TimeUnit.SECONDS); 19 | } 20 | 21 | @Override 22 | public void event(EventAdder adder) { 23 | long sum = 0; 24 | List memoryPoolMXBeans = ManagementFactory.getMemoryPoolMXBeans(); 25 | for (MemoryPoolMXBean memoryPoolMXBean : memoryPoolMXBeans) { 26 | if (memoryPoolMXBean.isValid()) { 27 | MemoryUsage usage = memoryPoolMXBean.getUsage(); 28 | if (usage != null) { 29 | sum += usage.getUsed(); 30 | } 31 | MemoryUsage collectionUsage = memoryPoolMXBean.getCollectionUsage(); 32 | if (collectionUsage != null) { 33 | sum += collectionUsage.getUsed(); 34 | } 35 | } 36 | } 37 | adder.add(Util.twoLeastSignificantBytes(sum)); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/grunka/random/fortuna/PrefetchingSupplier.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna; 2 | 3 | import java.util.concurrent.ExecutionException; 4 | import java.util.concurrent.ExecutorService; 5 | import java.util.concurrent.Future; 6 | import java.util.concurrent.atomic.AtomicReference; 7 | import java.util.concurrent.locks.ReentrantLock; 8 | import java.util.function.Supplier; 9 | 10 | class PrefetchingSupplier implements Supplier { 11 | private final ReentrantLock lock = new ReentrantLock(); 12 | private final Supplier delegate; 13 | private final ExecutorService executorService; 14 | private final AtomicReference> value = new AtomicReference<>(); 15 | 16 | PrefetchingSupplier(Supplier delegate, ExecutorService executorService) { 17 | this.delegate = delegate; 18 | this.executorService = executorService; 19 | value.set(executorService.submit(delegate::get)); 20 | } 21 | 22 | @Override 23 | public T get() { 24 | lock.lock(); 25 | try { 26 | T delegateValue = this.value.get().get(); 27 | value.set(executorService.submit(delegate::get)); 28 | return delegateValue; 29 | } catch (InterruptedException e) { 30 | throw new IllegalStateException("Interrupted while waiting for prefetch result", e); 31 | } catch (ExecutionException e) { 32 | throw new IllegalStateException("Failure while prefetching", e.getCause()); 33 | } finally { 34 | lock.unlock(); 35 | } 36 | } 37 | 38 | void shutdownPrefetch() { 39 | value.getAndSet(null).cancel(true); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/grunka/random/fortuna/accumulator/Accumulator.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna.accumulator; 2 | 3 | import com.grunka.random.fortuna.Pool; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.concurrent.ScheduledExecutorService; 8 | import java.util.concurrent.ScheduledFuture; 9 | import java.util.concurrent.atomic.AtomicBoolean; 10 | import java.util.concurrent.atomic.AtomicInteger; 11 | 12 | public class Accumulator { 13 | private final AtomicInteger sourceCount = new AtomicInteger(0); 14 | private final List> entropyFutures = new ArrayList<>(); 15 | private final Pool[] pools; 16 | private final ScheduledExecutorService scheduler; 17 | 18 | public Accumulator(Pool[] pools, ScheduledExecutorService scheduler) { 19 | this.pools = pools; 20 | this.scheduler = scheduler; 21 | } 22 | 23 | public Pool[] getPools() { 24 | return pools; 25 | } 26 | 27 | public void addSource(EntropySource entropySource) { 28 | int sourceId = sourceCount.getAndIncrement(); 29 | EventAdder eventAdder = new EventAdderImpl(sourceId, pools); 30 | AtomicBoolean scheduled = new AtomicBoolean(); 31 | entropySource.schedule(((delay, timeUnit) -> { 32 | entropyFutures.add(scheduler.scheduleWithFixedDelay(() -> entropySource.event(eventAdder), 0, delay, timeUnit)); 33 | scheduled.set(true); 34 | })); 35 | if (!scheduled.get()) { 36 | throw new IllegalStateException("Entropy source " + entropySource.getClass().getName() + " was not scheduled to run"); 37 | } 38 | } 39 | 40 | public void shutdownSources() { 41 | entropyFutures.forEach(f -> f.cancel(false)); 42 | entropyFutures.clear(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/com/grunka/random/fortuna/RandomDataBufferTest.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import java.util.function.Supplier; 7 | 8 | import static org.junit.Assert.assertEquals; 9 | 10 | public class RandomDataBufferTest { 11 | 12 | private RandomDataBuffer randomDataBuffer; 13 | private Supplier dataSupplier; 14 | 15 | @Before 16 | public void setUp() { 17 | randomDataBuffer = new RandomDataBuffer(); 18 | dataSupplier = () -> new byte[]{(byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef, (byte) 0xfa, (byte) 0xce, (byte) 0xfe, (byte) 0xed}; 19 | } 20 | 21 | @Test 22 | public void testGettingBits() { 23 | assertEquals("deadbeef", Integer.toHexString(randomDataBuffer.next(32, dataSupplier))); 24 | assertEquals("f", Integer.toHexString(randomDataBuffer.next(4, dataSupplier))); 25 | assertEquals("ac", Integer.toHexString(randomDataBuffer.next(8, dataSupplier))); 26 | assertEquals("efee", Integer.toHexString(randomDataBuffer.next(16, dataSupplier))); 27 | assertEquals("ddeadbee", Integer.toHexString(randomDataBuffer.next(32, dataSupplier))); 28 | assertEquals("f", Integer.toHexString(randomDataBuffer.next(4, dataSupplier))); 29 | 30 | for (int i = 0; i < 32; i++) { 31 | assertEquals("" + Integer.toBinaryString(0xfacefeed).charAt(i), Integer.toBinaryString(randomDataBuffer.next(1, dataSupplier))); 32 | } 33 | for (int i = 0; i < 32; i++) { 34 | assertEquals("" + Integer.toBinaryString(0xdeadbeef).charAt(i), Integer.toBinaryString(randomDataBuffer.next(1, dataSupplier))); 35 | } 36 | } 37 | 38 | @Test(expected = IllegalStateException.class) 39 | public void shouldFailIfNoDataIsProvided() { 40 | randomDataBuffer.next(1, () -> new byte[0]); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [master, ] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [master] 9 | schedule: 10 | - cron: '0 23 * * 4' 11 | 12 | jobs: 13 | analyse: 14 | name: Analyse 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v2 20 | with: 21 | # We must fetch at least the immediate parents so that if this is 22 | # a pull request then we can checkout the head. 23 | fetch-depth: 2 24 | 25 | # If this run was triggered by a pull request event, then checkout 26 | # the head of the pull request instead of the merge commit. 27 | - run: git checkout HEAD^2 28 | if: ${{ github.event_name == 'pull_request' }} 29 | 30 | # Initializes the CodeQL tools for scanning. 31 | - name: Initialize CodeQL 32 | uses: github/codeql-action/init@v1 33 | # Override language selection by uncommenting this and choosing your languages 34 | # with: 35 | # languages: go, javascript, csharp, python, cpp, java 36 | 37 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 38 | # If this step fails, then you should remove it and run the build manually (see below) 39 | - name: Autobuild 40 | uses: github/codeql-action/autobuild@v1 41 | 42 | # ℹ️ Command-line programs to run using the OS shell. 43 | # 📚 https://git.io/JvXDl 44 | 45 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 46 | # and modify them (or add more) to build your code if your project 47 | # uses a compiled language 48 | 49 | #- run: | 50 | # make bootstrap 51 | # make release 52 | 53 | - name: Perform CodeQL Analysis 54 | uses: github/codeql-action/analyze@v1 55 | -------------------------------------------------------------------------------- /src/main/java/com/grunka/random/fortuna/Pool.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna; 2 | 3 | import java.security.MessageDigest; 4 | import java.security.NoSuchAlgorithmException; 5 | import java.util.concurrent.locks.Lock; 6 | import java.util.concurrent.locks.ReentrantReadWriteLock; 7 | 8 | public class Pool { 9 | private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); 10 | private final Lock readLock = lock.readLock(); 11 | private final Lock writeLock = lock.writeLock(); 12 | private final MessageDigest poolDigest = createDigest(); 13 | private long size = 0; 14 | 15 | private MessageDigest createDigest() { 16 | try { 17 | return MessageDigest.getInstance("SHA-256"); 18 | } catch (NoSuchAlgorithmException e) { 19 | throw new Error("Could not initialize digest", e); 20 | } 21 | } 22 | 23 | long size() { 24 | readLock.lock(); 25 | try { 26 | return size; 27 | } finally { 28 | readLock.unlock(); 29 | } 30 | } 31 | 32 | public void add(int source, byte[] event) { 33 | writeLock.lock(); 34 | try { 35 | if (source < 0 || source > 255) { 36 | throw new IllegalArgumentException("Source needs to be in the range 0 to 255, it was " + source); 37 | } 38 | if (event.length < 1 || event.length > 32) { 39 | throw new IllegalArgumentException("The length of the event need to be in the range 1 to 32, it was " + event.length); 40 | } 41 | size += event.length + 2; 42 | poolDigest.update(new byte[]{(byte) source, (byte) event.length}); 43 | poolDigest.update(event); 44 | } finally { 45 | writeLock.unlock(); 46 | } 47 | } 48 | 49 | byte[] getAndClear() { 50 | writeLock.lock(); 51 | try { 52 | size = 0; 53 | return poolDigest.digest(); 54 | } finally { 55 | writeLock.unlock(); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/grunka/random/fortuna/Generator.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna; 2 | 3 | import java.security.MessageDigest; 4 | import java.security.NoSuchAlgorithmException; 5 | import java.util.Arrays; 6 | 7 | class Generator { 8 | private static final int KEY_LENGTH = 32; 9 | private static final int BLOCK_LENGTH = 16; 10 | private final Counter counter; 11 | private final byte[] key = new byte[KEY_LENGTH]; 12 | private final MessageDigest reseedDigest; 13 | private final Encryption encryption; 14 | 15 | Generator() { 16 | encryption = new Encryption(); 17 | counter = new Counter(128); 18 | try { 19 | reseedDigest = MessageDigest.getInstance("SHA-256"); 20 | } catch (NoSuchAlgorithmException e) { 21 | throw new Error("Was not able to initialize digest", e); 22 | } 23 | } 24 | 25 | void reseed(byte[] seed) { 26 | reseedDigest.update(key); 27 | System.arraycopy(reseedDigest.digest(seed), 0, key, 0, KEY_LENGTH); 28 | encryption.setKey(key); 29 | counter.increment(); 30 | } 31 | 32 | private byte[] generateBlocks(int blocks) { 33 | if (counter.isZero()) { 34 | throw new IllegalStateException("Generator not yet initialized"); 35 | } 36 | byte[] result = new byte[blocks * BLOCK_LENGTH]; 37 | for (int block = 0; block < blocks; block++) { 38 | byte[] encryptedBytes = encryption.encrypt(counter.getState()); 39 | System.arraycopy(encryptedBytes, 0, result, block * BLOCK_LENGTH, BLOCK_LENGTH); 40 | counter.increment(); 41 | } 42 | return result; 43 | } 44 | 45 | byte[] pseudoRandomData(int bytes) { 46 | if (bytes < 0 || bytes > 1048576) { 47 | throw new IllegalArgumentException("Cannot generate " + bytes + " bytes of random data"); 48 | } 49 | try { 50 | return Arrays.copyOf(generateBlocks(Util.ceil(bytes, BLOCK_LENGTH)), bytes); 51 | } finally { 52 | System.arraycopy(generateBlocks(2), 0, key, 0, KEY_LENGTH); 53 | encryption.setKey(key); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/test/java/com/grunka/random/fortuna/PoolTest.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import java.security.MessageDigest; 7 | 8 | import static org.junit.Assert.assertArrayEquals; 9 | import static org.junit.Assert.assertEquals; 10 | 11 | public class PoolTest { 12 | private Pool pool; 13 | 14 | @Before 15 | public void before() { 16 | pool = new Pool(); 17 | } 18 | 19 | @Test 20 | public void shouldCalculateSizeOfPool() { 21 | assertEquals(0, pool.size()); 22 | pool.add(255, "Hello".getBytes()); 23 | assertEquals(7, pool.size()); 24 | } 25 | 26 | @Test(expected = IllegalArgumentException.class) 27 | public void shouldFailIfSourceIsLessThanZero() { 28 | pool.add(-1, "Hello".getBytes()); 29 | } 30 | 31 | @Test(expected = IllegalArgumentException.class) 32 | public void shouldFailIfSourceIsGreaterThan255() { 33 | pool.add(256, "Hello".getBytes()); 34 | } 35 | 36 | @Test(expected = IllegalArgumentException.class) 37 | public void shouldFailIfEventIsEmpty() { 38 | pool.add(0, new byte[0]); 39 | } 40 | 41 | @Test(expected = IllegalArgumentException.class) 42 | public void shouldFailIfEventLengthIsGreaterThan32() { 43 | pool.add(0, new byte[33]); 44 | } 45 | 46 | @Test 47 | public void shouldClearPoolAfterGetting() throws Exception { 48 | MessageDigest digest = MessageDigest.getInstance("SHA-256"); 49 | byte[] bytes = new byte[34]; 50 | bytes[0] = (byte) 123; 51 | bytes[1] = (byte) 32; 52 | 53 | pool.add(123, new byte[32]); 54 | assertEquals(34, pool.size()); 55 | assertArrayEquals(digest.digest(bytes), pool.getAndClear()); 56 | assertEquals(0, pool.size()); 57 | 58 | pool.add(123, new byte[32]); 59 | assertEquals(34, pool.size()); 60 | assertArrayEquals(digest.digest(bytes), pool.getAndClear()); 61 | assertEquals(0, pool.size()); 62 | } 63 | 64 | @Test 65 | public void shouldGet32BytesOfSeedData() { 66 | byte[] bytes = pool.getAndClear(); 67 | assertEquals(32, bytes.length); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/grunka/random/fortuna/RandomDataBuffer.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna; 2 | 3 | import java.util.concurrent.locks.ReentrantLock; 4 | import java.util.function.Supplier; 5 | 6 | class RandomDataBuffer { 7 | private final ReentrantLock lock = new ReentrantLock(); 8 | private byte[] buffer = new byte[0]; 9 | private int remainingBits = 0; 10 | 11 | int next(int bits, Supplier randomDataSupplier) { 12 | lock.lock(); 13 | try { 14 | int result = 0; 15 | int bitsStillToTake = bits; 16 | while (bitsStillToTake > 0) { 17 | if (remainingBits == 0) { 18 | buffer = randomDataSupplier.get(); 19 | remainingBits = buffer.length * 8; 20 | if (remainingBits <= 0) { 21 | throw new IllegalStateException("Could not get more bits"); 22 | } 23 | } 24 | int remainingBitsInByte = remainingBits % 8; 25 | if (remainingBitsInByte == 0) { 26 | remainingBitsInByte = 8; 27 | } 28 | int currentByte = buffer.length - (remainingBits / 8) - (remainingBitsInByte == 8 ? 0 : 1); 29 | int bitsToTakeNow = Math.min(bitsStillToTake, remainingBitsInByte); 30 | result = (result << bitsToTakeNow) | (((buffer[currentByte] >>> (remainingBitsInByte - bitsToTakeNow)) & 0xff) & mask(bitsToTakeNow)); 31 | remainingBits -= bitsToTakeNow; 32 | bitsStillToTake -= bitsToTakeNow; 33 | } 34 | return result; 35 | } finally { 36 | lock.unlock(); 37 | } 38 | } 39 | 40 | private int mask(int bits) { 41 | return switch (bits) { 42 | case 1 -> (byte) 0b1; 43 | case 2 -> (byte) 0b11; 44 | case 3 -> (byte) 0b111; 45 | case 4 -> (byte) 0b1111; 46 | case 5 -> (byte) 0b11111; 47 | case 6 -> (byte) 0b111111; 48 | case 7 -> (byte) 0b1111111; 49 | case 8 -> (byte) 0b11111111; 50 | default -> throw new IllegalArgumentException("Too many bits or no bits at all"); 51 | }; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/com/grunka/random/fortuna/PrefetchingSupplierTest.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna; 2 | 3 | import org.junit.After; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.List; 10 | import java.util.concurrent.ExecutionException; 11 | import java.util.concurrent.ExecutorService; 12 | import java.util.concurrent.Executors; 13 | import java.util.concurrent.Future; 14 | import java.util.concurrent.atomic.AtomicInteger; 15 | 16 | import static org.junit.Assert.assertEquals; 17 | 18 | public class PrefetchingSupplierTest { 19 | private ExecutorService executorService; 20 | private List sleeps; 21 | 22 | @Before 23 | public void setUp() { 24 | sleeps = new ArrayList<>(Arrays.asList(200, 150, 100, 50, 0)); 25 | executorService = Executors.newFixedThreadPool(5); 26 | } 27 | 28 | @After 29 | public void tearDown() { 30 | executorService.shutdown(); 31 | } 32 | 33 | @Test 34 | public void shouldGetValues() { 35 | AtomicInteger number = new AtomicInteger(); 36 | PrefetchingSupplier prefetcher = new PrefetchingSupplier<>(() -> "hello " + number.getAndIncrement(), executorService); 37 | assertEquals("hello 0", prefetcher.get()); 38 | assertEquals("hello 1", prefetcher.get()); 39 | assertEquals("hello 2", prefetcher.get()); 40 | } 41 | 42 | @Test 43 | public void shouldBeOrderedAndCorrectNumberOfOutputs() throws ExecutionException, InterruptedException { 44 | AtomicInteger number = new AtomicInteger(); 45 | PrefetchingSupplier prefetcher = new PrefetchingSupplier<>(() -> { 46 | sleep(); 47 | return number.getAndIncrement(); 48 | }, executorService); 49 | List values = new ArrayList<>(); 50 | List> futures = new ArrayList<>(); 51 | for (int i = 0; i < 5; i++) { 52 | futures.add(executorService.submit(() -> values.add(prefetcher.get()))); 53 | } 54 | for (Future future : futures) { 55 | future.get(); 56 | } 57 | assertEquals(Arrays.asList(0, 1, 2, 3, 4), values); 58 | } 59 | 60 | private void sleep() { 61 | try { 62 | Thread.sleep(sleeps.remove(0)); 63 | } catch (InterruptedException e) { 64 | throw new Error(e); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/com/grunka/random/fortuna/CounterTest.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import static org.junit.Assert.*; 7 | 8 | public class CounterTest { 9 | 10 | private Counter counter; 11 | 12 | @Before 13 | public void before() { 14 | counter = new Counter(128); 15 | } 16 | 17 | @Test(expected = IllegalArgumentException.class) 18 | public void shouldFailForInvalidNumberOfBits() { 19 | new Counter(127); 20 | } 21 | 22 | @Test(expected = IllegalArgumentException.class) 23 | public void shouldFailForTooFewBits() { 24 | new Counter(0); 25 | } 26 | 27 | @Test 28 | public void shouldRollOverBackToZero() { 29 | Counter smallCounter = new Counter(8); 30 | for (int i = 0; i < 256; i++) { 31 | smallCounter.increment(); 32 | } 33 | assertTrue(smallCounter.isZero()); 34 | } 35 | 36 | @Test 37 | public void shouldBeZero() { 38 | assertTrue(counter.isZero()); 39 | } 40 | 41 | @Test 42 | public void shouldIncrementOneStep() { 43 | counter.increment(); 44 | byte[] state = counter.getState(); 45 | assertEquals(16, state.length); 46 | assertEquals(1, state[0]); 47 | assertFalse(counter.isZero()); 48 | } 49 | 50 | @Test 51 | public void shouldFillFirstByteWithOnes() { 52 | for (int i = 0; i < 255; i++) { 53 | counter.increment(); 54 | } 55 | byte[] state = counter.getState(); 56 | assertEquals((byte) 0xff, state[0]); 57 | } 58 | 59 | @Test 60 | public void shouldRollOverIntoNextByte() { 61 | for (int i = 0; i < 256; i++) { 62 | counter.increment(); 63 | } 64 | byte[] state = counter.getState(); 65 | assertEquals((byte) 0x0, state[0]); 66 | assertEquals((byte) 0x1, state[1]); 67 | } 68 | 69 | @Test 70 | public void shouldRollOverIntoNextByteAgain() { 71 | for (int i = 0; i < 257; i++) { 72 | counter.increment(); 73 | } 74 | byte[] state = counter.getState(); 75 | assertEquals((byte) 0x1, state[0]); 76 | assertEquals((byte) 0x1, state[1]); 77 | } 78 | 79 | @Test 80 | public void shouldRollOverIntoThirdByte() { 81 | for (int i = 0; i < 256*256; i++) { 82 | counter.increment(); 83 | } 84 | byte[] state = counter.getState(); 85 | assertEquals((byte) 0x0, state[0]); 86 | assertEquals((byte) 0x0, state[1]); 87 | assertEquals((byte) 0x1, state[2]); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fortuna 2 | 3 | This is an implementation of the Fortuna PRNG, read more at [wikipedia][fortuna] or in the book [Cryptographic Engineering][ce] by Bruce Schneier. 4 | 5 | ## Usage 6 | 7 | Add it as a maven dependency 8 | 9 | ```xml 10 | 11 | com.grunka.random.fortuna 12 | fortuna 13 | 2.2 14 | 15 | ``` 16 | 17 | or clone and build (nothing more than `mvn install` is needed) and add to classpath. 18 | 19 | Due to reasons there are a couple of ways to create an instance. Either the constructor or the methods `createInstance` on [com.grunka.random.fortuna.Fortuna][fortuna_class]. Both of these ways create a subclass of Javas own Random class so this can be used wherever that would be used. The instance created should be reused. It is thread safe, creation time is noticeable, and you do not gain anything by recreating it. Due to the background threads collecting entropy running continuously you should call `shutdown` on the instance if you will actually not use the instance anymore. 20 | 21 | There is an included runnable class for outputting random data for testing purposes. [com.grunka.random.fortuna.Dump][dump_class]. Dump outputs a specified number of megabytes of random data that can be used in other tools that analyze random data. 22 | 23 | ## Details 24 | 25 | For specifics either read the book referenced above and have a look at the code. Below are some descriptions of the specific choices taken for this implementation. 26 | 27 | ### Block cipher 28 | 29 | The block cipher used is a public domain implementation of [AES-256][aes256], in the code it uses the original name Rijndael. The reason for not using Javas own implementation is to avoid the system configuration changes needed to be allowed to use it since Java in its default configuration only allows up to 128-bit keys. 30 | 31 | ### Entropy sources 32 | 33 | For entropy sources I've selected several system dependant sources that are available to the Java runtime. In the current version there are [nine sources][entropy_sources]. For most of the values only the two least significant bytes are used which ensures that the values are fairly unpredictable even though they are polled often. 34 | 35 | [fortuna]: http://en.wikipedia.org/wiki/Fortuna_(PRNG) 36 | [ce]: http://www.schneier.com/book-ce.html 37 | [aes256]: http://en.wikipedia.org/wiki/Advanced_Encryption_Standard 38 | [entropy_sources]: https://github.com/grunka/fortuna/tree/master/src/main/java/com/grunka/random/fortuna/entropy 39 | [dump_class]: https://github.com/grunka/fortuna/blob/master/src/main/java/com/grunka/random/fortuna/tests/Dump.java 40 | [fortuna_class]: https://github.com/grunka/fortuna/blob/master/src/main/java/com/grunka/random/fortuna/Fortuna.java 41 | -------------------------------------------------------------------------------- /docs/Fortuna.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | Fortuna PRNG 8 | ============ 9 | 10 | Abstract 11 | -------- 12 | This document will give a short summary about the Fortuna pseudo random number generator in general and also some details about this specific implementation. 13 | 14 | The algorithm 15 | ------------- 16 | The very short summary is that it is a normal pseudo random generator using a cryptographic function but with several additions that adds a lot of unpredicability to it. 17 | 18 | The algorithm consists of several parts, the generator, accumulator and a set of entropy sources. 19 | 20 | The generator is the part that generates the random data. It does this by using a block cipher with a 256-bit key to encrypt a 128-bit counter. The counter is incremented for each block of random data generated and the key is replaced by generating two additional blocks after each request for blocks of random data. 21 | 22 | Reseeding of the generator is done by using the SHA-256 hash function to hash the current cipher key together with the seed data supplied and replacing the key with the result. 23 | 24 | The accumulator uses entropy sources to collect data which will be used to reseed the generator. Each entropy source tries to be an upredictable provider of bytes of data. The sources put their bytes of data into pools of collected entropy. There are 32 pools and each source separately keeps track of when it should add data and which pool it should put the data into, simply looping over them. 25 | 26 | Entropy sources can be basically anything, as long as at least one of them is unpredictable for an attacker either in the data provided or the timing of the data provided an attacker will not be able to predict the key that eventually will be supplied to the generator. 27 | 28 | The accumulated data is used to create seed data for the generator by picking a number of pools of entropy data and using SHA-256 to hash their data. Pools are reset when their data is used. Pools are picked if 2 to the power of the number of the pool is a divisor of the total number of reseeds done. This means that the first pool is used every reseed, the second is used every other, the third is used every fourth and so on. 29 | 30 | Reseeding is done automatically when there is enough entropy data available. This is determined by looking at the first pool of data, since it is used for every reseed, and simply checking if it has more than a set amount of bytes. This is checked every time random data is fetched. 31 | 32 | For more specifics and also calculations showing the robustness of the algorithm read chapter 9 of the book Cryptographic Engineering (ISBN: 9780470474242) where this algorithm is described by the creators of it. 33 | 34 |
35 | 36 | Implementation specifics 37 | ------------------------ 38 | This implementation is in pure java and considerations were made to make it as easy to use as possible and to avoid potential mistakes when adding it to a project. 39 | 40 | For the generator a public domain implementation of the AES-256 cipher was chosen over Java:s own implementation since it requires additional system configuration. 41 | 42 | As of the writing of this document the implementations has seven different entropy sources based on different things that are available to the JavaVM. For instance thread timing, memory usage and garbage collection statistics all of which are hard to affect in a predictable manner. 43 | -------------------------------------------------------------------------------- /src/main/java/com/grunka/random/fortuna/tests/Dump.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna.tests; 2 | 3 | import com.grunka.random.fortuna.Fortuna; 4 | 5 | import java.io.BufferedOutputStream; 6 | import java.io.FileNotFoundException; 7 | import java.io.FileOutputStream; 8 | import java.io.IOException; 9 | import java.io.OutputStream; 10 | import java.math.BigInteger; 11 | import java.util.Arrays; 12 | import java.util.regex.Matcher; 13 | import java.util.regex.Pattern; 14 | 15 | public class Dump { 16 | //TODO decide which dump method to use, maybe combine 17 | 18 | private static final int MEGABYTE = 1024 * 1024; 19 | 20 | // Compression test: xz -e9zvkf random.data 21 | 22 | public static void main(String[] args) throws Exception { 23 | if (args.length < 1 || args.length > 2) { 24 | usage(); 25 | System.exit(args.length == 0 ? 0 : 1); 26 | } 27 | long megabytes = 0; 28 | try { 29 | megabytes = Long.parseLong(args[0]); 30 | } catch (NumberFormatException e) { 31 | usage(); 32 | System.err.println("Megabytes was not a number: " + args[0]); 33 | System.exit(1); 34 | } 35 | if (megabytes < 1) { 36 | usage(); 37 | System.err.println("Needs to be at least one megabyte, was " + megabytes); 38 | System.exit(1); 39 | } 40 | long dataSize = megabytes * MEGABYTE; 41 | System.err.println("Initializing RNG..."); 42 | Fortuna fortuna = Fortuna.createInstance(); 43 | long start = System.currentTimeMillis(); 44 | System.err.println("Generating data..."); 45 | try (OutputStream output = getOutputStream(args)) { 46 | try (OutputStream outputStream = new BufferedOutputStream(output)) { 47 | byte[] buffer = new byte[1024]; 48 | long remainingBytes = dataSize; 49 | while (remainingBytes > 0) { 50 | fortuna.nextBytes(buffer); 51 | outputStream.write(buffer); 52 | remainingBytes -= buffer.length; 53 | System.err.print((100 * (dataSize - remainingBytes) / dataSize) + "%\r"); 54 | } 55 | } 56 | } 57 | System.err.println("Done in " + ((System.currentTimeMillis() - start) / 1000) + " seconds"); 58 | fortuna.shutdown(); 59 | } 60 | 61 | private static OutputStream getOutputStream(String[] args) throws FileNotFoundException { 62 | if (args.length == 2) { 63 | return new FileOutputStream(args[1], false); 64 | } else { 65 | return System.out; 66 | } 67 | } 68 | 69 | private static void usage() { 70 | System.err.println("Usage: " + Dump.class.getName() + " []"); 71 | System.err.println("Will generate of data and output them either to or stdout if is not specified"); 72 | } 73 | 74 | public static void otherMain(String[] args) throws IOException, InterruptedException { 75 | boolean hasLimit = false; 76 | BigInteger limit = BigInteger.ZERO; 77 | if (args.length == 1) { 78 | String amount = args[0]; 79 | Matcher matcher = Pattern.compile("^([1-9][0-9]*)([KMG])?$").matcher(amount); 80 | if (matcher.matches()) { 81 | String number = matcher.group(1); 82 | String suffix = matcher.group(2); 83 | limit = new BigInteger(number); 84 | if (suffix != null) { 85 | switch (suffix) { 86 | case "K" -> limit = limit.multiply(BigInteger.valueOf(1024)); 87 | case "M" -> limit = limit.multiply(BigInteger.valueOf(1024 * 1024)); 88 | case "G" -> limit = limit.multiply(BigInteger.valueOf(1024 * 1024 * 1024)); 89 | default -> { 90 | System.err.println("Unrecognized suffix"); 91 | System.exit(1); 92 | } 93 | } 94 | } 95 | hasLimit = true; 96 | } else { 97 | System.err.println("Unrecognized amount " + amount); 98 | System.exit(1); 99 | } 100 | } else if (args.length > 1) { 101 | System.err.println("Unrecognized parameters " + Arrays.toString(args)); 102 | System.exit(1); 103 | } 104 | Fortuna fortuna = Fortuna.createInstance(); 105 | final BigInteger chunk = BigInteger.valueOf(4 * 1024); 106 | while (!hasLimit || limit.compareTo(BigInteger.ZERO) > 0) { 107 | final byte[] buffer; 108 | if (hasLimit) { 109 | if (chunk.compareTo(limit) < 0) { 110 | buffer = new byte[chunk.intValue()]; 111 | limit = limit.subtract(chunk); 112 | } else { 113 | buffer = new byte[limit.intValue()]; 114 | limit = BigInteger.ZERO; 115 | } 116 | } else { 117 | buffer = new byte[chunk.intValue()]; 118 | } 119 | fortuna.nextBytes(buffer); 120 | System.out.write(buffer); 121 | } 122 | fortuna.shutdown(); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.grunka.random.fortuna 8 | fortuna 9 | 2.4-SNAPSHOT 10 | 11 | Fortuna 12 | An implementation of the Fortuna RNG 13 | https://github.com/grunka/fortuna 14 | 15 | 16 | MIT License 17 | https://opensource.org/licenses/MIT 18 | 19 | 20 | 21 | 22 | 23 | John Wästerlund 24 | john@grunka.com 25 | grunka 26 | https://github.com/grunka 27 | 28 | 29 | 30 | 31 | scm:git:git://github.com/grunka/fortuna.git 32 | scm:git:ssh://github.com:grunka/fortuna.git 33 | https://github.com/grunka/fortuna/tree/master 34 | 35 | 36 | 37 | UTF-8 38 | 39 | 40 | 41 | 42 | 43 | org.apache.maven.plugins 44 | maven-compiler-plugin 45 | 3.14.1 46 | 47 | 17 48 | 49 | 50 | 51 | org.apache.maven.plugins 52 | maven-source-plugin 53 | 3.4.0 54 | 55 | 56 | attach-sources 57 | 58 | jar-no-fork 59 | 60 | 61 | 62 | 63 | 64 | org.apache.maven.plugins 65 | maven-javadoc-plugin 66 | 3.12.0 67 | 68 | 69 | attach-javadocs 70 | 71 | jar 72 | 73 | 74 | 75 | 76 | true 77 | false 78 | true 79 | none 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | github-release 88 | 89 | 90 | github 91 | https://maven.pkg.github.com/grunka/fortuna 92 | 93 | 94 | 95 | 96 | release 97 | 98 | 99 | 100 | org.sonatype.central 101 | central-publishing-maven-plugin 102 | 0.9.0 103 | true 104 | 105 | central 106 | 107 | 108 | 109 | 110 | 111 | org.apache.maven.plugins 112 | maven-gpg-plugin 113 | 3.2.8 114 | 115 | 116 | sign-artifacts 117 | verify 118 | 119 | sign 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | junit 132 | junit 133 | 4.13.2 134 | test 135 | 136 | 137 | org.apache.commons 138 | commons-math3 139 | 3.6.1 140 | test 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /src/main/java/com/grunka/random/fortuna/Fortuna.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna; 2 | 3 | import com.grunka.random.fortuna.accumulator.Accumulator; 4 | import com.grunka.random.fortuna.entropy.BufferPoolEntropySource; 5 | import com.grunka.random.fortuna.entropy.FreeMemoryEntropySource; 6 | import com.grunka.random.fortuna.entropy.GarbageCollectorEntropySource; 7 | import com.grunka.random.fortuna.entropy.LoadAverageEntropySource; 8 | import com.grunka.random.fortuna.entropy.MemoryPoolEntropySource; 9 | import com.grunka.random.fortuna.entropy.SchedulingEntropySource; 10 | import com.grunka.random.fortuna.entropy.ThreadTimeEntropySource; 11 | import com.grunka.random.fortuna.entropy.URandomEntropySource; 12 | import com.grunka.random.fortuna.entropy.UptimeEntropySource; 13 | 14 | import java.nio.file.Files; 15 | import java.nio.file.Paths; 16 | import java.util.Arrays; 17 | import java.util.Random; 18 | import java.util.concurrent.Executors; 19 | import java.util.concurrent.ScheduledExecutorService; 20 | import java.util.concurrent.ThreadFactory; 21 | import java.util.concurrent.TimeUnit; 22 | 23 | public class Fortuna extends Random { 24 | private static final int MIN_POOL_SIZE = 64; 25 | private static final int[] POWERS_OF_TWO = initializePowersOfTwo(); 26 | private static final int RANDOM_DATA_CHUNK_SIZE = 128 * 1024; 27 | 28 | private static int[] initializePowersOfTwo() { 29 | int[] result = new int[32]; 30 | for (int power = 0; power < result.length; power++) { 31 | result[power] = (int) StrictMath.pow(2, power); 32 | } 33 | return result; 34 | } 35 | 36 | private long lastReseedTime = 0; 37 | private long reseedCount = 0; 38 | private final RandomDataBuffer randomDataBuffer; 39 | private final Generator generator; 40 | private final Accumulator accumulator; 41 | private final ScheduledExecutorService scheduler; 42 | private final boolean createdScheduler; 43 | private final PrefetchingSupplier randomDataPrefetcher; 44 | 45 | private static ScheduledExecutorService createDefaultScheduler() { 46 | return Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { 47 | private final ThreadFactory delegate = Executors.defaultThreadFactory(); 48 | 49 | @Override 50 | public Thread newThread(Runnable r) { 51 | Thread thread = delegate.newThread(r); 52 | thread.setDaemon(true); 53 | return thread; 54 | } 55 | }); 56 | } 57 | 58 | public static Fortuna createInstance() { 59 | return new Fortuna(); 60 | } 61 | 62 | @SuppressWarnings("WeakerAccess") 63 | public static Fortuna createInstance(ScheduledExecutorService scheduler) { 64 | return new Fortuna(scheduler); 65 | } 66 | 67 | public Fortuna() { 68 | ScheduledExecutorService scheduler = createDefaultScheduler(); 69 | this.createdScheduler = true; 70 | this.generator = new Generator(); 71 | this.randomDataBuffer = new RandomDataBuffer(); 72 | this.accumulator = createAccumulator(scheduler); 73 | this.randomDataPrefetcher = new PrefetchingSupplier<>(this::randomData, scheduler); 74 | this.scheduler = scheduler; 75 | } 76 | 77 | public Fortuna(ScheduledExecutorService scheduler) { 78 | this.createdScheduler = false; 79 | this.generator = new Generator(); 80 | this.randomDataBuffer = new RandomDataBuffer(); 81 | this.accumulator = createAccumulator(scheduler); 82 | this.randomDataPrefetcher = new PrefetchingSupplier<>(this::randomData, scheduler); 83 | this.scheduler = scheduler; 84 | } 85 | 86 | private static Accumulator createAccumulator(ScheduledExecutorService scheduler) { 87 | Pool[] pools = new Pool[32]; 88 | for (int pool = 0; pool < pools.length; pool++) { 89 | pools[pool] = new Pool(); 90 | } 91 | Accumulator accumulator = new Accumulator(pools, scheduler); 92 | accumulator.addSource(new SchedulingEntropySource()); 93 | accumulator.addSource(new GarbageCollectorEntropySource()); 94 | accumulator.addSource(new LoadAverageEntropySource()); 95 | accumulator.addSource(new FreeMemoryEntropySource()); 96 | accumulator.addSource(new ThreadTimeEntropySource()); 97 | accumulator.addSource(new UptimeEntropySource()); 98 | accumulator.addSource(new BufferPoolEntropySource()); 99 | accumulator.addSource(new MemoryPoolEntropySource()); 100 | if (Files.exists(Paths.get("/dev/urandom"))) { 101 | accumulator.addSource(new URandomEntropySource()); 102 | } 103 | while (pools[0].size() < MIN_POOL_SIZE) { 104 | try { 105 | Thread.sleep(10); 106 | } catch (InterruptedException e) { 107 | throw new Error("Interrupted while waiting for initialization", e); 108 | } 109 | } 110 | return accumulator; 111 | } 112 | 113 | private byte[] randomData() { 114 | long now = System.currentTimeMillis(); 115 | Pool[] pools = accumulator.getPools(); 116 | if (pools[0].size() >= MIN_POOL_SIZE && now - lastReseedTime > 100) { 117 | lastReseedTime = now; 118 | reseedCount++; 119 | byte[] seed = new byte[pools.length * 32]; // Maximum potential length 120 | int seedLength = 0; 121 | for (int pool = 0; pool < pools.length; pool++) { 122 | if (reseedCount % POWERS_OF_TWO[pool] == 0) { 123 | System.arraycopy(pools[pool].getAndClear(), 0, seed, seedLength, 32); 124 | seedLength += 32; 125 | } 126 | } 127 | generator.reseed(Arrays.copyOf(seed, seedLength)); 128 | } 129 | if (reseedCount == 0) { 130 | throw new IllegalStateException("Generator not reseeded yet"); 131 | } else { 132 | return generator.pseudoRandomData(RANDOM_DATA_CHUNK_SIZE); 133 | } 134 | } 135 | 136 | @Override 137 | protected int next(int bits) { 138 | return randomDataBuffer.next(bits, randomDataPrefetcher); 139 | } 140 | 141 | @Override 142 | public synchronized void setSeed(long seed) { 143 | // Does not do anything 144 | } 145 | 146 | @SuppressWarnings("WeakerAccess") 147 | public void shutdown(long timeout, TimeUnit unit) throws InterruptedException { 148 | randomDataPrefetcher.shutdownPrefetch(); 149 | accumulator.shutdownSources(); 150 | if (createdScheduler) { 151 | scheduler.shutdown(); 152 | 153 | if (!scheduler.awaitTermination(timeout, unit)) { 154 | scheduler.shutdownNow(); 155 | } 156 | } 157 | } 158 | 159 | public void shutdown() throws InterruptedException { 160 | shutdown(30, TimeUnit.SECONDS); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/test/java/com/grunka/random/fortuna/FortunaTest.java: -------------------------------------------------------------------------------- 1 | package com.grunka.random.fortuna; 2 | 3 | import org.apache.commons.math3.distribution.UniformRealDistribution; 4 | import org.apache.commons.math3.random.ISAACRandom; 5 | import org.apache.commons.math3.random.MersenneTwister; 6 | import org.apache.commons.math3.stat.descriptive.SummaryStatistics; 7 | import org.junit.Test; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Collection; 11 | import java.util.List; 12 | import java.util.concurrent.*; 13 | 14 | import static org.junit.Assert.*; 15 | 16 | public class FortunaTest { 17 | @Test 18 | public void shouldCreateInstanceAndWaitForInitialization() { 19 | Fortuna fortuna = Fortuna.createInstance(); 20 | try { 21 | fortuna.nextInt(42); 22 | } catch (IllegalStateException ignored) { 23 | fail("Did not wait for initialization"); 24 | } 25 | } 26 | 27 | @Test 28 | public void shouldProduceEvenDistribution() { 29 | int numbers = 1_000; 30 | SummaryStatistics fortunaNumbers = new SummaryStatistics(); 31 | SummaryStatistics isaacNumbers = new SummaryStatistics(); 32 | SummaryStatistics mersenneNumbers = new SummaryStatistics(); 33 | Fortuna fortuna = Fortuna.createInstance(); 34 | ISAACRandom isaacRandom = new ISAACRandom(); 35 | MersenneTwister mersenneTwister = new MersenneTwister(); 36 | for (int i = 0; i < 10_000_000; i++) { 37 | fortunaNumbers.addValue(fortuna.nextInt(numbers)); 38 | isaacNumbers.addValue(isaacRandom.nextInt(numbers)); 39 | mersenneNumbers.addValue(mersenneTwister.nextInt(numbers)); 40 | } 41 | double varFortuna = fortunaNumbers.getVariance(); 42 | double varIsaac = isaacNumbers.getVariance(); 43 | double varMersenne = mersenneNumbers.getVariance(); 44 | double varUni = new UniformRealDistribution(0, numbers).getNumericalVariance(); 45 | System.out.println("Variances: Fortuna " + varFortuna + ", ISAAC " + varIsaac + ", Mersenne " + varMersenne + ", Uniform " + varUni); 46 | double percentDifferenceFortuna = (varFortuna - varUni) / varUni; 47 | System.out.println("UniformRealDistribution vs Fortuna variance difference percent: " + percentDifferenceFortuna * 100 + " %"); 48 | double percentDifferenceIsaac = (varIsaac - varUni) / varUni; 49 | System.out.println("UniformRealDistribution vs ISAAC variance difference percent: " + percentDifferenceIsaac * 100 + " %"); 50 | double percentDifferenceMersenne = (varMersenne - varUni) / varUni; 51 | System.out.println("UniformRealDistribution vs Mersenne variance difference percent: " + percentDifferenceMersenne * 100 + " %"); 52 | assertEquals("UniformRealDistribution vs Fortuna variance", 0.0, percentDifferenceFortuna, 0.01); 53 | } 54 | 55 | @Test 56 | public void shouldNotShutdownPassedInScheduledExecutorService() throws InterruptedException { 57 | ScheduledExecutorService delegate = Executors.newSingleThreadScheduledExecutor(); 58 | try { 59 | MockScheduledExecutorService scheduler = new MockScheduledExecutorService(delegate); 60 | Fortuna fortuna = Fortuna.createInstance(scheduler); 61 | fortuna.shutdown(); 62 | scheduler.getCreatedFutures().forEach(f -> assertTrue("Future was not cancelled", f.isCancelled())); 63 | assertFalse("Scheduler was shut down", scheduler.isShutdown()); 64 | } finally { 65 | delegate.shutdown(); 66 | } 67 | } 68 | 69 | private static class MockScheduledExecutorService implements ScheduledExecutorService { 70 | private final ScheduledExecutorService delegate; 71 | private final List> futures = new ArrayList<>(); 72 | 73 | MockScheduledExecutorService(ScheduledExecutorService delegate) { 74 | this.delegate = delegate; 75 | } 76 | 77 | @Override 78 | public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { 79 | throw new UnsupportedOperationException(); 80 | } 81 | 82 | @Override 83 | public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { 84 | throw new UnsupportedOperationException(); 85 | } 86 | 87 | @Override 88 | public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { 89 | throw new UnsupportedOperationException(); 90 | } 91 | 92 | @Override 93 | public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { 94 | ScheduledFuture future = delegate.scheduleWithFixedDelay(command, initialDelay, delay, unit); 95 | futures.add(future); 96 | return future; 97 | } 98 | 99 | @Override 100 | public void shutdown() { 101 | delegate.shutdown(); 102 | } 103 | 104 | @Override 105 | public List shutdownNow() { 106 | throw new UnsupportedOperationException(); 107 | } 108 | 109 | @Override 110 | public boolean isShutdown() { 111 | return delegate.isShutdown(); 112 | } 113 | 114 | @Override 115 | public boolean isTerminated() { 116 | return delegate.isTerminated(); 117 | } 118 | 119 | @Override 120 | public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { 121 | return delegate.awaitTermination(timeout, unit); 122 | } 123 | 124 | @Override 125 | public Future submit(Callable task) { 126 | return delegate.submit(task); 127 | } 128 | 129 | @Override 130 | public Future submit(Runnable task, T result) { 131 | throw new UnsupportedOperationException(); 132 | } 133 | 134 | @Override 135 | public Future submit(Runnable task) { 136 | throw new UnsupportedOperationException(); 137 | } 138 | 139 | @Override 140 | public List> invokeAll(Collection> tasks) { 141 | throw new UnsupportedOperationException(); 142 | } 143 | 144 | @Override 145 | public List> invokeAll(Collection> tasks, long timeout, TimeUnit unit) { 146 | throw new UnsupportedOperationException(); 147 | } 148 | 149 | @Override 150 | public T invokeAny(Collection> tasks) { 151 | throw new UnsupportedOperationException(); 152 | } 153 | 154 | @Override 155 | public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) { 156 | throw new UnsupportedOperationException(); 157 | } 158 | 159 | @Override 160 | public void execute(Runnable command) { 161 | throw new UnsupportedOperationException(); 162 | } 163 | 164 | List> getCreatedFutures() { 165 | return futures; 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /reports/fortuna.txt: -------------------------------------------------------------------------------- 1 | #=============================================================================# 2 | # dieharder version 3.31.1 Copyright 2003 Robert G. Brown # 3 | #=============================================================================# 4 | rng_name |rands/second| Seed | 5 | stdin_input_raw| 2.46e+06 |3033161379| 6 | #=============================================================================# 7 | test_name |ntup| tsamples |psamples| p-value |Assessment 8 | #=============================================================================# 9 | diehard_birthdays| 0| 100| 100|0.79980283| PASSED 10 | diehard_operm5| 0| 1000000| 100|0.84733278| PASSED 11 | diehard_rank_32x32| 0| 40000| 100|0.24914396| PASSED 12 | diehard_rank_6x8| 0| 100000| 100|0.02581662| PASSED 13 | diehard_bitstream| 0| 2097152| 100|0.59900269| PASSED 14 | diehard_opso| 0| 2097152| 100|0.56622672| PASSED 15 | diehard_oqso| 0| 2097152| 100|0.40607505| PASSED 16 | diehard_dna| 0| 2097152| 100|0.62220146| PASSED 17 | diehard_count_1s_str| 0| 256000| 100|0.86973770| PASSED 18 | diehard_count_1s_byt| 0| 256000| 100|0.04383540| PASSED 19 | diehard_parking_lot| 0| 12000| 100|0.91232487| PASSED 20 | diehard_2dsphere| 2| 8000| 100|0.95471540| PASSED 21 | diehard_3dsphere| 3| 4000| 100|0.82926341| PASSED 22 | diehard_squeeze| 0| 100000| 100|0.36951001| PASSED 23 | diehard_sums| 0| 100| 100|0.00811282| PASSED 24 | diehard_runs| 0| 100000| 100|0.74561792| PASSED 25 | diehard_runs| 0| 100000| 100|0.62804425| PASSED 26 | diehard_craps| 0| 200000| 100|0.20473344| PASSED 27 | diehard_craps| 0| 200000| 100|0.47463198| PASSED 28 | marsaglia_tsang_gcd| 0| 10000000| 100|0.56028504| PASSED 29 | marsaglia_tsang_gcd| 0| 10000000| 100|0.24814876| PASSED 30 | sts_monobit| 1| 100000| 100|0.16394762| PASSED 31 | sts_runs| 2| 100000| 100|0.82580654| PASSED 32 | sts_serial| 1| 100000| 100|0.86636801| PASSED 33 | sts_serial| 2| 100000| 100|0.60501844| PASSED 34 | sts_serial| 3| 100000| 100|0.80549075| PASSED 35 | sts_serial| 3| 100000| 100|0.22003103| PASSED 36 | sts_serial| 4| 100000| 100|0.79793208| PASSED 37 | sts_serial| 4| 100000| 100|0.55649956| PASSED 38 | sts_serial| 5| 100000| 100|0.99845228| WEAK 39 | sts_serial| 5| 100000| 100|0.64594976| PASSED 40 | sts_serial| 6| 100000| 100|0.84202705| PASSED 41 | sts_serial| 6| 100000| 100|0.88712826| PASSED 42 | sts_serial| 7| 100000| 100|0.85321155| PASSED 43 | sts_serial| 7| 100000| 100|0.38619394| PASSED 44 | sts_serial| 8| 100000| 100|0.72954913| PASSED 45 | sts_serial| 8| 100000| 100|0.92844535| PASSED 46 | sts_serial| 9| 100000| 100|0.85295932| PASSED 47 | sts_serial| 9| 100000| 100|0.98393051| PASSED 48 | sts_serial| 10| 100000| 100|0.29167418| PASSED 49 | sts_serial| 10| 100000| 100|0.36699665| PASSED 50 | sts_serial| 11| 100000| 100|0.89834126| PASSED 51 | sts_serial| 11| 100000| 100|0.98398314| PASSED 52 | sts_serial| 12| 100000| 100|0.88185465| PASSED 53 | sts_serial| 12| 100000| 100|0.59270951| PASSED 54 | sts_serial| 13| 100000| 100|0.82781119| PASSED 55 | sts_serial| 13| 100000| 100|0.96982474| PASSED 56 | sts_serial| 14| 100000| 100|0.88301712| PASSED 57 | sts_serial| 14| 100000| 100|0.71022384| PASSED 58 | sts_serial| 15| 100000| 100|0.47752301| PASSED 59 | sts_serial| 15| 100000| 100|0.91713798| PASSED 60 | sts_serial| 16| 100000| 100|0.89928475| PASSED 61 | sts_serial| 16| 100000| 100|0.63212806| PASSED 62 | rgb_bitdist| 1| 100000| 100|0.39544385| PASSED 63 | rgb_bitdist| 2| 100000| 100|0.07713804| PASSED 64 | rgb_bitdist| 3| 100000| 100|0.69878213| PASSED 65 | rgb_bitdist| 4| 100000| 100|0.06662999| PASSED 66 | rgb_bitdist| 5| 100000| 100|0.26467489| PASSED 67 | rgb_bitdist| 6| 100000| 100|0.57118092| PASSED 68 | rgb_bitdist| 7| 100000| 100|0.82021003| PASSED 69 | rgb_bitdist| 8| 100000| 100|0.75733659| PASSED 70 | rgb_bitdist| 9| 100000| 100|0.85318878| PASSED 71 | rgb_bitdist| 10| 100000| 100|0.05582842| PASSED 72 | rgb_bitdist| 11| 100000| 100|0.58200702| PASSED 73 | rgb_bitdist| 12| 100000| 100|0.07042699| PASSED 74 | rgb_minimum_distance| 2| 10000| 1000|0.60327409| PASSED 75 | rgb_minimum_distance| 3| 10000| 1000|0.98897681| PASSED 76 | rgb_minimum_distance| 4| 10000| 1000|0.55481032| PASSED 77 | rgb_minimum_distance| 5| 10000| 1000|0.79589279| PASSED 78 | rgb_permutations| 2| 100000| 100|0.80476545| PASSED 79 | rgb_permutations| 3| 100000| 100|0.79217002| PASSED 80 | rgb_permutations| 4| 100000| 100|0.77371602| PASSED 81 | rgb_permutations| 5| 100000| 100|0.09476665| PASSED 82 | rgb_lagged_sum| 0| 1000000| 100|0.94650491| PASSED 83 | rgb_lagged_sum| 1| 1000000| 100|0.57695166| PASSED 84 | rgb_lagged_sum| 2| 1000000| 100|0.25555434| PASSED 85 | rgb_lagged_sum| 3| 1000000| 100|0.97832091| PASSED 86 | rgb_lagged_sum| 4| 1000000| 100|0.47393625| PASSED 87 | rgb_lagged_sum| 5| 1000000| 100|0.76487787| PASSED 88 | rgb_lagged_sum| 6| 1000000| 100|0.99454227| PASSED 89 | rgb_lagged_sum| 7| 1000000| 100|0.74082866| PASSED 90 | rgb_lagged_sum| 8| 1000000| 100|0.64414026| PASSED 91 | rgb_lagged_sum| 9| 1000000| 100|0.49009874| PASSED 92 | rgb_lagged_sum| 10| 1000000| 100|0.78935470| PASSED 93 | rgb_lagged_sum| 11| 1000000| 100|0.30866655| PASSED 94 | rgb_lagged_sum| 12| 1000000| 100|0.62849739| PASSED 95 | rgb_lagged_sum| 13| 1000000| 100|0.13108507| PASSED 96 | rgb_lagged_sum| 14| 1000000| 100|0.05341736| PASSED 97 | rgb_lagged_sum| 15| 1000000| 100|0.45621575| PASSED 98 | rgb_lagged_sum| 16| 1000000| 100|0.17599590| PASSED 99 | rgb_lagged_sum| 17| 1000000| 100|0.15589047| PASSED 100 | rgb_lagged_sum| 18| 1000000| 100|0.91358999| PASSED 101 | rgb_lagged_sum| 19| 1000000| 100|0.21443286| PASSED 102 | rgb_lagged_sum| 20| 1000000| 100|0.70888569| PASSED 103 | rgb_lagged_sum| 21| 1000000| 100|0.74778647| PASSED 104 | rgb_lagged_sum| 22| 1000000| 100|0.91661584| PASSED 105 | rgb_lagged_sum| 23| 1000000| 100|0.98792934| PASSED 106 | rgb_lagged_sum| 24| 1000000| 100|0.47740634| PASSED 107 | rgb_lagged_sum| 25| 1000000| 100|0.99719278| WEAK 108 | rgb_lagged_sum| 26| 1000000| 100|0.36364052| PASSED 109 | rgb_lagged_sum| 27| 1000000| 100|0.16406347| PASSED 110 | rgb_lagged_sum| 28| 1000000| 100|0.82781333| PASSED 111 | rgb_lagged_sum| 29| 1000000| 100|0.95625871| PASSED 112 | rgb_lagged_sum| 30| 1000000| 100|0.79820885| PASSED 113 | rgb_lagged_sum| 31| 1000000| 100|0.63535805| PASSED 114 | rgb_lagged_sum| 32| 1000000| 100|0.03840900| PASSED 115 | rgb_kstest_test| 0| 10000| 1000|0.12470172| PASSED 116 | dab_bytedistrib| 0| 51200000| 1|0.38007996| PASSED 117 | dab_dct| 256| 50000| 1|0.71656474| PASSED 118 | Preparing to run test 207. ntuple = 0 119 | dab_filltree| 32| 15000000| 1|0.13040348| PASSED 120 | dab_filltree| 32| 15000000| 1|0.97319842| PASSED 121 | Preparing to run test 208. ntuple = 0 122 | dab_filltree2| 0| 5000000| 1|0.58021800| PASSED 123 | dab_filltree2| 1| 5000000| 1|0.65289362| PASSED 124 | Preparing to run test 209. ntuple = 0 125 | dab_monobit2| 12| 65000000| 1|0.85717775| PASSED 126 | -------------------------------------------------------------------------------- /src/main/java/com/grunka/encryption/rijndael/Rijndael.java: -------------------------------------------------------------------------------- 1 | package com.grunka.encryption.rijndael; 2 | /** 3 | * Rijndael.java 4 | * 5 | * @version 1.0 (May 2001) 6 | * Optimised Java implementation of the Rijndael (AES) block cipher. 7 | * @author Paulo Barreto paulo.barreto@terra.com.br 8 | * This software is hereby placed in the public domain. 9 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS 10 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 11 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 12 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE 13 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 14 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 15 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 16 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 17 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 18 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 19 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | public final class Rijndael { 22 | 23 | public Rijndael() { 24 | } 25 | 26 | /** 27 | * Flag to setup the encryption key schedule. 28 | */ 29 | public static final int DIR_ENCRYPT = 1; 30 | 31 | /** 32 | * Flag to setup the decryption key schedule. 33 | */ 34 | public static final int DIR_DECRYPT = 2; 35 | 36 | /** 37 | * Flag to setup both key schedules (encryption/decryption). 38 | */ 39 | public static final int DIR_BOTH = (DIR_ENCRYPT|DIR_DECRYPT); 40 | 41 | /** 42 | * AES block size in bits 43 | * (N.B. the Rijndael algorithm itself allows for other sizes). 44 | */ 45 | public static final int BLOCK_BITS = 128; 46 | 47 | /** 48 | * AES block size in bytes 49 | * (N.B. the Rijndael algorithm itself allows for other sizes). 50 | */ 51 | public static final int BLOCK_SIZE = (BLOCK_BITS >>> 3); 52 | 53 | /** 54 | * Substitution table (S-box). 55 | */ 56 | @SuppressWarnings("UnnecessaryUnicodeEscape") 57 | private static final String SS = 58 | "\u637C\u777B\uF26B\u6FC5\u3001\u672B\uFED7\uAB76" + 59 | "\uCA82\uC97D\uFA59\u47F0\uADD4\uA2AF\u9CA4\u72C0" + 60 | "\uB7FD\u9326\u363F\uF7CC\u34A5\uE5F1\u71D8\u3115" + 61 | "\u04C7\u23C3\u1896\u059A\u0712\u80E2\uEB27\uB275" + 62 | "\u0983\u2C1A\u1B6E\u5AA0\u523B\uD6B3\u29E3\u2F84" + 63 | "\u53D1\u00ED\u20FC\uB15B\u6ACB\uBE39\u4A4C\u58CF" + 64 | "\uD0EF\uAAFB\u434D\u3385\u45F9\u027F\u503C\u9FA8" + 65 | "\u51A3\u408F\u929D\u38F5\uBCB6\uDA21\u10FF\uF3D2" + 66 | "\uCD0C\u13EC\u5F97\u4417\uC4A7\u7E3D\u645D\u1973" + 67 | "\u6081\u4FDC\u222A\u9088\u46EE\uB814\uDE5E\u0BDB" + 68 | "\uE032\u3A0A\u4906\u245C\uC2D3\uAC62\u9195\uE479" + 69 | "\uE7C8\u376D\u8DD5\u4EA9\u6C56\uF4EA\u657A\uAE08" + 70 | "\uBA78\u252E\u1CA6\uB4C6\uE8DD\u741F\u4BBD\u8B8A" + 71 | "\u703E\uB566\u4803\uF60E\u6135\u57B9\u86C1\u1D9E" + 72 | "\uE1F8\u9811\u69D9\u8E94\u9B1E\u87E9\uCE55\u28DF" + 73 | "\u8CA1\u890D\uBFE6\u4268\u4199\u2D0F\uB054\uBB16"; 74 | 75 | private static final byte[] 76 | Se = new byte[256]; 77 | 78 | private static final int[] 79 | Te0 = new int[256], 80 | Te1 = new int[256], 81 | Te2 = new int[256], 82 | Te3 = new int[256]; 83 | 84 | private static final byte[] 85 | Sd = new byte[256]; 86 | 87 | private static final int[] 88 | Td0 = new int[256], 89 | Td1 = new int[256], 90 | Td2 = new int[256], 91 | Td3 = new int[256]; 92 | 93 | /** 94 | * Round constants 95 | */ 96 | private static final int[] 97 | rcon = new int[10]; /* for 128-bit blocks, Rijndael never uses more than 10 rcon values */ 98 | 99 | /** 100 | * Number of rounds (depends on key size). 101 | */ 102 | private int Nr = 0; 103 | 104 | private int Nk = 0; 105 | 106 | private int Nw = 0; 107 | 108 | /** 109 | * Encryption key schedule 110 | */ 111 | private int[] rek = null; 112 | 113 | /** 114 | * Decryption key schedule 115 | */ 116 | private int[] rdk = null; 117 | 118 | static { 119 | /* 120 | Te0[x] = Se[x].[02, 01, 01, 03]; 121 | Te1[x] = Se[x].[03, 02, 01, 01]; 122 | Te2[x] = Se[x].[01, 03, 02, 01]; 123 | Te3[x] = Se[x].[01, 01, 03, 02]; 124 | 125 | Td0[x] = Sd[x].[0e, 09, 0d, 0b]; 126 | Td1[x] = Sd[x].[0b, 0e, 09, 0d]; 127 | Td2[x] = Sd[x].[0d, 0b, 0e, 09]; 128 | Td3[x] = Sd[x].[09, 0d, 0b, 0e]; 129 | */ 130 | int ROOT = 0x11B; 131 | int s1, s2, s3, i1, i2, i4, i8, i9, ib, id, ie, t; 132 | for (i1 = 0; i1 < 256; i1++) { 133 | char c = SS.charAt(i1 >>> 1); 134 | s1 = (byte)((i1 & 1) == 0 ? c >>> 8 : c) & 0xff; 135 | s2 = s1 << 1; 136 | if (s2 >= 0x100) { 137 | s2 ^= ROOT; 138 | } 139 | s3 = s2 ^ s1; 140 | i2 = i1 << 1; 141 | if (i2 >= 0x100) { 142 | i2 ^= ROOT; 143 | } 144 | i4 = i2 << 1; 145 | if (i4 >= 0x100) { 146 | i4 ^= ROOT; 147 | } 148 | i8 = i4 << 1; 149 | if (i8 >= 0x100) { 150 | i8 ^= ROOT; 151 | } 152 | i9 = i8 ^ i1; 153 | ib = i9 ^ i2; 154 | id = i9 ^ i4; 155 | ie = i8 ^ i4 ^ i2; 156 | 157 | Se[i1] = (byte)s1; 158 | Te0[i1] = t = (s2 << 24) | (s1 << 16) | (s1 << 8) | s3; 159 | Te1[i1] = (t >>> 8) | (t << 24); 160 | Te2[i1] = (t >>> 16) | (t << 16); 161 | Te3[i1] = (t >>> 24) | (t << 8); 162 | 163 | Sd[s1] = (byte)i1; 164 | Td0[s1] = t = (ie << 24) | (i9 << 16) | (id << 8) | ib; 165 | Td1[s1] = (t >>> 8) | (t << 24); 166 | Td2[s1] = (t >>> 16) | (t << 16); 167 | Td3[s1] = (t >>> 24) | (t << 8); 168 | } 169 | /* 170 | * round constants 171 | */ 172 | int r = 1; 173 | rcon[0] = r << 24; 174 | for (int i = 1; i < 10; i++) { 175 | r <<= 1; 176 | if (r >= 0x100) { 177 | r ^= ROOT; 178 | } 179 | rcon[i] = r << 24; 180 | } 181 | } 182 | 183 | /** 184 | * Expand a cipher key into a full encryption key schedule. 185 | * 186 | * @param cipherKey the cipher key (128, 192, or 256 bits). 187 | */ 188 | private void expandKey(byte[] cipherKey) { 189 | int temp, r = 0; 190 | for (int i = 0, k = 0; i < Nk; i++, k += 4) { 191 | rek[i] = 192 | ((cipherKey[k ] ) << 24) | 193 | ((cipherKey[k + 1] & 0xff) << 16) | 194 | ((cipherKey[k + 2] & 0xff) << 8) | 195 | ((cipherKey[k + 3] & 0xff)); 196 | } 197 | for (int i = Nk, n = 0; i < Nw; i++, n--) { 198 | temp = rek[i - 1]; 199 | if (n == 0) { 200 | n = Nk; 201 | temp = 202 | ((Se[(temp >>> 16) & 0xff] ) << 24) | 203 | ((Se[(temp >>> 8) & 0xff] & 0xff) << 16) | 204 | ((Se[(temp ) & 0xff] & 0xff) << 8) | 205 | ((Se[(temp >>> 24) ] & 0xff)); 206 | temp ^= rcon[r++]; 207 | } else if (Nk == 8 && n == 4) { 208 | temp = 209 | ((Se[(temp >>> 24) ] ) << 24) | 210 | ((Se[(temp >>> 16) & 0xff] & 0xff) << 16) | 211 | ((Se[(temp >>> 8) & 0xff] & 0xff) << 8) | 212 | ((Se[(temp ) & 0xff] & 0xff)); 213 | } 214 | rek[i] = rek[i - Nk] ^ temp; 215 | } 216 | } 217 | 218 | /** 219 | * Compute the decryption schedule from the encryption schedule . 220 | */ 221 | private void invertKey() { 222 | int d = 0, e = 4*Nr, w; 223 | /* 224 | * apply the inverse MixColumn transform to all round keys 225 | * but the first and the last: 226 | */ 227 | rdk[d ] = rek[e ]; 228 | rdk[d + 1] = rek[e + 1]; 229 | rdk[d + 2] = rek[e + 2]; 230 | rdk[d + 3] = rek[e + 3]; 231 | d += 4; 232 | e -= 4; 233 | for (int r = 1; r < Nr; r++) { 234 | w = rek[e ]; 235 | rdk[d ] = 236 | Td0[Se[(w >>> 24) ] & 0xff] ^ 237 | Td1[Se[(w >>> 16) & 0xff] & 0xff] ^ 238 | Td2[Se[(w >>> 8) & 0xff] & 0xff] ^ 239 | Td3[Se[(w ) & 0xff] & 0xff]; 240 | w = rek[e + 1]; 241 | rdk[d + 1] = 242 | Td0[Se[(w >>> 24) ] & 0xff] ^ 243 | Td1[Se[(w >>> 16) & 0xff] & 0xff] ^ 244 | Td2[Se[(w >>> 8) & 0xff] & 0xff] ^ 245 | Td3[Se[(w ) & 0xff] & 0xff]; 246 | w = rek[e + 2]; 247 | rdk[d + 2] = 248 | Td0[Se[(w >>> 24) ] & 0xff] ^ 249 | Td1[Se[(w >>> 16) & 0xff] & 0xff] ^ 250 | Td2[Se[(w >>> 8) & 0xff] & 0xff] ^ 251 | Td3[Se[(w ) & 0xff] & 0xff]; 252 | w = rek[e + 3]; 253 | rdk[d + 3] = 254 | Td0[Se[(w >>> 24) ] & 0xff] ^ 255 | Td1[Se[(w >>> 16) & 0xff] & 0xff] ^ 256 | Td2[Se[(w >>> 8) & 0xff] & 0xff] ^ 257 | Td3[Se[(w ) & 0xff] & 0xff]; 258 | d += 4; 259 | e -= 4; 260 | } 261 | rdk[d ] = rek[e ]; 262 | rdk[d + 1] = rek[e + 1]; 263 | rdk[d + 2] = rek[e + 2]; 264 | rdk[d + 3] = rek[e + 3]; 265 | } 266 | 267 | /** 268 | * Setup the AES key schedule for encryption, decryption, or both. 269 | * 270 | * @param cipherKey the cipher key (128, 192, or 256 bits). 271 | * @param keyBits size of the cipher key in bits. 272 | * @param direction cipher direction (DIR_ENCRYPT, DIR_DECRYPT, or DIR_BOTH). 273 | */ 274 | public void makeKey(byte[] cipherKey, int keyBits, int direction) 275 | throws RuntimeException { 276 | // check key size: 277 | if (keyBits != 128 && keyBits != 192 && keyBits != 256) { 278 | throw new RuntimeException("Invalid AES key size (" + keyBits + " bits)"); 279 | } 280 | Nk = keyBits >>> 5; 281 | Nr = Nk + 6; 282 | Nw = 4*(Nr + 1); 283 | rek = new int[Nw]; 284 | rdk = new int[Nw]; 285 | if ((direction & DIR_BOTH) != 0) { 286 | expandKey(cipherKey); 287 | if ((direction & DIR_DECRYPT) != 0) { 288 | invertKey(); 289 | } 290 | } 291 | } 292 | 293 | /** 294 | * Encrypt exactly one block (BLOCK_SIZE bytes) of plaintext. 295 | * 296 | * @param pt plaintext block. 297 | * @param ct ciphertext block. 298 | */ 299 | public void encrypt(byte[] pt, byte[] ct) { 300 | if (pt.length != BLOCK_SIZE) { 301 | throw new IllegalArgumentException("Plain text is incorrect size"); 302 | } 303 | if (ct.length != BLOCK_SIZE) { 304 | throw new IllegalArgumentException("Cipher text is incorrect size"); 305 | } 306 | /* 307 | * map byte array block to cipher state 308 | * and add initial round key: 309 | */ 310 | int k = 0, v; 311 | int t0 = ((pt[ 0] ) << 24 | 312 | (pt[ 1] & 0xff) << 16 | 313 | (pt[ 2] & 0xff) << 8 | 314 | (pt[ 3] & 0xff) ) ^ rek[0]; 315 | int t1 = ((pt[ 4] ) << 24 | 316 | (pt[ 5] & 0xff) << 16 | 317 | (pt[ 6] & 0xff) << 8 | 318 | (pt[ 7] & 0xff) ) ^ rek[1]; 319 | int t2 = ((pt[ 8] ) << 24 | 320 | (pt[ 9] & 0xff) << 16 | 321 | (pt[10] & 0xff) << 8 | 322 | (pt[11] & 0xff) ) ^ rek[2]; 323 | int t3 = ((pt[12] ) << 24 | 324 | (pt[13] & 0xff) << 16 | 325 | (pt[14] & 0xff) << 8 | 326 | (pt[15] & 0xff) ) ^ rek[3]; 327 | /* 328 | * Nr - 1 full rounds: 329 | */ 330 | for (int r = 1; r < Nr; r++) { 331 | k += 4; 332 | int a0 = 333 | Te0[(t0 >>> 24) ] ^ 334 | Te1[(t1 >>> 16) & 0xff] ^ 335 | Te2[(t2 >>> 8) & 0xff] ^ 336 | Te3[(t3 ) & 0xff] ^ 337 | rek[k ]; 338 | int a1 = 339 | Te0[(t1 >>> 24) ] ^ 340 | Te1[(t2 >>> 16) & 0xff] ^ 341 | Te2[(t3 >>> 8) & 0xff] ^ 342 | Te3[(t0 ) & 0xff] ^ 343 | rek[k + 1]; 344 | int a2 = 345 | Te0[(t2 >>> 24) ] ^ 346 | Te1[(t3 >>> 16) & 0xff] ^ 347 | Te2[(t0 >>> 8) & 0xff] ^ 348 | Te3[(t1 ) & 0xff] ^ 349 | rek[k + 2]; 350 | int a3 = 351 | Te0[(t3 >>> 24) ] ^ 352 | Te1[(t0 >>> 16) & 0xff] ^ 353 | Te2[(t1 >>> 8) & 0xff] ^ 354 | Te3[(t2 ) & 0xff] ^ 355 | rek[k + 3]; 356 | t0 = a0; t1 = a1; t2 = a2; t3 = a3; 357 | } 358 | /* 359 | * last round lacks MixColumn: 360 | */ 361 | k += 4; 362 | 363 | v = rek[k ]; 364 | ct[ 0] = (byte)(Se[(t0 >>> 24) ] ^ (v >>> 24)); 365 | ct[ 1] = (byte)(Se[(t1 >>> 16) & 0xff] ^ (v >>> 16)); 366 | ct[ 2] = (byte)(Se[(t2 >>> 8) & 0xff] ^ (v >>> 8)); 367 | ct[ 3] = (byte)(Se[(t3 ) & 0xff] ^ (v )); 368 | 369 | v = rek[k + 1]; 370 | ct[ 4] = (byte)(Se[(t1 >>> 24) ] ^ (v >>> 24)); 371 | ct[ 5] = (byte)(Se[(t2 >>> 16) & 0xff] ^ (v >>> 16)); 372 | ct[ 6] = (byte)(Se[(t3 >>> 8) & 0xff] ^ (v >>> 8)); 373 | ct[ 7] = (byte)(Se[(t0 ) & 0xff] ^ (v )); 374 | 375 | v = rek[k + 2]; 376 | ct[ 8] = (byte)(Se[(t2 >>> 24) ] ^ (v >>> 24)); 377 | ct[ 9] = (byte)(Se[(t3 >>> 16) & 0xff] ^ (v >>> 16)); 378 | ct[10] = (byte)(Se[(t0 >>> 8) & 0xff] ^ (v >>> 8)); 379 | ct[11] = (byte)(Se[(t1 ) & 0xff] ^ (v )); 380 | 381 | v = rek[k + 3]; 382 | ct[12] = (byte)(Se[(t3 >>> 24) ] ^ (v >>> 24)); 383 | ct[13] = (byte)(Se[(t0 >>> 16) & 0xff] ^ (v >>> 16)); 384 | ct[14] = (byte)(Se[(t1 >>> 8) & 0xff] ^ (v >>> 8)); 385 | ct[15] = (byte)(Se[(t2 ) & 0xff] ^ (v )); 386 | } 387 | 388 | 389 | /** 390 | * Decrypt exactly one block (BLOCK_SIZE bytes) of ciphertext. 391 | * 392 | * @param ct ciphertext block. 393 | * @param pt plaintext block. 394 | */ 395 | public void decrypt(byte[] ct, byte[] pt) { 396 | if (pt.length != BLOCK_SIZE) { 397 | throw new IllegalArgumentException("Plain text is incorrect size"); 398 | } 399 | if (ct.length != BLOCK_SIZE) { 400 | throw new IllegalArgumentException("Cipher text is incorrect size"); 401 | } 402 | /* 403 | * map byte array block to cipher state 404 | * and add initial round key: 405 | */ 406 | int k = 0, v; 407 | int t0 = ((ct[ 0] ) << 24 | 408 | (ct[ 1] & 0xff) << 16 | 409 | (ct[ 2] & 0xff) << 8 | 410 | (ct[ 3] & 0xff) ) ^ rdk[0]; 411 | int t1 = ((ct[ 4] ) << 24 | 412 | (ct[ 5] & 0xff) << 16 | 413 | (ct[ 6] & 0xff) << 8 | 414 | (ct[ 7] & 0xff) ) ^ rdk[1]; 415 | int t2 = ((ct[ 8] ) << 24 | 416 | (ct[ 9] & 0xff) << 16 | 417 | (ct[10] & 0xff) << 8 | 418 | (ct[11] & 0xff) ) ^ rdk[2]; 419 | int t3 = ((ct[12] ) << 24 | 420 | (ct[13] & 0xff) << 16 | 421 | (ct[14] & 0xff) << 8 | 422 | (ct[15] & 0xff) ) ^ rdk[3]; 423 | /* 424 | * Nr - 1 full rounds: 425 | */ 426 | for (int r = 1; r < Nr; r++) { 427 | k += 4; 428 | int a0 = 429 | Td0[(t0 >>> 24) ] ^ 430 | Td1[(t3 >>> 16) & 0xff] ^ 431 | Td2[(t2 >>> 8) & 0xff] ^ 432 | Td3[(t1 ) & 0xff] ^ 433 | rdk[k ]; 434 | int a1 = 435 | Td0[(t1 >>> 24) ] ^ 436 | Td1[(t0 >>> 16) & 0xff] ^ 437 | Td2[(t3 >>> 8) & 0xff] ^ 438 | Td3[(t2 ) & 0xff] ^ 439 | rdk[k + 1]; 440 | int a2 = 441 | Td0[(t2 >>> 24) ] ^ 442 | Td1[(t1 >>> 16) & 0xff] ^ 443 | Td2[(t0 >>> 8) & 0xff] ^ 444 | Td3[(t3 ) & 0xff] ^ 445 | rdk[k + 2]; 446 | int a3 = 447 | Td0[(t3 >>> 24) ] ^ 448 | Td1[(t2 >>> 16) & 0xff] ^ 449 | Td2[(t1 >>> 8) & 0xff] ^ 450 | Td3[(t0 ) & 0xff] ^ 451 | rdk[k + 3]; 452 | t0 = a0; t1 = a1; t2 = a2; t3 = a3; 453 | } 454 | /* 455 | * last round lacks MixColumn: 456 | */ 457 | k += 4; 458 | 459 | v = rdk[k ]; 460 | pt[ 0] = (byte)(Sd[(t0 >>> 24) ] ^ (v >>> 24)); 461 | pt[ 1] = (byte)(Sd[(t3 >>> 16) & 0xff] ^ (v >>> 16)); 462 | pt[ 2] = (byte)(Sd[(t2 >>> 8) & 0xff] ^ (v >>> 8)); 463 | pt[ 3] = (byte)(Sd[(t1 ) & 0xff] ^ (v )); 464 | 465 | v = rdk[k + 1]; 466 | pt[ 4] = (byte)(Sd[(t1 >>> 24) ] ^ (v >>> 24)); 467 | pt[ 5] = (byte)(Sd[(t0 >>> 16) & 0xff] ^ (v >>> 16)); 468 | pt[ 6] = (byte)(Sd[(t3 >>> 8) & 0xff] ^ (v >>> 8)); 469 | pt[ 7] = (byte)(Sd[(t2 ) & 0xff] ^ (v )); 470 | 471 | v = rdk[k + 2]; 472 | pt[ 8] = (byte)(Sd[(t2 >>> 24) ] ^ (v >>> 24)); 473 | pt[ 9] = (byte)(Sd[(t1 >>> 16) & 0xff] ^ (v >>> 16)); 474 | pt[10] = (byte)(Sd[(t0 >>> 8) & 0xff] ^ (v >>> 8)); 475 | pt[11] = (byte)(Sd[(t3 ) & 0xff] ^ (v )); 476 | 477 | v = rdk[k + 3]; 478 | pt[12] = (byte)(Sd[(t3 >>> 24) ] ^ (v >>> 24)); 479 | pt[13] = (byte)(Sd[(t2 >>> 16) & 0xff] ^ (v >>> 16)); 480 | pt[14] = (byte)(Sd[(t1 >>> 8) & 0xff] ^ (v >>> 8)); 481 | pt[15] = (byte)(Sd[(t0 ) & 0xff] ^ (v )); 482 | } 483 | } 484 | --------------------------------------------------------------------------------