Implementing TOTP Using Google Authenticator in Java

Posted By : Ekesh Bahuguna | 29-Jun-2018

Google authenticator is used to implement two-factor verification using TOTP( Time-based One-time Password Algorithm) and HOTP (hash-based message authentication code). Authenticator provides six-eight digit code to authenticate use. Google authenticator works on the principle of shared secret key. Otp have a short validity period of typically 30 or 60 seconds


Dependencies: Add following maven dependecies to enable google authenticator

<!-- https://mvnrepository.com/artifact/com.warrenstrange/googleauth -->
		<dependency>
			<groupId>com.warrenstrange</groupId>
			<artifactId>googleauth</artifactId>
			<version>1.1.5</version>
		</dependency>


		<!-- https://mvnrepository.com/artifact/com.google.zxing/javase -->
		<dependency>
			<groupId>com.google.zxing</groupId>
			<artifactId>javase</artifactId>
			<version>3.3.3</version>
		</dependency>

Steps to implement Google Authenticator:

1- Generating google auth key

final GoogleAuthenticator gAuth = new GoogleAuthenticator();	
final GoogleAuthenticatorKey googleAuthkey = gAuth.createCredentials();
String key = googleAuthkey.getKey();

2- Generating key URI

URI uri = new URI("otpauth", "totp", "/" + issuer + ":" + account, "secret=" + secret + "&issuer=" + issuer,null);
uri.toASCIIString();

3- Genrating Qr code

public Map<String, String> qrCodeGeneration(Users user) throws URISyntaxException, WriterException, IOException {
		String key = getTwoFactorKey(user);
		String filePath = qrCodeLocation + File.separator + user.getId() + ".png";
		String charset = "UTF-8"; // or "ISO-8859-1"
		Map<EncodeHintType, ErrorCorrectionLevel> hintMap = new EnumMap<>(EncodeHintType.class);
		hintMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);
		String keyUri = generateKeyUri("test.com", user.getEmailId(), key);
		String qrCodeData = keyUri;
		String base64Image = createQRCode(qrCodeData, filePath, charset, hintMap, 200, 200);
		Map<String, String> map = new HashMap<>();
		map.put("fileName", user.getId() + ".png");
		map.put("key", key);
		map.put("base64Image", base64Image);
		map.put("verificationToken", verificationToken.getToken());
		return map;
	}

//Configure the Google Authenticator App by scanning the following QR code image
private static String createQRCode(String qrCodeData, String filePath, String charset,
			@SuppressWarnings("rawtypes") Map hintMap, int qrCodeheight, int qrCodewidth)
			throws WriterException, IOException {
		@SuppressWarnings("unchecked")
		BitMatrix matrix = new MultiFormatWriter().encode(new String(qrCodeData.getBytes(charset), charset),
				BarcodeFormat.QR_CODE, qrCodewidth, qrCodeheight, hintMap);
		File file = new File(filePath);
		MatrixToImageWriter.writeToPath(matrix, filePath.substring(filePath.lastIndexOf('.') + 1), file.toPath());
		Encoder encoder = Base64.getEncoder();
		String base64Image = encoder.encodeToString(Files.readAllBytes(file.toPath()));
		// using PosixFilePermission to set file permissions 777
		Set<PosixFilePermission> perms = new HashSet<>();
		// add owners permission
		perms.add(PosixFilePermission.OWNER_READ);
		perms.add(PosixFilePermission.OWNER_WRITE);
		// add group permissions
		perms.add(PosixFilePermission.GROUP_READ);
		perms.add(PosixFilePermission.GROUP_WRITE);
		// add others permissions
		perms.add(PosixFilePermission.OTHERS_READ);
		Files.setPosixFilePermissions(Paths.get(file.toString()), perms);
		logger.info("2FA QR code generated");
		return "data:image/png;base64," + base64Image;
}

4- perform authentication

//To check whether generated google auth key is authenticated or not
public boolean performAuthentication(String value, Users user) {
        Integer totp = Integer.valueOf((value.equals("") ? "-1" : value));
        boolean unused = isUnusedPassword(totp, windowSize.get());
        boolean matches = gAuth.authorize(user.getGoogle2FaAuthKey(), totp);
        return (unused && matches);
}

private long KEY_VALIDATION_INTERVAL_MS = TimeUnit.SECONDS.toMillis(30);

private boolean isUnusedPassword(int password, int windowSize) {
        long now = new Date().getTime();
        long timeslotNow = now / KEY_VALIDATION_INTERVAL_MS;
        int forwardTimeslots = ((windowSize - 1) / 2);
        long timeslotThen = lastVerifiedTime / KEY_VALIDATION_INTERVAL_MS;
        if (password != lastUsedPassword || timeslotNow > timeslotThen + forwardTimeslots) {
            lastUsedPassword = password;
            lastVerifiedTime = now;
            return true;
        }
        return false;
}

Advantages:
Using google 2fa, if anyone knows username and password of the user that is not sufficient to break the security. The attacker also requires the secret key or the device on which google authenticator app is running.

Note: 
In google authenticator, otp is based on a mixture of the secret key and the current time. Sometime it may happen that internal clocks can desync between device and service, which results in invalid otp

Disadvantages:
If anyone gets access to your secret key then, they can generate their own valid codes. If service doesn't prevent the specific login attempt, hackers may still be able to compromise your account through brute force.

About Author

Author Image
Ekesh Bahuguna

Ekesh is Java Developer. Along with that he is good in linux, c, networking and competitive programming. He love to answer over Quora.

Request for Proposal

Name is required

Comment is required

Sending message..