killbill-aplcache

introduce a "light" secure-random alternative (based on SHA1

5/28/2015 7:32:08 AM

Details

diff --git a/api/src/main/java/org/killbill/billing/util/UUIDs.java b/api/src/main/java/org/killbill/billing/util/UUIDs.java
index d85d9b6..eeb12df 100644
--- a/api/src/main/java/org/killbill/billing/util/UUIDs.java
+++ b/api/src/main/java/org/killbill/billing/util/UUIDs.java
@@ -15,7 +15,10 @@
  */
 package org.killbill.billing.util;
 
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
+import java.util.Random;
 import java.util.UUID;
 
 /**
@@ -29,7 +32,7 @@ public abstract class UUIDs {
 
     private static UUID rndUUIDv4() {
         // ~ return UUID.randomUUID() :
-        final SecureRandom random = threadRandom.get();
+        final Random random = threadRandom.get();
 
         final byte[] uuid = new byte[16];
         random.nextBytes(uuid);
@@ -61,11 +64,199 @@ public abstract class UUIDs {
         return new UUID(msb, lsb);
     }
 
-    private static final ThreadLocal<SecureRandom> threadRandom =
-        new ThreadLocal<SecureRandom>() {
-            protected SecureRandom initialValue() {
-                return new SecureRandom();
+    private static final ThreadLocal<Random> threadRandom =
+        new ThreadLocal<Random>() {
+            protected Random initialValue() {
+                return new LightSecureRandom(); // new SecureRandom();
             }
     };
 
+    /**
+     * An implementation of SecureRandom inspired by bouncy-castle's own for
+     * light-weight APIs (JDK 1.0, and J2ME).
+     *
+     * Random generation is based on the traditional SHA1 with
+     * counter. Calling setSeed will always increase the entropy of the hash.
+     */
+    // NOTE: assumes non-concurrent use (generator calls should be synchronized)
+    private static class LightSecureRandom extends Random {
+
+        private static abstract class SeederHolder {
+            static final SecureRandom seeder = new SecureRandom();
+        }
+
+        private final DigestRandomGenerator generator;
+
+        LightSecureRandom() {
+            this( sha1Generator() );
+            setSeed( SeederHolder.seeder.generateSeed( generator.getDigestLength() ) );
+        }
+
+        LightSecureRandom(final byte[] inSeed) {
+            this( sha1Generator() );
+            setSeed(inSeed);
+        }
+
+        private LightSecureRandom(DigestRandomGenerator generator) {
+            super(0);
+            this.generator = generator;
+        }
+
+        private static DigestRandomGenerator sha1Generator() {
+            try {
+                return new DigestRandomGenerator(MessageDigest.getInstance("SHA-1"));
+            }
+            catch (NoSuchAlgorithmException ex) {
+                throw new AssertionError("unexpeced missing SHA-1 digest", ex);
+            }
+        }
+
+        @Override
+        public void setSeed(final long seed) {
+            if ( seed != 0 ) { // to avoid problems with Random calling setSeed in construction
+                generator.addSeedMaterial(seed);
+            }
+        }
+
+        public void setSeed(final byte[] seed) {
+            generator.addSeedMaterial(seed);
+        }
+
+        @Override
+        public void nextBytes(byte[] bytes) {
+            generator.nextBytes(bytes);
+        }
+
+        @Override
+        public int nextInt() {
+            final byte[] intBytes = new byte[4];
+
+            nextBytes(intBytes);
+
+            int result = 0;
+
+            for ( int i = 0; i < 4; i++ ) {
+                result = (result << 8) + (intBytes[i] & 0xff);
+            }
+
+            return result;
+        }
+
+        @Override
+        protected final int next(int numBits) {
+            int size = (numBits + 7) / 8;
+            byte[] bytes = new byte[size];
+
+            nextBytes(bytes);
+
+            int result = 0;
+
+            for (int i = 0; i < size; i++)
+            {
+                result = (result << 8) + (bytes[i] & 0xff);
+            }
+
+            return result & ((1 << numBits) - 1);
+        }
+
+    }
+
+    private static class DigestRandomGenerator {
+
+        private static final long CYCLE_COUNT = 10;
+
+        private long stateCounter;
+        private long seedCounter;
+        private final MessageDigest digest;
+        private byte[] state;
+        private byte[] seed;
+
+        private DigestRandomGenerator(MessageDigest digest) {
+            this.digest = digest;
+
+            this.seed = new byte[digest.getDigestLength()];
+            this.seedCounter = 1;
+
+            this.state = new byte[digest.getDigestLength()];
+            this.stateCounter = 1;
+        }
+
+        int getDigestLength() { return digest.getDigestLength(); }
+
+        // NOTE: requires external synchronization
+        final void addSeedMaterial(byte[] inSeed) {
+            //synchronized (this) {
+            digestUpdate(inSeed);
+            digestUpdate(seed);
+            seed = digest.digest(); // digestDoFinal(seed);
+            //}
+        }
+
+        // NOTE: requires external synchronization
+        final void addSeedMaterial(long rSeed) {
+            //synchronized (this) {
+            digestAddCounter(rSeed);
+            digestUpdate(seed);
+            seed = digest.digest(); // digestDoFinal(seed);
+            //}
+        }
+
+        void nextBytes(byte[] bytes) {
+            nextBytes(bytes, 0, bytes.length);
+        }
+
+        // NOTE: requires external synchronization
+        final void nextBytes(byte[] bytes, int start, int len) {
+            //synchronized (this) {
+            int stateOff = 0;
+
+            generateState();
+
+            int end = start + len;
+            for (int i = start; i != end; i++) {
+                if (stateOff == state.length) {
+                    generateState();
+                    stateOff = 0;
+                }
+                bytes[i] = state[stateOff++];
+            }
+            //}
+        }
+
+        private void cycleSeed() {
+            digestUpdate(seed);
+            digestAddCounter(seedCounter++);
+
+            seed = digest.digest(); // digestDoFinal(seed);
+        }
+
+        private void generateState() {
+            digestAddCounter(stateCounter++);
+            digestUpdate(state);
+            digestUpdate(seed);
+
+            state = digest.digest(); // digestDoFinal(state);
+
+            if ((stateCounter % CYCLE_COUNT) == 0) {
+                cycleSeed();
+            }
+        }
+
+        private void digestAddCounter(long seed) {
+            for (int i = 0; i != 8; i++) {
+                digest.update((byte) seed);
+                seed >>>= 8;
+            }
+        }
+
+        private void digestUpdate(byte[] inSeed) {
+            digest.update(inSeed, 0, inSeed.length);
+        }
+
+        //private void digestDoFinal(byte[] result) {
+        //    digest.doFinal(result, 0);
+        //}
+
+    }
+
 }