Java tutorial
/******************************************************************************* * Copyright (c) 2013, 2014 Lectorius, Inc. * Authors: * Vijay Pandurangan (vijayp@mitro.co) * Evan Jones (ej@mitro.co) * Adam Hilss (ahilss@mitro.co) * * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * You can contact the authors at inbound@mitro.co. *******************************************************************************/ package co.mitro.twofactor; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.sql.SQLException; import org.apache.commons.codec.binary.Base64; import co.mitro.core.server.Manager; import co.mitro.core.server.data.DBIdentity; import com.google.common.base.Charsets; public class CryptoForBackupCodes { public static final int NUM_BACKUP_CODES = 10; public static final int BACKUP_CODE_LENGTH = 9; // TODO: compute MAX_BACKUP from BACKUP_CODE_LENGTH public static final int MAX_BACKUP = 1000000000; private static final int SALT_LENGTH = 8; //the web site recommended 8 byte salt or more private static final SecureRandom randomGenerator = new SecureRandom(); //random salt generation public static byte[] randSaltGen() { byte[] salt = new byte[SALT_LENGTH]; randomGenerator.nextBytes(salt); return salt; } //appending 2 byte[]. when putting salt and code together, put salt first. When putting undigested salt and digest together, put salt first. public static byte[] appendByteArrays(byte[] first, byte[] second) { byte[] combination = new byte[first.length + second.length]; System.arraycopy(first, 0, combination, 0, first.length); System.arraycopy(second, 0, combination, first.length, second.length); return combination; //works. isn't problem } //for when I change the code into byte format before digest public static byte[] stringToByte(String code) { byte[] newCodeInBytes = null; try { newCodeInBytes = code.getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { } return newCodeInBytes; } //digesting the salt+code byte array. This has to be iterated at least 1000 times public static byte[] digestSaltCode(byte[] saltCode) { MessageDigest md; try { md = MessageDigest.getInstance("SHA-256"); } catch (NoSuchAlgorithmException e) { // TODO Auto-generated catch block throw new RuntimeException(e); } md.update(saltCode); return md.digest(); } //for saving into database public static String digestToStringEncode(byte[] saltCode) { String toStore = new String(Base64.encodeBase64(saltCode), Charsets.UTF_8); //assert toBytes = Base64.decode(saltCode.getBytes()) is toStore return toStore; } //for decoding what's stored in the database to be compared to digested code user puts in public static byte[] encodedStringToBytes(String saltCode) { byte[] toBytes = null; byte[] bytes = null; try { bytes = saltCode.getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } toBytes = Base64.decodeBase64(bytes); return toBytes; } //retrieve the salt from the database digest (after it has been converted back into a byte[] of course, using encodedStringToBytes above) public static byte[] getSaltFromDigest(byte[] digest) { //only method that isn't quite on point. when works, can take out the randSalt static final ini TempVerify byte[] salt = new byte[SALT_LENGTH]; for (int i = 0; i < SALT_LENGTH; i++) salt[i] = digest[i]; return salt; } // Checks if codeString is a valid backup code and, if so, resets the backup code so it cannot be reused. // Returns true if a backup code was used and false otherwise. // WARNING: does not commit the reset to the db. It is the caller's responsibility to commit. // TODO: Commit this update in a separate transaction. public static boolean tryBackupCode(String secret, String codeString, DBIdentity identity, Manager manager) throws SQLException { if (codeString.length() == CryptoForBackupCodes.BACKUP_CODE_LENGTH) { for (int i = 0; i < CryptoForBackupCodes.NUM_BACKUP_CODES; i++) { String backupCode = identity.getBackup(i); if (backupCode != null) { byte[] salt = CryptoForBackupCodes .getSaltFromDigest(CryptoForBackupCodes.encodedStringToBytes(backupCode)); String digestedCode = CryptoForBackupCodes.digest(codeString, salt); if (digestedCode.equals(backupCode)) { identity.setBackup(i, null); manager.identityDao.update(identity); return true; } } } } return false; } public static String digest(String backupCode, byte[] salt) { byte[] backupCodeBytes = CryptoForBackupCodes.stringToByte(backupCode); byte[] appendedSaltByte = CryptoForBackupCodes.appendByteArrays(salt, backupCodeBytes); byte[] digestedSaltCode = appendedSaltByte; for (int i = 0; i < 1000; i++) { digestedSaltCode = CryptoForBackupCodes.digestSaltCode(digestedSaltCode); } byte[] undigestedSaltAndDigested = CryptoForBackupCodes.appendByteArrays(salt, digestedSaltCode); String digestedTogether = CryptoForBackupCodes.digestToStringEncode(undigestedSaltAndDigested); return digestedTogether; } public static String generateBackupCode() { return String.format("%0" + BACKUP_CODE_LENGTH + "d", randomGenerator.nextInt(MAX_BACKUP)); } public static String[] generateBackupCodesForUser(DBIdentity identity) { String[] backups = new String[NUM_BACKUP_CODES]; String[] digestedBackups = new String[NUM_BACKUP_CODES]; for (int i = 0; i < backups.length; i++) { backups[i] = generateBackupCode(); byte[] randSalt = CryptoForBackupCodes.randSaltGen(); digestedBackups[i] = CryptoForBackupCodes.digest(backups[i], randSalt); identity.setBackup(i, digestedBackups[i]); } return backups; } }