All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.directwebremoting.impl.DefaultSecureIdGenerator Maven / Gradle / Ivy

package org.directwebremoting.impl;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.util.Arrays;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.directwebremoting.extend.IdGenerator;
import org.directwebremoting.util.Base64;

/**
 * An id generator that generates secure (non-predictable) random strings
 * that are guaranteed to be unique for eternity within the scope of the
 * running server, as long as the real-time clock is not adjusted backwards.
 * @author Mike Wilson [mikewse at g mail dot com]
 */
public class DefaultSecureIdGenerator implements IdGenerator
{
    public DefaultSecureIdGenerator()
    {
        // SecureRandom implements a cryptographically secure pseudo-random
        // number generator (PRNG).
        // We want Sun's SHA1 algorithm on all platforms.
        // (see http://www.cigital.com/justiceleague/2009/08/14/proper-use-of-javas-securerandom/)

        // Try Sun's SHA1
        try
        {
            random = SecureRandom.getInstance("SHA1PRNG", "SUN");
        }
        catch (NoSuchAlgorithmException ex) { /* squelch */ }
        catch (NoSuchProviderException ex) { /* squelch */ }

        // Try any SHA1
        try
        {
            if (random == null)
            {
                random = SecureRandom.getInstance("SHA1PRNG");
            }
        }
        catch (NoSuchAlgorithmException ex) { /* squelch */ }

        // Fall back to default
        if (random == null)
        {
            random = new SecureRandom();
        }

        // Let SecureRandom do its own secure initialization before we add our seed
        random.nextBytes(new byte[1]);

        // Now seed the generator
        reseed();
    }

    /**
     * Generates an id string guaranteed to be unique for eternity within
     * the scope of the running server, as long as the real-time clock is
     * not adjusted backwards. The generated string consists of alphanumerics
     * (A-Z, a-z, 0-9) and symbols !, ~ and -.
     * @return A unique id string
     * @see org.directwebremoting.extend.IdGenerator#generate()
     */
    public synchronized String generate()
    {
        reseedIfNeeded();

        StringBuilder idbuf = new StringBuilder();

        // New cookie RFC 6265 says the following non-alphanumeric chars are allowed
        // in cookie values: %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E, which
        // corresponds to:
        //   !#$%&'()*+-./:<=>?@[]^_`{|}~
        // We avoid the following as they have special meaning in URLs:
        //   #%&+?
        // We avoid the following as we are using them as separators ourselves:
        //   /-
        // We avoid these as they were discouraged in older cookie specs:
        //   ():=@[]{}
        // We avoid these as they have special meaning in regexps:
        //   $*.^|
        // These remain:
        //   !'<>_`~
        // And we have chosen these as our two "base64" special chars:
        //   !~

        // Generate 21 random bytes (168 bits) and add as 28 printable 6-bit bytes
        final byte[] bytes = new byte[21];
        random.nextBytes(bytes);
        String base64 = new String(Base64.encodeBase64(bytes));
        String base64Adjusted = base64.replaceAll("\\+", "!").replaceAll("/", "~");
        idbuf.append(base64Adjusted);

        // Second part of the id string is the 64 bit timestamp converted
        // into as many 6 bit lookup chars as needed (variable length)
        long time = System.currentTimeMillis();
        long remainder = time;
        final char[] charmap = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!~".toCharArray();
        while (remainder > 0)
        {
            idbuf.append(charmap[(int) remainder & 0x3F]);
            remainder = remainder >>> 6;
        }

        // If we have generated other ids during the same millisecond (same
        // millisecond could mean an up to 50 msec interval on some platforms
        // due to a coarse timer resolution) then ensure that we have no
        // collisions ...
        if (time == lastGenTime)
        {
            // Add a third delimited section (delimiter needed to avoid
            // collisions due to sections two and three being of variable
            // length) with an incremented number mapped to lookup chars
            idbuf.append('-'); // delimiter
            remainder = countSinceTimeChange;
            while (remainder > 0)
            {
                idbuf.append(charmap[(int) remainder & 0x3F]);
                remainder = remainder >>> 6;
            }
        }
        // ... otherwise reset to prepare the new millisecond
        else
        {
            countSinceTimeChange = 0;
        }

        count++;
        countSinceSeed++;
        countSinceTimeChange++;
        lastGenTime = time;
        String id = idbuf.toString();
        lastHashCode = System.identityHashCode(id);
        return id;
    }

    /**
     * Trigger reseed at desired intervals.
     */
    protected void reseedIfNeeded()
    {
        boolean needReseed = false;

        // Reseed if more than 15 minutes have passed since last reseed
        long time = System.currentTimeMillis();
        if (time - seedTime > 15 * 60 * 1000)
        {
            needReseed = true;
        }

        // Reseed if more than 1000 ids have been generated
        if (countSinceSeed > 1000)
        {
            needReseed = true;
        }

        if (needReseed)
        {
            reseed();

            // Update bookkeeping
            seedTime = time;
            countSinceSeed = 0;
        }
    }

    /**
     * Set up entropy in random number generator
     */
    protected void reseed()
    {
        // We would really like to reseed using:
        //   random.setSeed(random.generateSeed(20));
        // to get 160 bits (SHA1 width) truly random data, but as most
        // Linuxes don't come configured with the driver for the Intel
        // hardware RNG, this usually blocks the whole server...

        try {
            // Make up a base for entropy
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            DataOutputStream data = new DataOutputStream(os);
            data.writeLong(System.nanoTime());
            byte[] prngOutput = new byte[128];
            random.nextBytes(prngOutput);
            data.write(prngOutput);
            data.write(count);
            data.write(lastHashCode);
            os.close();
            byte[] base = os.toByteArray();

            // Hash and pick out first 128 bits
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] hash = digest.digest(base);
            byte[] hash128 = new byte[16];
            System.arraycopy(hash, 0, hash128, 0, 16);

            // Reseed
            log.debug("Reseeding with " + Arrays.toString(hash128));
            random.setSeed(hash128);
        }
        catch(IOException ex) {
            throw new RuntimeException(ex);
        }
        catch(NoSuchAlgorithmException ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * The random number source
     */
    protected SecureRandom random = null;

    /**
     * Number of ids generated since startup
     */
    protected int count = 0;

    /**
     * Number of ids generated since last seeding of random source
     */
    protected int countSinceSeed = 0;

    /**
     * Timestamp from last seeding of random source
     */
    protected long seedTime = 0;

    /**
     * Number of ids generated during the last same millisecond
     */
    protected int countSinceTimeChange = 0;

    /**
     * Timestamp from last id generation
     */
    protected long lastGenTime = 0;

    /**
     * Hashcode from last id generation
     */
    protected int lastHashCode = 0;

    /**
     * The log stream
     */
    private static final Log log = LogFactory.getLog(DefaultSecureIdGenerator.class);
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy