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 d85d9b6edb..eeb12df410 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 @@ private static UUID rndUUIDv4() { return new UUID(msb, lsb); } - private static final ThreadLocal threadRandom = - new ThreadLocal() { - protected SecureRandom initialValue() { - return new SecureRandom(); + private static final ThreadLocal threadRandom = + new ThreadLocal() { + 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); + //} + + } + }