package ch.ge.ve.commons.crypto.utils;
/*-
* #%L
* Common crypto utilities
* %%
* Copyright (C) 2015 - 2016 République et Canton de Genève
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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 Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
import com.google.common.base.Joiner;
import org.apache.log4j.Logger;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Utility for generating random values
*/
public class RandomValuesUtilities {
private static final Logger LOG = Logger.getLogger(RandomValuesUtilities.class);
public static final String SESSION_ID_POSSIBLE_CHARS = "abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ0123456789";
public static final int SESSION_ID_LENGTH = 64;
public static final String TRANSACTION_SECRET_POSSIBLE_CHARS = "ABCDEF0123456789";
public static final String CODE_POSSIBLE_ALPHA_CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ";
public static final String CODE_POSSIBLE_NUM_CHARS = "23456789";
public static final String FINALIZATION_CODE_POSSIBLE_CHARS = "123456789";
public static final int FINALIZATION_CODE_LENGTH = 6;
private final SecureRandom secureRandom;
private final Set<String> generatedCardNumbers = new HashSet<String>();
public RandomValuesUtilities() {
secureRandom = SecureRandomFactory.createPRNG();
if(LOG.isDebugEnabled()) {
LOG.debug("initialized RandomValuesUtilities");
}
}
public static RandomValuesUtilities createRandomValuesUtilities() {
return new RandomValuesUtilities();
}
/**
* Generates a random String enforcing the card number format.
* <p>As a further refinement, the result is guaranteed not to match a card number generated with this instance of {@link RandomValuesUtilities}</p>
*
* @param length Length of the returned String
* @param votaPrefix The prefix of the cards generated by the VOTA system (non evoting cards) that should never
* be present at the beginning of the generated evoting card numbers.
* @return An evoting card number guaranteed to not clash with any non evoting card generated by VOTA.
*/
public String generateUniqueCardNumber(int length, int votaPrefix) {
String cardNumber = generateCardNumber(length);
while (cardNumber.startsWith(String.valueOf(votaPrefix)) || generatedCardNumbers.contains(cardNumber)) {
cardNumber = generateCardNumber(length);
}
generatedCardNumbers.add(cardNumber);
return cardNumber;
}
private String generateCardNumber(int length) {
List<Integer> digits = new ArrayList<Integer>();
for (int i = 0; i < length; i++) {
// Valid digits are 1-9
digits.add(secureRandom.nextInt(9) + 1);
}
return Joiner.on("").join(digits);
}
/**
* Generates a random confirmation code
* @return a valid confirmation code of the form "A3B6"
*/
public String generateConfirmationCode() {
return generateAlternateAlphaNumCode(4);
}
/**
* Generates a random finalization code
* @return a valid finalization code of the form "123456"
*/
public String generateFinalizationCode() {
return generateRandomString(FINALIZATION_CODE_POSSIBLE_CHARS, FINALIZATION_CODE_LENGTH);
}
/**
* Generates a random verification code
* @return a valid verification code of the form "A3B6"
*/
public String generateVerificationCode() {
return generateAlternateAlphaNumCode(4);
}
/**
* Generates a random alpha-numeric code, by alternating alpha characters with numeric characters
* @param length the length of the code to be generated
* @return <i>e.g. A2B6</i>
*/
private String generateAlternateAlphaNumCode(int length) {
StringBuilder builder = new StringBuilder(length);
for (int i = 0; i < length; i++) {
if (i % 2 == 0) {
int nextAlphaIndex = secureRandom.nextInt(CODE_POSSIBLE_ALPHA_CHARS.length());
builder.append(CODE_POSSIBLE_ALPHA_CHARS.charAt(nextAlphaIndex));
} else {
// ==>> i % 2 == 1
int nextNumIndex = secureRandom.nextInt(CODE_POSSIBLE_NUM_CHARS.length());
builder.append(CODE_POSSIBLE_NUM_CHARS.charAt(nextNumIndex));
}
}
return builder.toString();
}
/**
* Generates a random String enforcing the transaction secret code format.
*
* @param length Length length of the returned String
* @return a random transaction secret
*/
public String generateTransactionSecret(final int length) {
return generateRandomString(TRANSACTION_SECRET_POSSIBLE_CHARS, length);
}
/**
* Generates a random session id
* @return a random session id
*/
public String generateSessionId() {
return generateRandomString(SESSION_ID_POSSIBLE_CHARS, SESSION_ID_LENGTH);
}
/**
* @param dictionary the dictionary to pick characters from
* @param length the length of the String needed
* @return a random String of length <tt>length</tt>, with characters taken uniformly from <tt>dictionary</tt>
*/
public String generateRandomString(String dictionary, int length) {
List<Character> chars = new ArrayList<Character>();
for (int i = 0; i < length; i++) {
int nextCharIndex = secureRandom.nextInt(dictionary.length());
chars.add(dictionary.charAt(nextCharIndex));
}
return Joiner.on("").join(chars);
}
}