001    /*
002     * Cumulus4j - Securing your data in the cloud - http://cumulus4j.org
003     * Copyright (C) 2011 NightLabs Consulting GmbH
004     *
005     * This program is free software: you can redistribute it and/or modify
006     * it under the terms of the GNU Affero General Public License as
007     * published by the Free Software Foundation, either version 3 of the
008     * License, or (at your option) any later version.
009     *
010     * This program is distributed in the hope that it will be useful,
011     * but WITHOUT ANY WARRANTY; without even the implied warranty of
012     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013     * GNU Affero General Public License for more details.
014     *
015     * You should have received a copy of the GNU Affero General Public License
016     * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017     */
018    package org.cumulus4j.keymanager.back.shared;
019    
020    import java.math.BigInteger;
021    import java.security.SecureRandom;
022    import java.util.UUID;
023    
024    /**
025     * Utility class for identifiers used within Cumulus4j.
026     *
027     * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
028     */
029    public class IdentifierUtil
030    {
031            private static SecureRandom random = new SecureRandom();
032    
033            private static double log(double base, double value)
034            {
035                    return Math.log10(value) / Math.log10(base);
036            }
037    
038            /**
039             * <p>
040             * Create a random <code>String</code> identifier with a sufficiently unique length.
041             * </p>
042             * <p>
043             * This method calls {@link #createRandomID(int)} with a <code>length</code> of 25.
044             * </p>
045             * <p>
046             * The <code>length</code> of 25 is chosen, because it produces an identifier
047             * which has about the same uniqueness as {@link UUID#randomUUID()}. This is because
048             * the String has 36 ^ 25 (approximately equals 2 ^ 129) possible values while a UUID
049             * has 2 ^ 128 possible values and both identifiers are created using the same
050             * method ({@link SecureRandom#nextBytes(byte[])}).
051             * </p>
052             * @return a random <code>String</code>.
053             * @see #createRandomID(int)
054             */
055            public static String createRandomID()
056            {
057                    return createRandomID(25);
058            }
059    
060            /**
061             * <p>
062             * Create a random <code>String</code> identifier with a specified length.
063             * </p>
064             * <p>
065             * The generated identifier will contain
066             * only the characters '0'...'9' and 'a'...'z' and will have the specified <code>length</code>.
067             * This method uses a {@link SecureRandom} (just like {@link UUID#randomUUID()}). With a length
068             * of 25, the identifier will have about the same uniqueness as a <code>UUID</code> - see
069             * {@link #createRandomID()}.
070             * </p>
071             *
072             * @param length the number of <code>char</code>s in the result.
073             * @return a random <code>String</code> with the given <code>length</code>.
074             * @see #createRandomID()
075             */
076            public static String createRandomID(int length)
077            {
078                    int byteArrayLength = (int)log(256, Math.pow(36, length)) + 1;
079    
080                    byte[] val = new byte[byteArrayLength];
081                    random.nextBytes(val);
082                    val[0] = (byte)(val[0] & 0x7F); // ensure a positive value
083                    BigInteger bi = new BigInteger(val);
084                    String result = bi.toString(36).substring(1); // cut the first character, because its range is limited (never reaches 'z')
085    
086                    if (result.length() < length) { // prepend with '0' to reach a fixed length.
087                            StringBuilder sb = new StringBuilder(length);
088                            for (int i = result.length(); i < length; ++i)
089                                    sb.append('0');
090    
091                            sb.append(result);
092                            result = sb.toString();
093                    }
094    
095                    if (result.length() > length + 1)
096                            throw new IllegalStateException("Why is result.length == " + result.length() + " > " + length + "+1 chars?!");
097    
098                    if (result.length() > length)
099                            result = result.substring(result.length() - length);
100    
101                    if (result.length() != length)
102                            throw new IllegalStateException("Why is result.length != " + length + " chars?!");
103    
104                    return result;
105            }
106    
107    //      public static void main(String[] args) {
108    //              // Check to see whether a length of 25 is approximately as unique as a UUID.
109    //              double possibleValues = Math.pow(36, 25);
110    //              double possibleValuesExponentTo2 = log(2, possibleValues);
111    //              System.out.println(possibleValues);
112    //              System.out.println(possibleValuesExponentTo2);
113    //              System.out.println(Math.pow(2, possibleValuesExponentTo2));
114    //      }
115    
116    //      public static void main(String[] args) {
117    //              long start = System.currentTimeMillis();
118    //              double a = Math.random();
119    //              double b = Math.random();
120    //              double p = Math.pow(a, b);
121    //              double l = log(a, p);
122    //              System.out.println("a = " + a);
123    //              System.out.println("b = " + b);
124    //              System.out.println("p = " + p);
125    //              System.out.println("l = " + l);
126    //              System.out.println("|b-l| = " + Math.abs(b-l));
127    //
128    //              for (int i = 0; i < 10000; ++i) {
129    //                      String id = createRandomID(1 + (i % 50));
130    //                      System.out.println(id);
131    //              }
132    //              System.out.println("Duration: " + (System.currentTimeMillis() - start) + " msec");
133    //      }
134    }