org.entrystore.repository.security.Password.java Source code

Java tutorial

Introduction

Here is the source code for org.entrystore.repository.security.Password.java

Source

/*
 * Copyright (c) 2007-2014 MetaSolutions AB
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.entrystore.repository.security;

import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Date;

import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Helper methods for handling hashed and salted passwords, using PBKDF2.
 * 
 * Inspired by Martin Konicek's code on StackOverflow.
 * 
 * @author Hannes Ebner
 */
public class Password {

    private static Logger log = LoggerFactory.getLogger(Password.class);

    // Higher number of iterations causes more work for both the attacker and
    // our system when checking passwords. Optimally this should be dynamically
    // chosen depending on how many iterations the local machine manages in a
    // specific amount of time (e.g. < 10 ms); the amount of iterations should
    // then be stored together with hash and password.
    private static final int iterations = 10 * 1024;

    /*
     * SecureRandom, which is based internally on the 160-bit SHA-1 hash
     * function, can only generate 2^160 possible sequences of any length. So
     * there is no point in using more than 160 bits of salt (20 bytes) with
     * this generator.
     */
    // We use a salt of 16 byte length
    private static final int saltLen = 16;

    private static final int desiredKeyLen = 192;

    private static SecureRandom random;

    private static SecretKeyFactory secretKeyFactory;

    static {
        try {
            random = SecureRandom.getInstance("SHA1PRNG");
            secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");

            long before = new Date().getTime();
            random.setSeed(random.generateSeed(saltLen));
            log.info("Seeding of SecureRandom took " + (new Date().getTime() - before) + " ms");
        } catch (NoSuchAlgorithmException e) {
            log.error(e.getMessage());
        }
    }

    /**
     * Computes a salted PBKDF2. Empty passwords are not supported.
     */
    public static String getSaltedHash(String password) {
        if (password == null || password.length() == 0) {
            throw new IllegalArgumentException("Empty passwords are not supported");
        }

        byte[] salt = new byte[saltLen];
        random.nextBytes(salt);

        // return the salt along with the salted and hashed password
        return Base64.encodeBase64String(salt) + "$" + hash(password, salt);
    }

    /**
     * Checks whether given plaintext password corresponds to a stored salted
     * hash of the password.
     */
    public static boolean check(String password, String stored) {
        if (password == null || password.length() == 0) {
            throw new IllegalArgumentException("Empty passwords are not supported");
        }
        if (stored == null) {
            throw new IllegalArgumentException("Stored password must not be null");
        }
        String[] saltAndPass = stored.split("\\$");
        if (saltAndPass.length != 2) {
            return false;
        }
        String hashOfInput = hash(password, Base64.decodeBase64(saltAndPass[0]));
        return hashOfInput.equals(saltAndPass[1]);
    }

    private static String hash(String password, byte[] salt) {
        if (password == null || password.length() == 0) {
            throw new IllegalArgumentException("Empty passwords are not supported");
        }
        try {
            long before = new Date().getTime();
            SecretKey key = secretKeyFactory
                    .generateSecret(new PBEKeySpec(password.toCharArray(), salt, iterations, desiredKeyLen));
            log.info("Password hashing took " + (new Date().getTime() - before) + " ms");
            return Base64.encodeBase64String(key.getEncoded());
        } catch (GeneralSecurityException gse) {
            log.error(gse.getMessage());
        }
        return null;
    }

    public static String getRandomBase64(int length) {
        byte[] result = new byte[length];
        random.nextBytes(result);
        return Base64.encodeBase64String(result);
    }

    public static String sha256(String s) {
        MessageDigest digester;
        try {
            digester = MessageDigest.getInstance("SHA-256");
            digester.update(s.getBytes("UTF-8"));
            byte[] key = digester.digest();
            SecretKeySpec spec = new SecretKeySpec(key, "AES");
            return Base64.encodeBase64String(spec.getEncoded());
        } catch (NoSuchAlgorithmException nsae) {
            log.error(nsae.getMessage());
        } catch (UnsupportedEncodingException uee) {
            log.error(uee.getMessage());
        }
        return null;
    }

}