co.mitro.twofactor.CryptoForBackupCodes.java Source code

Java tutorial

Introduction

Here is the source code for co.mitro.twofactor.CryptoForBackupCodes.java

Source

/*******************************************************************************
 * 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;
    }
}