PBEKeySpecCrypt.java

/*
 * GovWay - A customizable API Gateway 
 * https://govway.org
 * 
 * Copyright (c) 2005-2024 Link.it srl (https://link.it). 
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3, as published by
 * the Free Software Foundation.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package org.openspcoop2.utils.crypt;

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

import org.openspcoop2.utils.UtilsException;
import org.openspcoop2.utils.io.Base64Utilities;
import org.openspcoop2.utils.io.HexBinaryUtilities;
import org.openspcoop2.utils.random.RandomGenerator;
import org.slf4j.Logger;

/**
 * PBEKeySpecCrypt
 *
 * @author Poli Andrea (apoli@link.it)
 * @author $Author$
 * @version $Rev$, $Date$
 */
public class PBEKeySpecCrypt implements ICrypt {

	private static final String PBE_ALGORITHM = "PBKDF2WithHmacSHA1";
	
	private Logger log;
	private CryptConfig config;
	private RandomGenerator randomGenerator;

	public PBEKeySpecCrypt(){
		
	}
	
	@Override
	public void init(Logger log, CryptConfig config) {
		this.log = log;
		
		this.config = config;
		if(this.config == null) {
			this.config = new CryptConfig();
		}
		
		this.randomGenerator = new RandomGenerator(this.config.isUseSecureRandom(), this.config.getAlgorithmSecureRandom());
	}
	
	@Override
	public String crypt(String password) throws UtilsException {
		
		int iterations = 1000;
		if(this.config.getIteration()!=null && this.config.getIteration()>0) {
			iterations = this.config.getIteration().intValue();
		}
		
		char[] chars = password.toCharArray();
		
		int saltLength = 16;
		if(this.config.getSaltLength()!=null && this.config.getSaltLength()>0) {
			saltLength = this.config.getSaltLength().intValue();
		}
		byte[] salt = this.randomGenerator.nextRandomBytes(saltLength);
		
		try {
			PBEKeySpec spec = new PBEKeySpec(chars, salt, iterations, 64 * 8);
			String algo = PBE_ALGORITHM;
			if(this.config.getDigestAlgorithm()!=null) {
				algo = this.config.getDigestAlgorithm();
			}
			SecretKeyFactory skf = SecretKeyFactory.getInstance(algo);
			byte[] hash = skf.generateSecret(spec).getEncoded();
			String prefix = iterations + ":";
			if(this.config.isUseBase64Encoding()) {
				return prefix +  Base64Utilities.encodeAsString(salt) + ":" + Base64Utilities.encodeAsString(hash);
			}
			else {
				return prefix +  HexBinaryUtilities.encodeAsString(salt) + ":" + HexBinaryUtilities.encodeAsString(hash);
			}
		}catch(Exception e) {
			throw new UtilsException(e.getMessage(),e);
		}
		
	}
	
	@Override
	public boolean check(String password, String pwcrypt) {
		try {
			String[] parts = pwcrypt.split(":");
			if(parts.length!=3) {
				throw new Exception("Wrong format");
			}
			int iterations = Integer.parseInt(parts[0]);
			byte[] salt = null;
			byte[] hash = null;
			if(this.config.isUseBase64Encoding()) {
				salt = Base64Utilities.decode(parts[1]);
				hash = Base64Utilities.decode(parts[2]);
			}
			else {
				salt = HexBinaryUtilities.decode(parts[1]);
				hash = HexBinaryUtilities.decode(parts[2]);
			}
			
			PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterations, hash.length * 8);
			
			String algo = PBE_ALGORITHM;
			if(this.config.getDigestAlgorithm()!=null) {
				algo = this.config.getDigestAlgorithm();
			}
			SecretKeyFactory skf = SecretKeyFactory.getInstance(algo);
			
			byte[] testHash = skf.generateSecret(spec).getEncoded();
			
			int diff = hash.length ^ testHash.length;
			
			for(int i = 0; i < hash.length && i < testHash.length; i++)
			{
				diff |= hash[i] ^ testHash[i];
			}
			
			return diff == 0;
		}catch(Throwable e){
			if(this.log!=null) {
				this.log.error("Verifica password '"+pwcrypt+"' fallita: "+e.getMessage(),e);
			}
			return false;
		}
	}
	
}