/*************************************************************************
* *
* EJBCA Community: The OpenSource Certificate Authority *
* *
* This software is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2.1 of the License, or any later version. *
* *
* See terms of license at gnu.org. *
* *
*************************************************************************/
package org.ejbca.ui.cli;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
/**
*
PasswordGenerator Module
*
Implements a cryptographically secure password generator. Runs as a
* command line client which may ask for the following parameters (if specified)
*
* Flag Description Default
* -h Hash function used for mixing SHA-256
* -c Charset used for the generated password [a-zA-Z0-9]
* -b Length of the password in bits 128
* -s User input to mix into the password null
*
* @version $Id: PasswordGenerator.java 28525 2018-03-20 11:49:35Z bastianf $
*/
public class PasswordGenerator extends ClientToolBox {
private static final SecureRandom secureRandom = new SecureRandom();
@Override
protected void execute(final String[] args) {
final List argsList = new ArrayList(Arrays.asList(args));
argsList.remove(getName());
if (argsList.contains("help")) {
System.out.println("Flag Description Default");
System.out.println("-h Hash function used for mixing SHA-256");
System.out.println("-c Charset used for the generated password [a-zA-Z0-9]");
System.out.println("-b Length of the password in bits 128");
System.out.println("-s User input to mix into the password null");
return;
}
try {
final String algorithm = readInput(argsList, "Algorithm [SHA-256]", "-h", "SHA-256");
final String charset = expandRegex(readInput(argsList, "Charset [a-zA-Z0-9]", "-c", "[a-zA-Z0-9]"));
final int passwordBits = Integer.valueOf(readInput(argsList, "Bit strength [128]", "-b", "128"));
final String seed = readInput(argsList, "Seed [null]", "-s", "");
if (charset.length() < 2) {
System.err.println("Charset must consist of at least 2 characters.");
return;
}
byte[] state = new byte[256];
secureRandom.nextBytes(state);
state = mix(algorithm, state, seed.getBytes("UTF-8"));
final int charCount = (int) Math.ceil((passwordBits * Math.log(2)) / Math.log(charset.length()));
final StringBuilder password = new StringBuilder();
for (int i = 0; i < charCount; ++i) {
state = mix(algorithm, state, state);
final int p = new BigInteger(1, state).mod(BigInteger.valueOf(charset.length())).intValue();
password.append(charset.charAt(p));
}
System.out.println(password.toString());
} catch (final NumberFormatException e) {
System.err.println(e.getMessage());
} catch (final UnsupportedEncodingException e) {
System.err.println(e.getMessage());
} catch (final NoSuchAlgorithmException e) {
System.err.println(e.getMessage());
} catch (final IllegalStateException e) {
System.err.println("Bye!");
return;
}
}
private String readInput(final List args, final String title, final String flag, final String defaultValue) throws IllegalStateException {
if (args.contains(flag)) {
System.out.print(title + ": ");
System.out.flush();
final String input = System.console().readLine();
if (input == null) {
throw new IllegalStateException();
}
if (input.length() > 0) {
return input;
}
}
return defaultValue;
}
private byte[] mix(final String algorithm, final byte[] b1, final byte[] b2) throws NoSuchAlgorithmException {
final byte[] b = new byte[b1.length + b2.length];
for (int i = 0; i < b1.length; ++i) {
b[i] = b1[i];
}
for (int i = 0; i < b2.length; ++i) {
b[b1.length + i] = b2[i];
}
return MessageDigest.getInstance(algorithm).digest(b);
}
String expandRegex(final String regex) {
final StringBuilder charset = new StringBuilder();
final Pattern pattern = Pattern.compile(regex);
for (int i = 0; i < 256; ++i) {
if (pattern.matcher(Character.toString((char) i)).matches()) {
charset.append((char) i);
}
}
return charset.toString();
}
@Override
protected String getName() {
return "PasswordGenerator";
}
}