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

uk.ac.ceh.components.tokengeneration.stateless.StatelessTokenGenerator Maven / Gradle / Ivy

The newest version!
package uk.ac.ceh.components.tokengeneration.stateless;

import java.nio.ByteBuffer;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Calendar;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import uk.ac.ceh.components.tokengeneration.ExpiredTokenException;
import uk.ac.ceh.components.tokengeneration.InvalidTokenException;
import uk.ac.ceh.components.tokengeneration.Token;
import uk.ac.ceh.components.tokengeneration.TokenGenerator;

/**
 * The following TokenGenerator will use simple cryptographic principles to 
 * generate tokens which do not require state to be stored about those tokens.
 * 
 * The only 2 pieces of information needed to release a token are an Hmac key and
 * an AES key (Obtained from an instance of StatelessTokenKeyContainer)
 * @author Christopher Johnson
 */
public class StatelessTokenGenerator implements TokenGenerator {
    protected static final int INITALIZATION_VECTOR_SIZE = 16;
    protected static final String MAC_ALGORITHM = "HmacSHA256";
    protected static final String SECRET_KEY_ALGORITHM = "AES";
    protected static final String CRYPTO_ALGORITHM = "AES/CBC/PKCS5Padding";
    
    private StatelessTokenKeyContainer keys;
    
    public StatelessTokenGenerator(StatelessTokenKeyContainer keys) {
        this.keys = keys;
    }
    
    /**
     * The following method will perform a secret message decryption of the given
     * token.
     * @param token The token to decrypt
     * @return The message encoded in the Token which was encoded by this 
     *  generator
     * @throws InvalidTokenException If the token was not generated by this Object
     * @throws ExpiredTokenException If the token is no longer valid
     */
    @Override
    public byte[] getMessage(Token token) throws InvalidTokenException, ExpiredTokenException {
        try {
            Mac mac = Mac.getInstance(MAC_ALGORITHM);
            mac.init(keys.getHMacKey());
            
            if(token.getBytes().length > INITALIZATION_VECTOR_SIZE + mac.getMacLength()) {
                ByteBuffer message = ByteBuffer.wrap(token.getBytes());

                byte[] keyCheckValue = getBytes(message, mac.getMacLength());
                message.mark(); //store the current place into the buffer
                
                //check if this message was created by this authenticator
                if(Arrays.equals(keyCheckValue, mac.doFinal(getBytes(message, message.remaining())))) {
                    message.reset(); //return to just after reading the mac message
                    Cipher decrypt = Cipher.getInstance(CRYPTO_ALGORITHM);
                    decrypt.init(Cipher.DECRYPT_MODE, keys.getKey(), new IvParameterSpec( getBytes(message, INITALIZATION_VECTOR_SIZE) ));

                    ByteBuffer payload = ByteBuffer.wrap(decrypt.doFinal(getBytes(message, message.remaining())));
                    if(payload.getLong() >= Calendar.getInstance().getTimeInMillis())  //is ticket still valid
                        return getBytes(payload, payload.remaining());
                    else 
                        throw new ExpiredTokenException("No longer valid");
                }
            }
            throw new InvalidTokenException("Invalid composite key");
        } catch (NoSuchAlgorithmException | NoSuchPaddingException ex) {
            throw new RuntimeException("A configuration error has occurred", ex);
        } catch (   InvalidKeyException | InvalidAlgorithmParameterException | 
                    IllegalBlockSizeException | BadPaddingException bpe) {
            throw new InvalidTokenException("Invalid composite key");
        }
    }
    
    /**
     * The following method will perform a SECRET_KEY_ALGORITHM encryption of
     * the following structure.
     * 
     * The byte structure of a token generated by this class is :
     *      BYTE_ARRAY_LENGTH           |                   CONTENT
     * -----------------------------------------------------------------------
     * ---------------------------- PLAINTEXT --------------------------------
     *  32 (byte size of hmac)          | HMAC value of the message including 
     *                                  |   the plain text vector
     *  @see #INITALIZATION_VECTOR_SIZE | The vector used for the message
     * --------------------------- CIPHER TEXT -------------------------------
     *  8 (byte size of long)           | The time in milliseconds from epoch 
     *                                  | when this token expires
     *  message.length                  | A secret message to be encrypted in 
     *                                  |   the token
     * 
     * @param message A message to be encrypted as a token
     * @param ttl Time in milliseconds until this token expires
     * @return An Token object with the above byte structure encrypted in 
     *  @see #SECRET_KEY_ALGORITHM
     */
    @Override
    public Token generateToken(byte[] message, int ttl) {
        try {
            Cipher encrypt = Cipher.getInstance(CRYPTO_ALGORITHM);
            encrypt.init(Cipher.ENCRYPT_MODE, keys.getKey());
            Mac mac = Mac.getInstance(MAC_ALGORITHM);
            mac.init(keys.getHMacKey());
            
            byte[] cipherText = encrypt.doFinal(ByteBuffer.allocate(8 + message.length) //Encrypt byte buffer
                .putLong(Calendar.getInstance().getTimeInMillis() + ttl)    //Store the time when this token becomes invalid
                .put(message)                                               //Store message
                .array()
            );                    
            
            byte[] payload = ByteBuffer.allocate(INITALIZATION_VECTOR_SIZE + cipherText.length)
                .put(encrypt.getIV())
                .put(cipherText)
                .array();
            
            return new Token(ByteBuffer.allocate(mac.getMacLength() + payload.length)
                .put(mac.doFinal(payload))
                .put(payload)
                .array()
            );
        } catch (   IllegalBlockSizeException | BadPaddingException | 
                    InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException ex) {
            throw new RuntimeException("A configuration error has occurred", ex);
        }
    }
    
    
    /**
     * Utility method to read a byte array of size from a ByteBuffer
     * @param buf The buffer to read from
     * @param size The amount of bytes to read
     * @return A byte array of bytes read from the buffer
     */
    private static byte[] getBytes(ByteBuffer buf, int size) {
        byte[] toReturn = new byte[size];
        buf.get(toReturn);
        return toReturn;
    }  
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy