001: /*
002: * Copyright 2004-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.springframework.webflow.util;
017:
018: /*
019: * RandomGUID from http://www.javaexchange.com/aboutRandomGUID.html
020: * @version 1.2.1 11/05/02
021: * @author Marc A. Mnich
022: *
023: * From www.JavaExchange.com, Open Software licensing
024: *
025: * 11/05/02 -- Performance enhancement from Mike Dubman.
026: * Moved InetAddr.getLocal to static block. Mike has measured
027: * a 10 fold improvement in run time.
028: * 01/29/02 -- Bug fix: Improper seeding of nonsecure Random object
029: * caused duplicate GUIDs to be produced. Random object
030: * is now only created once per JVM.
031: * 01/19/02 -- Modified random seeding and added new constructor
032: * to allow secure random feature.
033: * 01/14/02 -- Added random function seeding with JVM run time
034: */
035:
036: import java.net.InetAddress;
037: import java.net.UnknownHostException;
038: import java.security.MessageDigest;
039: import java.security.NoSuchAlgorithmException;
040: import java.security.SecureRandom;
041: import java.util.Random;
042:
043: /**
044: * Globally unique identifier generator.
045: * <p>
046: * In the multitude of java GUID generators, I found none that guaranteed
047: * randomness. GUIDs are guaranteed to be globally unique by using ethernet
048: * MACs, IP addresses, time elements, and sequential numbers. GUIDs are not
049: * expected to be random and most often are easy/possible to guess given a
050: * sample from a given generator. SQL Server, for example generates GUID that
051: * are unique but sequencial within a given instance.
052: * <p>
053: * GUIDs can be used as security devices to hide things such as files within a
054: * filesystem where listings are unavailable (e.g. files that are served up from
055: * a Web server with indexing turned off). This may be desireable in cases where
056: * standard authentication is not appropriate. In this scenario, the RandomGuids
057: * are used as directories. Another example is the use of GUIDs for primary keys
058: * in a database where you want to ensure that the keys are secret. Random GUIDs
059: * can then be used in a URL to prevent hackers (or users) from accessing
060: * records by guessing or simply by incrementing sequential numbers.
061: * <p>
062: * There are many other possiblities of using GUIDs in the realm of security and
063: * encryption where the element of randomness is important. This class was
064: * written for these purposes but can also be used as a general purpose GUID
065: * generator as well.
066: * <p>
067: * RandomGuid generates truly random GUIDs by using the system's IP address
068: * (name/IP), system time in milliseconds (as an integer), and a very large
069: * random number joined together in a single String that is passed through an
070: * MD5 hash. The IP address and system time make the MD5 seed globally unique
071: * and the random number guarantees that the generated GUIDs will have no
072: * discernable pattern and cannot be guessed given any number of previously
073: * generated GUIDs. It is generally not possible to access the seed information
074: * (IP, time, random number) from the resulting GUIDs as the MD5 hash algorithm
075: * provides one way encryption.
076: * <p>
077: * <b>Security of RandomGuid</b>: RandomGuid can be called one of two ways --
078: * with the basic java Random number generator or a cryptographically strong
079: * random generator (SecureRandom). The choice is offered because the secure
080: * random generator takes about 3.5 times longer to generate its random numbers
081: * and this performance hit may not be worth the added security especially
082: * considering the basic generator is seeded with a cryptographically strong
083: * random seed.
084: * <p>
085: * Seeding the basic generator in this way effectively decouples the random
086: * numbers from the time component making it virtually impossible to predict the
087: * random number component even if one had absolute knowledge of the System
088: * time. Thanks to Ashutosh Narhari for the suggestion of using the static
089: * method to prime the basic random generator.
090: * <p>
091: * Using the secure random option, this class complies with the statistical
092: * random number generator tests specified in FIPS 140-2, Security Requirements
093: * for Cryptographic Modules, secition 4.9.1.
094: * <p>
095: * I converted all the pieces of the seed to a String before handing it over to
096: * the MD5 hash so that you could print it out to make sure it contains the data
097: * you expect to see and to give a nice warm fuzzy. If you need better
098: * performance, you may want to stick to byte[] arrays.
099: * <p>
100: * I believe that it is important that the algorithm for generating random GUIDs
101: * be open for inspection and modification. This class is free for all uses.
102: *
103: * @version 1.2.1 11/05/02
104: * @author Marc A. Mnich
105: */
106: public class RandomGuid {
107:
108: private static Random random;
109:
110: private static SecureRandom secureRandom;
111:
112: private static String id;
113:
114: private String guid;
115:
116: /*
117: * Static block to take care of one time secureRandom seed. It takes a few
118: * seconds to initialize SecureRandom. You might want to consider removing
119: * this static block or replacing it with a "time since first loaded" seed
120: * to reduce this time. This block will run only once per JVM instance.
121: */
122: static {
123: secureRandom = new SecureRandom();
124: long secureInitializer = secureRandom.nextLong();
125: random = new Random(secureInitializer);
126: try {
127: id = InetAddress.getLocalHost().toString();
128: } catch (UnknownHostException e) {
129: throw new RuntimeException(e);
130: }
131: }
132:
133: /**
134: * Default constructor. With no specification of security option, this
135: * constructor defaults to lower security, high performance.
136: */
137: public RandomGuid() {
138: getRandomGuid(false);
139: }
140:
141: /**
142: * Constructor with security option. Setting secure true enables each random
143: * number generated to be cryptographically strong. Secure false defaults to
144: * the standard Random function seeded with a single cryptographically
145: * strong random number.
146: */
147: public RandomGuid(boolean secure) {
148: getRandomGuid(secure);
149: }
150:
151: /**
152: * Method to generate the random GUID.
153: */
154: private void getRandomGuid(boolean secure) {
155: MessageDigest md5 = null;
156: StringBuffer sbValueBeforeMD5 = new StringBuffer();
157:
158: try {
159: md5 = MessageDigest.getInstance("MD5");
160: } catch (NoSuchAlgorithmException e) {
161: throw new RuntimeException(e);
162: }
163:
164: long time = System.currentTimeMillis();
165: long rand = 0;
166:
167: if (secure) {
168: rand = secureRandom.nextLong();
169: } else {
170: rand = random.nextLong();
171: }
172:
173: // This StringBuffer can be a long as you need; the MD5
174: // hash will always return 128 bits. You can change
175: // the seed to include anything you want here.
176: // You could even stream a file through the MD5 making
177: // the odds of guessing it at least as great as that
178: // of guessing the contents of the file!
179: sbValueBeforeMD5.append(id);
180: sbValueBeforeMD5.append(":");
181: sbValueBeforeMD5.append(Long.toString(time));
182: sbValueBeforeMD5.append(":");
183: sbValueBeforeMD5.append(Long.toString(rand));
184:
185: String valueBeforeMD5 = sbValueBeforeMD5.toString();
186: md5.update(valueBeforeMD5.getBytes());
187:
188: byte[] array = md5.digest();
189: StringBuffer sb = new StringBuffer();
190: for (int j = 0; j < array.length; ++j) {
191: int b = array[j] & 0xFF;
192: if (b < 0x10)
193: sb.append('0');
194: sb.append(Integer.toHexString(b));
195: }
196: guid = sb.toString();
197: }
198:
199: /**
200: * Convert to the standard format for GUID (Useful for SQL Server
201: * UniqueIdentifiers, etc).
202: * Example: "C2FEEEAC-CFCD-11D1-8B05-00600806D9B6".
203: */
204: public String toString() {
205: String raw = guid.toUpperCase();
206: StringBuffer sb = new StringBuffer();
207: sb.append(raw.substring(0, 8));
208: sb.append("-");
209: sb.append(raw.substring(8, 12));
210: sb.append("-");
211: sb.append(raw.substring(12, 16));
212: sb.append("-");
213: sb.append(raw.substring(16, 20));
214: sb.append("-");
215: sb.append(raw.substring(20));
216: return sb.toString();
217: }
218: }
|