Two-step verification JAVA realization (TOTP)

1.First of all

I want to realize two-step verification of login in order to strengthen security measures by exposing a certain in-house system to the outside. As an IT engineer, I will consider how to achieve two-step verification.

2. Flow of using two-step verification

--When you use the two-step verification function for each site, a message will be displayed asking you to scan the QR code and then register the verification app in your account. --There are various authentication apps, such as Google Authenticator, Authy, Duo Mobile, 1Password (time-based one-time password (TOTP) authentication app). --After scanning the QR code, the authentication app will generate a password every 30 seconds
image.png --Other than the normal login password, use the corresponding password as a two-step verification code.

3.TOTP "Time-based One-Time Password" is abbreviated as TOTP because the one-time password is calculated according to the time.

image.png

4. JAVA realized

4-1. Example of generating a one-time password for two-step verification

--Time window size --Generation of private key --Authentication code check

Google Authenticator OpenSource TOTP: Time-Based One-Time Password Algorithm

AuthenticatorDemo.java


import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base32;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;

/**
 *Google Authenticator TOTP generator class
 *
 * @see <a href="https://github.com/google/google-authenticator">Google Authenticator OpenSource</a>
 * @see <a href="https://tools.ietf.org/html/rfc6238">TOTP</a>
 */
public class AuthenticatorDemo {

    // taken from Google pam docs - we probably don't need to mess with these
    public static final int SECRET_SIZE = 10;

    //Random number generator seed
    public static final String SEED = "g8GjEvTbW5oVSV7avLBdwIHqGlUYNzKFI7izOF8GwLDVKs2m0QN7vxRs2im5MDaNCWGmcD2rvcZx";

    //Random number generation algorithm
    public static final String RANDOM_NUMBER_ALGORITHM = "SHA1PRNG";

    //Default value for time window size
    private static int window_size = 3; // default 3 - max 17 (from google docs)

    private AuthenticatorDemo() {
    }

    /**
     *Set the size of the time window.
     *This is an integer value that represents the number of windows allowed for 30 seconds.
     *The larger the size, the higher the tolerance for clock skew.
     *This also increases the chances of being attacked.
     *
     * <p>Size range 1-17</p>
     *
     * @param size time window
     */
    public static void setWindowSize(int size) {
        if (size >= 1 && size <= 17)
            window_size = size;
    }

    /**
     *Generate a random private key.
     *It is stored by the server and associated with the user account,
     *You need to verify the code displayed by the Google authentication system.
     *The user must register this private key on the device.
     *
     * @return private key
     */
    public static String generateSecretKey() {
        SecureRandom sr;
        try {
            //Random number generation
            sr = SecureRandom.getInstance(RANDOM_NUMBER_ALGORITHM);
            sr.setSeed(Base64.decodeBase64(SEED));
            byte[] buffer = sr.generateSeed(SECRET_SIZE);

            //Convert the random number to Base32 and use it as the private key.
            Base32 codec = new Base32();
            byte[] bEncodedKey = codec.encode(buffer);
            return new String(bEncodedKey);
        } catch (NoSuchAlgorithmException e) {
            System.err.println("Random number generation error");
        }
        return null;
    }

    /**
     *Authentication code input check
     *
     * @param secret private key
     * @param code Authentication code
     * @return true:Success; false:Failure
     */
    public static boolean checkCode(String secret, String code) {
        //Private key
        Base32 codec = new Base32();
        byte[] decodedKey = codec.decode(secret);
        //Convert UNIX milliseconds time to a 30-second "time window"
        //It complies with the TOTP specification (see RFC 6238 for details).
        long timeMsec = System.currentTimeMillis();
        long timeNo = (timeMsec / 1000L) / 30L;
        //The Time Window is used to check previously generated authorization codes.
        // window_You can use size to adjust how far you go.
        for (int i = -window_size; i <= window_size; ++i) {
            long hash;
            try {
                hash = verifyCode(decodedKey, timeNo + i);
            } catch (Exception e) {
                // Yes, this is bad form - but
                // the exceptions thrown would be rare and a static configuration problem
                e.printStackTrace();
                throw new RuntimeException(e.getMessage());
                //return false;
            }

            String hashStr = StringUtils.leftPad(String.valueOf(hash), 6, '0');
            if (code.equals(hashStr)) {
                return true;
            }
        }
        // The validation code is invalid.
        return false;
    }

    /**
     *TOTP verification algorithm
     * <p>TOTP = HMAC-SHA-1(K, (T - T0) / X)</p>
     *
     * @param key private key
     * @param timeNo time window number
     * @return authorization code
     * @throws NoSuchAlgorithmException Cryptographic exception
     * @throws InvalidKeyException Key exception
     */
    private static int verifyCode(byte[] key, long timeNo) throws NoSuchAlgorithmException, InvalidKeyException {
        byte[] data = new byte[8];
        long value = timeNo;
        for (int i = 8; i-- > 0; value >>>= 8) {
            data[i] = (byte) value;
        }
        SecretKeySpec signKey = new SecretKeySpec(key, "HmacSHA1");
        Mac mac = Mac.getInstance("HmacSHA1");
        mac.init(signKey);
        byte[] hash = mac.doFinal(data);
        int offset = hash[20 - 1] & 0xF;
        // We're using a long because Java hasn't got unsigned int.
        long truncatedHash = 0;
        for (int i = 0; i < 4; ++i) {
            truncatedHash <<= 8;
            //Hold the first byte
            truncatedHash |= (hash[offset + i] & 0xFF);
        }
        truncatedHash &= 0x7FFFFFFF;
        truncatedHash %= 1000000;
        return (int) truncatedHash;
    }
}

4-2. Test class

--Generate a private key and create a QR code at the same time (via Google)

QR Code Uri Formard

--To see the effect, use selenium to get the QR code image (you can check the QR code URL directly from the browser) --When executed, the screen for scanning the QR code is displayed. image.png

--After scanning, enter the verification code (one-time password every 30 seconds) registered from the app to confirm. image.png

--Success case image.png

AuthDemoTest.java


import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.io.IOException;
import java.net.URL;

import static org.junit.Assert.assertTrue;

/*
 *It's not really a unit test, but it does show how to use it.
 */
public class AuthDemoTest {

    /**
     * Google Chart API
     */
    private final String google = "https://www.google.com/chart?cht=qr&chs=200x200&chld=M|0&chl=";

    /**
     *QR code
     * otpauth://totp/<userId>?secret=<secretKey>&issuer=<applicationName>
     *
     * @see <a href="https://github.com/google/google-authenticator/wiki/Key-Uri-Format">QR codes</a>
     */
    private final String format = google + "otpauth://totp/%s@%s?secret=%s&issuer=AuthDemoTest";

    /**
     *TOTP certification test
     *
     * @throws IOException IO exception
     */
    @Test
    public void authTest() throws IOException {
        //Private key generation
        String secretKey = AuthenticatorDemo.generateSecretKey();
        //QR code URL.
        String url = String.format(format, "testuser", "testhost", secretKey);
        System.out.println("QR code:" + url);

        //QR code image (via Google).
        ImageIcon icon = new ImageIcon(getImg(url));
        String msg = "To confirm the verification code\r\n";
        msg = msg + "With a smartphone app (Google Authenticator, etc.)\r\n";
        msg = msg + "Please scan.";
        JOptionPane.showMessageDialog(null, msg, "QR code", JOptionPane.ERROR_MESSAGE, icon);
        String inputCode = JOptionPane.showInputDialog(null, "Please enter the verification code of the app.");

        //Time window size (1 time for 30 seconds), default value 3 times
        AuthenticatorDemo.setWindowSize(4);
        boolean isSuccess = AuthenticatorDemo.checkCode(secretKey, inputCode);
        assertTrue(isSuccess);
    }

    /**
     *Get img image of URL
     *
     * @param url URL
     * @return image
     * @throws IOException IO exception
     */
    private Image getImg(String url) throws IOException {
        //chrome browser
        System.setProperty("webdriver.chrome.driver", "src/main/resources/chromedriver.exe");
        ChromeOptions options = new ChromeOptions();
        //Hide browser
        options.addArguments("--headless");
        options.addArguments("--disable-gpu");
        WebDriver driver = new ChromeDriver(options);
        //URL access
        driver.get(url);
        //Get src of img tag
        WebElement imageElement = driver.findElement(By.tagName("img"));
        String imagePath = imageElement.getAttribute("src");
        //Close browser
        driver.close();
        //image
        URL imageUrl = new URL(imagePath);
        return ImageIO.read(imageUrl);
    }
}

5. Summary

--Calculate with TOTP, generate a private key, and display it like a QR code --Similarly, scan with the TOTP generation rule app and get a one-time password. --For two-step verification, log in using the corresponding one-time password as the verification code.

Recommended Posts

Two-step verification JAVA realization (TOTP)
Implement two-step verification in Java