KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jboss > security > Util


1 /*
2  * JBoss, the OpenSource EJB server
3  *
4  * Distributable under LGPL license.
5  * See terms of license at gnu.org.
6  */

7 package org.jboss.security;
8
9 import java.io.Serializable JavaDoc;
10 import java.io.UnsupportedEncodingException JavaDoc;
11 import java.lang.reflect.Constructor JavaDoc;
12 import java.lang.reflect.Method JavaDoc;
13 import java.math.BigInteger JavaDoc;
14 import java.security.GeneralSecurityException JavaDoc;
15 import java.security.KeyException JavaDoc;
16 import java.security.MessageDigest JavaDoc;
17 import java.security.NoSuchAlgorithmException JavaDoc;
18 import java.security.Provider JavaDoc;
19 import java.security.Security JavaDoc;
20 import java.security.SecureRandom JavaDoc;
21 import java.util.Random JavaDoc;
22
23 import org.jboss.crypto.JBossSXProvider;
24 import org.jboss.crypto.digest.DigestCallback;
25 import org.jboss.logging.Logger;
26
27 /** Various security related utilities like MessageDigest
28  factories, SecureRandom access, password hashing.
29
30  This product includes software developed by Tom Wu and Eugene
31  Jhong for the SRP Distribution (http://srp.stanford.edu/srp/).
32
33  @author Scott.Stark@jboss.org
34  @version $Revision: 1.8.4.2 $
35  */

36 public class Util
37 {
38    private static Logger log = Logger.getLogger(Util.class);
39    private static final int HASH_LEN = 20;
40    public static final String JavaDoc BASE64_ENCODING = "BASE64";
41    public static final String JavaDoc BASE16_ENCODING = "HEX";
42
43    private static SecureRandom JavaDoc psuedoRng;
44    private static MessageDigest JavaDoc sha1Digest;
45    private static boolean initialized;
46
47    public static void init() throws NoSuchAlgorithmException JavaDoc
48    {
49       if( initialized )
50          return;
51       init(null);
52    }
53    public static void init(byte[] prngSeed) throws NoSuchAlgorithmException JavaDoc
54    {
55       // Get an instance of the SHA-1 digest
56
sha1Digest = MessageDigest.getInstance("SHA");
57       // Get a cryptographically strong pseudo-random generator
58
psuedoRng = SecureRandom.getInstance("SHA1PRNG");
59       if( prngSeed != null )
60          psuedoRng.setSeed(prngSeed);
61       // Install the JBossSX security provider
62
Provider JavaDoc provider = new JBossSXProvider();
63       Security.addProvider(provider);
64       initialized = true;
65    }
66
67    public static MessageDigest JavaDoc newDigest()
68    {
69       MessageDigest JavaDoc md = null;
70       try
71       {
72          md = (MessageDigest JavaDoc) sha1Digest.clone();
73       }
74       catch(CloneNotSupportedException JavaDoc e)
75       {
76       }
77       return md;
78    }
79    public static MessageDigest JavaDoc copy(MessageDigest JavaDoc md)
80    {
81       MessageDigest JavaDoc copy = null;
82       try
83       {
84          copy = (MessageDigest JavaDoc) md.clone();
85       }
86       catch(CloneNotSupportedException JavaDoc e)
87       {
88       }
89       return copy;
90    }
91
92    public static Random JavaDoc getPRNG()
93    {
94       return psuedoRng;
95    }
96    /** Returns the next pseudorandom, uniformly distributed double value
97     between 0.0 and 1.0 from this random number generator's sequence.
98     */

99    public static double nextDouble()
100    {
101       return psuedoRng.nextDouble();
102    }
103    /** Returns the next pseudorandom, uniformly distributed long value from
104     this random number generator's sequence. The general contract of
105     nextLong is that one long value is pseudorandomly generated and
106     returned. All 264 possible long values are produced with
107     (approximately) equal probability.
108     */

109    public static long nextLong()
110    {
111       return psuedoRng.nextLong();
112    }
113    /** Generates random bytes and places them into a user-supplied byte
114     array. The number of random bytes produced is equal to the length
115     of the byte array.
116     */

117    public static void nextBytes(byte[] bytes)
118    {
119       psuedoRng.nextBytes(bytes);
120    }
121    /** Returns the given number of seed bytes, computed using the seed
122     generation algorithm that this class uses to seed itself. This call
123     may be used to seed other random number generators.
124     */

125    public static byte[] generateSeed(int numBytes)
126    {
127       return psuedoRng.generateSeed(numBytes);
128    }
129
130    /** Cacluate the SRP RFC2945 password hash = H(salt | H(username | ':' | password))
131     where H = SHA secure hash. The username is converted to a byte[] using the
132     UTF-8 encoding.
133     */

134    public static byte[] calculatePasswordHash(String JavaDoc username, char[] password,
135       byte[] salt)
136    {
137       // Calculate x = H(s | H(U | ':' | password))
138
MessageDigest JavaDoc xd = newDigest();
139       // Try to convert the username to a byte[] using UTF-8
140
byte[] user = null;
141       byte[] colon = {};
142       try
143       {
144          user = username.getBytes("UTF-8");
145          colon = ":".getBytes("UTF-8");
146       }
147       catch(UnsupportedEncodingException JavaDoc e)
148       {
149          log.error("Failed to convert username to byte[] using UTF-8", e);
150          // Use the default platform encoding
151
user = username.getBytes();
152          colon = ":".getBytes();
153       }
154       byte[] passBytes = new byte[2*password.length];
155       int passBytesLength = 0;
156       for(int p = 0; p < password.length; p ++)
157       {
158          int c = (password[p] & 0x00FFFF);
159          // The low byte of the char
160
byte b0 = (byte) (c & 0x0000FF);
161          // The high byte of the char
162
byte b1 = (byte) ((c & 0x00FF00) >> 8);
163          passBytes[passBytesLength ++] = b0;
164          // Only encode the high byte if c is a multi-byte char
165
if( c > 255 )
166             passBytes[passBytesLength ++] = b1;
167       }
168
169       // Build the hash
170
xd.update(user);
171       xd.update(colon);
172       xd.update(passBytes, 0, passBytesLength);
173       byte[] h = xd.digest();
174       xd.reset();
175       xd.update(salt);
176       xd.update(h);
177       byte[] xb = xd.digest();
178       return xb;
179    }
180
181    /** Calculate x = H(s | H(U | ':' | password)) verifier
182     v = g^x % N
183     described in RFC2945.
184     */

185    public static byte[] calculateVerifier(String JavaDoc username, char[] password,
186       byte[] salt, byte[] Nb, byte[] gb)
187    {
188       BigInteger JavaDoc g = new BigInteger JavaDoc(1, gb);
189       BigInteger JavaDoc N = new BigInteger JavaDoc(1, Nb);
190       return calculateVerifier(username, password, salt, N, g);
191    }
192    /** Calculate x = H(s | H(U | ':' | password)) verifier
193     v = g^x % N
194     described in RFC2945.
195     */

196    public static byte[] calculateVerifier(String JavaDoc username, char[] password,
197       byte[] salt, BigInteger JavaDoc N, BigInteger JavaDoc g)
198    {
199       byte[] xb = calculatePasswordHash(username, password, salt);
200       BigInteger JavaDoc x = new BigInteger JavaDoc(1, xb);
201       BigInteger JavaDoc v = g.modPow(x, N);
202       return v.toByteArray();
203    }
204
205    /** Perform an interleaved even-odd hash on the byte string
206     */

207    public static byte[] sessionKeyHash(byte[] number)
208    {
209       int i, offset;
210
211       for(offset = 0; offset < number.length && number[offset] == 0; ++offset)
212          ;
213
214       byte[] key = new byte[2 * HASH_LEN];
215       byte[] hout;
216
217       int klen = (number.length - offset) / 2;
218       byte[] hbuf = new byte[klen];
219
220       for(i = 0; i < klen; ++i)
221       {
222          hbuf[i] = number[number.length - 2 * i - 1];
223       }
224       hout = newDigest().digest(hbuf);
225       for(i = 0; i < HASH_LEN; ++i)
226          key[2 * i] = hout[i];
227
228       for(i = 0; i < klen; ++i)
229       {
230          hbuf[i] = number[number.length - 2 * i - 2];
231       }
232       hout = newDigest().digest(hbuf);
233       for(i = 0; i < HASH_LEN; ++i)
234          key[2 * i + 1] = hout[i];
235
236       return key;
237    }
238
239    /** Treat the input as the MSB representation of a number,
240     and lop off leading zero elements. For efficiency, the
241     input is simply returned if no leading zeroes are found.
242     */

243    public static byte[] trim(byte[] in)
244    {
245       if(in.length == 0 || in[0] != 0)
246          return in;
247
248       int len = in.length;
249       int i = 1;
250       while(in[i] == 0 && i < len)
251          ++i;
252       byte[] ret = new byte[len - i];
253       System.arraycopy(in, i, ret, 0, len - i);
254       return ret;
255    }
256
257    public static byte[] xor(byte[] b1, byte[] b2, int length)
258    {
259       byte[] result = new byte[length];
260       for(int i = 0; i < length; ++i)
261          result[i] = (byte) (b1[i] ^ b2[i]);
262       return result;
263    }
264
265    /**
266     * Hex encoding of hashes, as used by Catalina. Each byte is converted to
267     * the corresponding two hex characters.
268     */

269    public static String JavaDoc encodeBase16(byte[] bytes)
270    {
271       StringBuffer JavaDoc sb = new StringBuffer JavaDoc(bytes.length * 2);
272       for (int i = 0; i < bytes.length; i++)
273       {
274          byte b = bytes[i];
275          // top 4 bits
276
char c = (char)((b >> 4) & 0xf);
277          if(c > 9)
278             c = (char)((c - 10) + 'a');
279          else
280             c = (char)(c + '0');
281          sb.append(c);
282          // bottom 4 bits
283
c = (char)(b & 0xf);
284          if (c > 9)
285             c = (char)((c - 10) + 'a');
286          else
287             c = (char)(c + '0');
288          sb.append(c);
289       }
290       return sb.toString();
291    }
292
293    /**
294     * BASE64 encoder implementation.
295     * Provides encoding methods, using the BASE64 encoding rules, as defined
296     * in the MIME specification, <a HREF="http://ietf.org/rfc/rfc1521.txt">rfc1521</a>.
297     */

298    public static String JavaDoc encodeBase64(byte[] bytes)
299    {
300       String JavaDoc base64 = null;
301       try
302       {
303          base64 = Base64Encoder.encode(bytes);
304       }
305       catch(Exception JavaDoc e)
306       {
307       }
308       return base64;
309    }
310
311   /**
312    * Calculate a password hash using a MessageDigest.
313    *
314    * @param hashAlgorithm - the MessageDigest algorithm name
315    * @param hashEncoding - either base64 or hex to specify the type of
316       encoding the MessageDigest as a string.
317    * @param hashCharset - the charset used to create the byte[] passed to the
318    * MessageDigestfrom the password String. If null the platform default is
319    * used.
320    * @param username - ignored in default version
321    * @param password - the password string to be hashed
322    * @return the hashed string if successful, null if there is a digest exception
323    */

324    public static String JavaDoc createPasswordHash(String JavaDoc hashAlgorithm, String JavaDoc hashEncoding,
325       String JavaDoc hashCharset, String JavaDoc username, String JavaDoc password)
326   {
327      return createPasswordHash(hashAlgorithm, hashEncoding,
328       hashCharset, username, password, null);
329   }
330    /**
331     * Calculate a password hash using a MessageDigest.
332     *
333     * @param hashAlgorithm - the MessageDigest algorithm name
334     * @param hashEncoding - either base64 or hex to specify the type of
335        encoding the MessageDigest as a string.
336     * @param hashCharset - the charset used to create the byte[] passed to the
337     * MessageDigestfrom the password String. If null the platform default is
338     * used.
339     * @param username - ignored in default version
340     * @param password - the password string to be hashed
341     * @param callback - the callback used to allow customization of the hash
342     * to occur. The preDigest method is called before the password is added
343     * and the postDigest method is called after the password has been added.
344     * @return the hashed string if successful, null if there is a digest exception
345     */

346    public static String JavaDoc createPasswordHash(String JavaDoc hashAlgorithm, String JavaDoc hashEncoding,
347       String JavaDoc hashCharset, String JavaDoc username, String JavaDoc password, DigestCallback callback)
348    {
349       byte[] passBytes;
350       String JavaDoc passwordHash = null;
351
352       // convert password to byte data
353
try
354       {
355          if(hashCharset == null)
356             passBytes = password.getBytes();
357          else
358             passBytes = password.getBytes(hashCharset);
359       }
360       catch(UnsupportedEncodingException JavaDoc uee)
361       {
362          log.error("charset " + hashCharset + " not found. Using platform default.", uee);
363          passBytes = password.getBytes();
364       }
365
366       // calculate the hash and apply the encoding.
367
try
368       {
369          MessageDigest JavaDoc md = MessageDigest.getInstance(hashAlgorithm);
370          if( callback != null )
371             callback.preDigest(md);
372          md.update(passBytes);
373          if( callback != null )
374             callback.postDigest(md);
375          byte[] hash = md.digest();
376          if(hashEncoding.equalsIgnoreCase(BASE64_ENCODING))
377          {
378             passwordHash = Util.encodeBase64(hash);
379          }
380          else if(hashEncoding.equalsIgnoreCase(BASE16_ENCODING))
381          {
382             passwordHash = Util.encodeBase16(hash);
383          }
384          else
385          {
386             log.error("Unsupported hash encoding format " + hashEncoding);
387          }
388       }
389       catch(Exception JavaDoc e)
390       {
391          log.error("Password hash calculation failed ", e);
392       }
393       return passwordHash;
394    }
395
396    // These functions assume that the byte array has MSB at 0, LSB at end.
397
// Reverse the byte array (not the String) if this is not the case.
398
// All base64 strings are in natural order, least significant digit last.
399
public static String JavaDoc tob64(byte[] buffer)
400    {
401       return Base64Utils.tob64(buffer);
402    }
403
404    public static byte[] fromb64(String JavaDoc str) throws NumberFormatException JavaDoc
405    {
406       return Base64Utils.fromb64(str);
407    }
408
409    /** From Appendix E of the JCE ref guide, the xaximum key size
410     * allowed by the "Strong" jurisdiction policy files allows a maximum Blowfish
411     * cipher size of 128 bits.
412     * @return true if a Blowfish key can be initialized with 256 bit
413     * size, false otherwise.
414     */

415    public static boolean hasUnlimitedCrypto()
416    {
417       boolean hasUnlimitedCrypto = false;
418       try
419       {
420          ClassLoader JavaDoc loader = Thread.currentThread().getContextClassLoader();
421          Class JavaDoc keyGenClass = loader.loadClass("javax.crypto.KeyGenerator");
422          Class JavaDoc[] sig = {String JavaDoc.class};
423          Object JavaDoc[] args = {"Blowfish"};
424          Method JavaDoc kgenInstance = keyGenClass.getDeclaredMethod("getInstance", sig);
425          Object JavaDoc kgen = kgenInstance.invoke(null, args);
426
427          Class JavaDoc[] sig2 = {int.class};
428          Object JavaDoc[] args2 = {new Integer JavaDoc(256)};
429          Method JavaDoc init = keyGenClass.getDeclaredMethod("init", sig2);
430          init.invoke(kgen, args2);
431          hasUnlimitedCrypto = true;
432       }
433       catch(Throwable JavaDoc e)
434       {
435          log.debug("hasUnlimitedCrypto error", e);
436       }
437       return hasUnlimitedCrypto;
438    }
439
440    /** Use reflection to create a javax.crypto.spec.SecretKeySpec to avoid
441     an explicit reference to SecretKeySpec so that the JCE is not needed
442     unless the SRP parameters indicate that encryption is needed.
443     @return a javax.cyrpto.SecretKey
444    */

445    public static Object JavaDoc createSecretKey(String JavaDoc cipherAlgorithm, Object JavaDoc key) throws KeyException JavaDoc
446    {
447       Class JavaDoc[] signature = {key.getClass(), String JavaDoc.class};
448       Object JavaDoc[] args = {key, cipherAlgorithm};
449       Object JavaDoc secretKey = null;
450       try
451       {
452           ClassLoader JavaDoc loader = Thread.currentThread().getContextClassLoader();
453           Class JavaDoc secretKeySpecClass = loader.loadClass("javax.crypto.spec.SecretKeySpec");
454           Constructor JavaDoc ctor = secretKeySpecClass.getDeclaredConstructor(signature);
455           secretKey = ctor.newInstance(args);
456       }
457       catch(Exception JavaDoc e)
458       {
459           throw new KeyException JavaDoc("Failed to create SecretKeySpec from session key, msg="+e.getMessage());
460       }
461       catch(Throwable JavaDoc e)
462       {
463          throw new KeyException JavaDoc("Unexpected exception during SecretKeySpec creation, msg="+e.getMessage());
464       }
465       return secretKey;
466    }
467
468    /**
469     * @param cipherAlgorithm
470     * @return A javax.crypto.Cipher
471     * @throws GeneralSecurityException
472     */

473    public static Object JavaDoc createCipher(String JavaDoc cipherAlgorithm)
474       throws GeneralSecurityException JavaDoc
475    {
476       javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(cipherAlgorithm);
477       return cipher;
478    }
479    public static Object JavaDoc createSealedObject(String JavaDoc cipherAlgorithm, Object JavaDoc key, byte[] cipherIV,
480       Serializable JavaDoc data)
481       throws GeneralSecurityException JavaDoc
482    {
483       Object JavaDoc sealedObject = null;
484       try
485       {
486           javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(cipherAlgorithm);
487          javax.crypto.SecretKey skey = (javax.crypto.SecretKey) key;
488          if( cipherIV != null )
489          {
490             javax.crypto.spec.IvParameterSpec iv = new javax.crypto.spec.IvParameterSpec(cipherIV);
491             cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, skey, iv);
492          }
493          else
494          {
495             cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, skey);
496          }
497          sealedObject = new javax.crypto.SealedObject(data, cipher);
498       }
499       catch(GeneralSecurityException JavaDoc e)
500       {
501           throw e;
502       }
503       catch(Throwable JavaDoc e)
504       {
505          throw new GeneralSecurityException JavaDoc("Failed to create SealedObject, msg="+e.getMessage());
506       }
507       return sealedObject;
508    }
509
510    public static Object JavaDoc accessSealedObject(String JavaDoc cipherAlgorithm, Object JavaDoc key, byte[] cipherIV,
511       Object JavaDoc obj)
512       throws GeneralSecurityException JavaDoc
513    {
514       Object JavaDoc data = null;
515       try
516       {
517           javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(cipherAlgorithm);
518          javax.crypto.SecretKey skey = (javax.crypto.SecretKey) key;
519          if( cipherIV != null )
520          {
521             javax.crypto.spec.IvParameterSpec iv = new javax.crypto.spec.IvParameterSpec(cipherIV);
522             cipher.init(javax.crypto.Cipher.DECRYPT_MODE, skey, iv);
523          }
524          else
525          {
526             cipher.init(javax.crypto.Cipher.DECRYPT_MODE, skey);
527          }
528          javax.crypto.SealedObject sealedObj = (javax.crypto.SealedObject) obj;
529          data = sealedObj.getObject(cipher);
530       }
531       catch(GeneralSecurityException JavaDoc e)
532       {
533           throw e;
534       }
535       catch(Throwable JavaDoc e)
536       {
537          throw new GeneralSecurityException JavaDoc("Failed to access SealedObject, msg="+e.getMessage());
538       }
539       return data;
540    }
541 }
542
Popular Tags