PasswordGenerator.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 java.io.InputStream;
import java.io.Serializable;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;

import org.openspcoop2.utils.UtilsException;
import org.openspcoop2.utils.io.Base64Utilities;
import org.openspcoop2.utils.io.HexBinaryUtilities;

/**
 * PasswordGenerator
 *
 * @author Andrea Poli (apoli@link.it)
 * @author $Author$
 * @version $Rev$, $Date$
 */

public class PasswordGenerator extends PasswordVerifier implements Serializable {
	
	private static final long serialVersionUID = 1L;
	
	public static PasswordGenerator DEFAULT;
	static {
		DEFAULT = new PasswordGenerator();
		DEFAULT.setIncludeLowerCaseLetter(true);
		DEFAULT.setIncludeUpperCaseLetter(true);
		DEFAULT.setIncludeNumber(true);
		DEFAULT.setIncludeNotAlphanumericSymbol(true);
	}
	
	public static void main(String[] args) throws UtilsException {
		// Metodo utilizzato dal setup antinstaller
		System.out.println(DEFAULT.generate());
	}
	
	public PasswordGenerator(){
		super();
	}
	public PasswordGenerator(String resource) throws UtilsException{
		super(resource);
	}
	public PasswordGenerator(InputStream is) throws UtilsException{
		super(is);
	}
	public PasswordGenerator(Properties p) throws UtilsException{
		super(p);
	}
	public PasswordGenerator(PasswordVerifier pv) throws UtilsException{
		super(pv);
		if(!this.includeLowerCaseLetter && !this.includeUpperCaseLetter && !this.includeNumber) {
			// non essendoci vincoli genero password come il generatore di default
			this.includeLowerCaseLetter = DEFAULT.isIncludeLowerCaseLetter();
			this.includeUpperCaseLetter = DEFAULT.isIncludeUpperCaseLetter();
			this.includeNumber = DEFAULT.isIncludeNumber();
		}
	}
	
	private String dictionaryChars = "abcdefghijklmnopqrstuvwxyz";
	private String dictionaryNumbers = "1234567890";
	private String dictionaryAlpha = "!?@#$%^&*()-+<>.:_";

	public String getDictionaryChars() {
		return this.dictionaryChars;
	}
	public void setDictionaryChars(String dictionaryChars) {
		this.dictionaryChars = dictionaryChars;
	}
	public String getDictionaryNumbers() {
		return this.dictionaryNumbers;
	}
	public void setDictionaryNumbers(String dictionaryNumbers) {
		this.dictionaryNumbers = dictionaryNumbers;
	}
	public String getDictionaryAlpha() {
		return this.dictionaryAlpha;
	}
	public void setDictionaryAlpha(String dictionaryAlpha) {
		this.dictionaryAlpha = dictionaryAlpha;
	}
	
	private int defaultLength = 10;
	public int getDefaultLength() {
		return this.defaultLength;
	}
	public void setDefaultLength(int defaultLength) {
		this.defaultLength = defaultLength;
	}
	
	private boolean base64 = false;
	private boolean hex = false;
	public boolean isBase64() {
		return this.base64;
	}
	public void setBase64(boolean base64) {
		this.base64 = base64;
	}
	public boolean isHex() {
		return this.hex;
	}
	public void setHex(boolean hex) {
		this.hex = hex;
	}
	
	private String prefix = null;
	private String suffix = null;
	public String getPrefix() {
		return this.prefix;
	}
	public void setPrefix(String prefix) {
		this.prefix = prefix;
	}
	public String getSuffix() {
		return this.suffix;
	}
	public void setSuffix(String suffix) {
		this.suffix = suffix;
	}
	
	public String generate() throws UtilsException{
				
		if(this.minLenght>0){
			if(this.defaultLength<this.minLenght) {
				this.defaultLength = this.minLenght;
			}
		}
		
		if(this.maxLenght>0){
			if(this.defaultLength>this.maxLenght){
				this.defaultLength = this.maxLenght;
			}
		}
		
		return this.generate(this.defaultLength);
	}
	public String generate(int length) throws UtilsException{
		return this.generate("login",length);
	}
	public String generate(String username, int length) throws UtilsException{
		
		if(this.minLenght>0){
			if(length<this.minLenght){
				throw new UtilsException("La password deve essere composta almeno da "+this.minLenght+" caratteri");
			}
		}
		if(this.maxLenght>0){
			if(length>this.maxLenght){
				throw new UtilsException("La password non deve essere composta da più di "+this.minLenght+" caratteri");
			}
		}
		
		int tentativi = 100;
		for (int i = 0; i < tentativi; i++) {
			String password = _generate(length);
			if(this.validate(username, password)) {
				
				if(this.base64) {
					password =  Base64Utilities.encodeAsString(password.getBytes());
				}
				else if(this.hex) {
					password = HexBinaryUtilities.encodeAsString(password.getBytes());
				}
				
				// NOTA i prefissi e i suffissi non si codificano in modo che si possa aggiungere caratteri speciali che consentano di identificare le parti una volta effettuata la codifica (es. il '.' in base64)
				if(this.prefix!=null) {
					password = this.prefix + password;
				}
				if(this.suffix!=null) {
					password = password + this.suffix;
				}
				
				return password;
			}
		}
		
		throw new UtilsException("La generazione non è riuscita a produrre una password che soddisfi tutti i vincoli"); 
	}
	
	//private Random random = new Random();
	private SecureRandom random = new SecureRandom(); 
	
	private String _generate(int length) throws UtilsException{
		
		if(!this.includeLowerCaseLetter && !this.includeUpperCaseLetter && !this.includeNumber) {
			throw new UtilsException("La generazione richiede almeno che l'utilizzo di numeri o caratteri sia abilitato"); 
		}
		
		List<String> password = new ArrayList<>();

		
		String tmpDictionaryCharsLowerCase = new String(this.dictionaryChars);
		String tmpDictionaryCharsUpperCase = new String(this.dictionaryChars);
		String tmpDictionaryNumbers = new String(this.dictionaryNumbers);
		String tmpDictionaryAlpha = new String(this.dictionaryAlpha);
		
		int i = 0;
		if(this.includeNotAlphanumericSymbol){
			int randomOffset = this.random.nextInt(tmpDictionaryAlpha.length());
			String s = tmpDictionaryAlpha.charAt(randomOffset)+"";
			password.add(s);
			i++;
			if(this.allDistinctCharacters){
				tmpDictionaryAlpha = tmpDictionaryAlpha.replace(s, "");
			}
		}
		for (; i < length;) {
			boolean addChar = false;
			if(this.includeLowerCaseLetter){
				if(tmpDictionaryCharsLowerCase.length()>0) {
					int randomOffset = this.random.nextInt(tmpDictionaryCharsLowerCase.length());
					String s = tmpDictionaryCharsLowerCase.charAt(randomOffset)+"";
					password.add(s);
					addChar = true;
					i++;
					if(this.allDistinctCharacters){
						tmpDictionaryCharsLowerCase = tmpDictionaryCharsLowerCase.replace(s, "");
					}
				}
			}
			if(i >= length) {
				break;
			}
			if(this.includeUpperCaseLetter){
				if(tmpDictionaryCharsUpperCase.length()>0) {
					int randomOffset = this.random.nextInt(tmpDictionaryCharsUpperCase.length());
					String s = tmpDictionaryCharsUpperCase.charAt(randomOffset)+"";
					password.add(s.toUpperCase());
					addChar = true;
					i++;
					if(this.allDistinctCharacters){
						tmpDictionaryCharsUpperCase = tmpDictionaryCharsUpperCase.replace(s, "");
					}
				}
			}
			if(i >= length) {
				break;
			}
			if(this.includeNumber){
				if(tmpDictionaryNumbers.length()>0) {
					int randomOffset = this.random.nextInt(tmpDictionaryNumbers.length());
					String s = tmpDictionaryNumbers.charAt(randomOffset)+"";
					password.add(s);
					addChar = true;
					i++;
					if(this.allDistinctCharacters){
						tmpDictionaryNumbers = tmpDictionaryNumbers.replace(s, "");
					}
				}
			}
			if(!addChar) {
				throw new UtilsException("Sono terminati i caratteri utilizzabili per la generazione della password di lunghezza '"+length+"'"); 
			}
		}
		
		
		Collections.shuffle(password);
		
		StringBuilder bf = new StringBuilder();
		for (String s : password) {
			bf.append(s);
		}
		return  bf.toString();
		
	}
}