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); } }