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.
--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
--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.
--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;
}
}
--Generate a private key and create a QR code at the same time (via Google)
--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.
--After scanning, enter the verification code (one-time password every 30 seconds) registered from the app to confirm.
--Success case
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);
}
}
--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.