In this post I’ll explore how to set up MFA (Multi Factor Authentication) in a user application written in Java. Be it a stand alone java application, or a web portal. And I’ll examine how the Google Authenticator smartphone app can be used by the user to generate an MFA code.
First, I’ll assume that you already have a data back end which stores user’s details, and a scheme to authenticate usernames with password. MFA will augment the already existing user auth scheme, and not replace it, by adding an added layer of protection in addition to the password.
MFA works with a seed key assigned to a user. The seed key is used along with the current timestamp to generate a numeric code. And this is the code that the user must submit, in order for the server to authenticate. So this seed key must be stored, along with the user’s password, in the database. Hence your user data table may look like:
Table USERS:
-USER_ID
-USERNAME
-PASSWORD
-MFA_KEY
-…
To generate the MFA code to store in your USERS table/datastore, you can use the Java code below:
public static class MFACredentials { public String user; public String encodedKey; public String QRBarcodeURL; public MFACredentials(String user, String encodedKey, String QRBarcodeURL) { this.user = user; this.encodedKey = encodedKey; this.QRBarcodeURL = QRBarcodeURL; } } public static MFACredentials generateNewKeyAndGetQRBarcodeURL(String user, String hostname) { int secretSize = 20; byte[] buffer = new byte[secretSize]; new SecureRandom().nextBytes(buffer); Base32 codec = new Base32(); byte[] secretKey = Arrays.copyOf(buffer, secretSize); byte[] bEncodedKey = codec.encode(secretKey); String encodedKey = new String(bEncodedKey); String QRBarcodeURL = getQRBarcodeURL(user, hostname, encodedKey); MFACredentials mfaCredentials = new MFACredentials(user, encodedKey, QRBarcodeURL); return (mfaCredentials); } private static String getQRBarcodeURL(String user, String host, String secret) { "https://www.google.com/chart?chs=200x200&chld=M%%7C0&cht=qr&chl=otp" + "auth://totp/%s@%s%%3Fsecret%%3D%s" return String.format(format, user, host, secret); }
Let’s examine. The generateNewKeyAndGetQRBarcodeURL() method is what generates the MFA key, and returns an instance of the MFACredentials class. The returned values include the username, a URL that can be used to generate a QR barcode that can be scanned into Google Authenticator, and the user’s MFA key. The MFA key should then be stored in MFA_KEY field in your USERS data table. Note that the hostname passed in is not important and you can make it whatever you’d like, at least for the way we use MFA in this example.
The QR barcode URL returned by generateNewKeyAndGetQRBarcodeURL() should be sent to the user, with instructions to visit the URL in a browser on a device, and then scan the QR barcode into the Google Authenticator app on a smartphone.
Next, in your UI you’ll want to add a field for a 6 digit numeric code, call it “MFA code” or whatever you please, in addition to the existing username and password fields.
And then lastly, when the MFA code is passed in to your server side for authentication, you’ll want to use the Java code below to verify it:
public static boolean checkCode(String secret, long code) throws NoSuchAlgorithmException, InvalidKeyException { Base32 codec = new Base32(); byte[] decodedKey = codec.decode(secret); long t = new Date().getTime() / TimeUnit.SECONDS.toMillis(30); if (secret == null || secret.replaceAll("\\s+", "").equals("") || code == 0) return false; // Window is used to check codes generated in the near past. // You can use this value to tune how far you're willing to go. int window = 3; for (int i = -window; i <= window; ++i) { long hash = verifyCode(decodedKey, t + i); if (hash == code) { return true; } } return false; } private static int verifyCode(byte[] key, long t) throws NoSuchAlgorithmException, InvalidKeyException { byte[] data = new byte[8]; long value = t; 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; long truncatedHash = 0; for (int i = 0; i < 4; ++i) { truncatedHash <<= 8; truncatedHash |= (hash[offset + i] & 0xFF); } truncatedHash &= 0x7FFFFFFF; truncatedHash %= 1000000; return (int) truncatedHash; }
The checkCode() method takes the user’s MFA seed key (taken from the MFA_KEY field in the USERS table in our example), and the numeric MFA code that the user passes in. It then authenticates the code against the user’s MFA seed key. You can call the checkCode() method in your server side method that takes the username/password/mfa-code input from the user for authentication. If checkCode() returns true, all is good. If false, that means the MFA code input was incorrect.
And voila, you now have a way to generate an MFA seed key, send a user a URL that generates a QR code that they can use to scan into Google Authenticator, and take the user’s MFA code, to authenticate it. Google Authenticator will generate the MFA code for the user to input when logging in, along with their password.
Disclaimer: The code above was originally not my own, and at this point I’m not sure where I got it from. Though I augmented it a bit, I cannot take credit for it.