/* * JBoss, Home of Professional Open Source. * Copyright 2008, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.web.tomcat.service.session; import java.util.Random; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import org.jboss.logging.Logger; /** * Unique session id generator * * @author Ben Wang */ public class SessionIDGenerator { protected final static int SESSION_ID_BYTES = 16; // We want 16 Bytes for the session-id protected final static String SESSION_ID_HASH_ALGORITHM = "MD5"; protected final static String SESSION_ID_RANDOM_ALGORITHM = "SHA1PRNG"; protected final static String SESSION_ID_RANDOM_ALGORITHM_ALT = "IBMSecureRandom"; protected Logger log = Logger.getLogger(SessionIDGenerator.class); protected MessageDigest digest = null; protected Random random = null; protected static final SessionIDGenerator s_ = new SessionIDGenerator(); protected String sessionIdAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-_"; public static SessionIDGenerator getInstance() { return s_; } /** * The SessionIdAlphabet is the set of characters used to create a session Id */ public void setSessionIdAlphabet(String sessionIdAlphabet) { if (sessionIdAlphabet.length() != 65) { throw new IllegalArgumentException("SessionIdAlphabet must be exactly 65 characters long"); } checkDuplicateChars(sessionIdAlphabet); this.sessionIdAlphabet = sessionIdAlphabet; } protected void checkDuplicateChars(String sessionIdAlphabet) { char[] alphabet = sessionIdAlphabet.toCharArray(); for (int i=0; i < alphabet.length; i++) { if (!uniqueChar(alphabet[i], sessionIdAlphabet)) { throw new IllegalArgumentException("All chars in SessionIdAlphabet must be unique"); } } } // does a character appear in the String once and only once? protected boolean uniqueChar(char c, String s) { int firstIndex = s.indexOf(c); if (firstIndex == -1) return false; return s.indexOf(c, firstIndex + 1) == -1; } /** * The SessionIdAlphabet is the set of characters used to create a session Id */ public String getSessionIdAlphabet() { return this.sessionIdAlphabet; } public synchronized String getSessionId() { String id = generateSessionId(); if (log.isTraceEnabled()) log.trace("getSessionId() called: " + id); return id; } /** * Generate a session-id that is not guessable * * @return generated session-id */ protected synchronized String generateSessionId() { if (this.digest == null) { this.digest = getDigest(); } if (this.random == null) { this.random = getRandom(); } byte[] bytes = new byte[SESSION_ID_BYTES]; // get random bytes this.random.nextBytes(bytes); // Hash the random bytes bytes = this.digest.digest(bytes); // Render the result as a String of hexadecimal digits return encode(bytes); } /** * Encode the bytes into a String with a slightly modified Base64-algorithm * This code was written by Kevin Kelley <kelley@ruralnet.net> * and adapted by Thomas Peuss <jboss@peuss.de> * * @param data The bytes you want to encode * @return the encoded String */ protected String encode(byte[] data) { char[] out = new char[((data.length + 2) / 3) * 4]; char[] alphabet = this.sessionIdAlphabet.toCharArray(); // // 3 bytes encode to 4 chars. Output is always an even // multiple of 4 characters. // for (int i = 0, index = 0; i < data.length; i += 3, index += 4) { boolean quad = false; boolean trip = false; int val = (0xFF & (int) data[i]); val <<= 8; if ((i + 1) < data.length) { val |= (0xFF & (int) data[i + 1]); trip = true; } val <<= 8; if ((i + 2) < data.length) { val |= (0xFF & (int) data[i + 2]); quad = true; } out[index + 3] = alphabet[(quad ? (val & 0x3F) : 64)]; val >>= 6; out[index + 2] = alphabet[(trip ? (val & 0x3F) : 64)]; val >>= 6; out[index + 1] = alphabet[val & 0x3F]; val >>= 6; out[index + 0] = alphabet[val & 0x3F]; } return new String(out); } /** * get a random-number generator * * @return a random-number generator */ protected synchronized Random getRandom() { long seed; Random random = null; // Mix up the seed a bit seed = System.currentTimeMillis(); seed ^= Runtime.getRuntime().freeMemory(); try { random = SecureRandom.getInstance(SESSION_ID_RANDOM_ALGORITHM); } catch (NoSuchAlgorithmException e) { try { random = SecureRandom.getInstance(SESSION_ID_RANDOM_ALGORITHM_ALT); } catch (NoSuchAlgorithmException e_alt) { log.error("Could not generate SecureRandom for session-id randomness", e); log.error("Could not generate SecureRandom for session-id randomness", e_alt); return null; } } // set the generated seed for this PRNG random.setSeed(seed); return random; } /** * get a MessageDigest hash-generator * * @return a hash generator */ protected synchronized MessageDigest getDigest() { MessageDigest digest = null; try { digest = MessageDigest.getInstance(SESSION_ID_HASH_ALGORITHM); } catch (NoSuchAlgorithmException e) { log.error("Could not generate MessageDigest for session-id hashing", e); return null; } return digest; } }