A java library to help generate and verify time-based one time passwords for Multi-Factor Authentication.
Generates QR codes that are recognisable by applications like Google Authenticator, and verify the one time passwords they produce.
Inspired by PHP library for Two Factor Authentication, a similar library for PHP.
This is a fork for HangarMC's needs, mostly just updating dependencies.
- Java 17+
The quickest way to start using this library in a Spring Boot project is to require the TOTP Spring Boot Starter. See Using Java-TOTP with Spring Boot for more information, or read on to learn about the library.
To add this library to your java project using Maven, add the following dependency:
<dependency>
<groupId>dev.samstevens.totp</groupId>
<artifactId>totp-hangar</artifactId>
<version>1.7.2-SNAPSHOT</version>
</dependency>
To add the dependency using Gradle, add the following to the build script:
dependencies {
compile 'dev.samstevens.totp:totp-hangar:1.7.2-SNAPSHOT'
}
- Generating secrets
- Generating QR codes
- Verifying one time passwords
- Using different time providers
- Recovery codes
To generate a secret, use the dev.samstevens.totp.secret.DefaultSecretGenerator
class.
SecretGenerator secretGenerator = new DefaultSecretGenerator();
String secret = secretGenerator.generate();
// secret = "BP26TDZUZ5SVPZJRIHCAUVREO5EWMHHV"
By default, this class generates secrets that are 32 characters long, but this number is configurable via
the class constructor.
// Generates secrets that are 64 characters long
SecretGenerator secretGenerator = new DefaultSecretGenerator(64);
Once a shared secret has been generated, this must be given to the user so they can add it to an MFA application, such as Google Authenticator. Whilst they could just enter the secret manually, a much better and more common option is to generate a QR code containing the secret (and other information), which can then be scanned by the application.
To generate such a QR code, first create a dev.samstevens.totp.qr.QrData
instance with the relevant information.
QrData data = new QrData.Builder()
.label("[email protected]")
.secret(secret)
.issuer("AppName")
.algorithm(HashingAlgorithm.SHA1) // More on this below
.digits(6)
.period(30)
.build();
Once you have a QrData
object holding the relevant details, a PNG image of the code can be generated using the dev.samstevens.totp.qr.ZxingPngQrGenerator
class.
QrGenerator generator = new ZxingPngQrGenerator();
byte[] imageData = generator.generate(data)
The generate
method returns a byte array of the raw image data. The mime type of the data that is generated by the generator can be retrieved using the getImageMimeType
method.
String mimeType = generator.getImageMimeType();
// mimeType = "image/png"
The image data can then be outputted to the browser, or saved to a temporary file to show it to the user.
To avoid the QR code image having to be saved to disk, or passing the shared secret to another endpoint that generates and returns the image, it can be encoded in a Data URI, and embedded directly in the HTML served to the user.
import static dev.samstevens.totp.util.Utils.getDataUriForImage;
...
String dataUri = getDataUriForImage(imageData, mimeType);
// dataUri = data:image/png;base64,iVBORw0KGgoAAAANSU...
The QR code image can now be embedded directly in HTML via the data URI. Below is an example using Thymeleaf:
<img th:src="${dataUri}" />
After a user sets up their MFA, it's a good idea to get them to enter two of the codes generated by their app to verify the setup was successful. To verify a code submitted by the user, do the following:
TimeProvider timeProvider = new SystemTimeProvider();
CodeGenerator codeGenerator = new DefaultCodeGenerator();
CodeVerifier verifier = new DefaultCodeVerifier(codeGenerator, timeProvider);
// secret = the shared secret for the user
// code = the code submitted by the user
boolean successful = verifier.isValidCode(secret, code)
This same process is used when verifying the submitted code every time the user needs to in the future.
By default, the DefaultCodeGenerator
uses the SHA1 algorithm to generate/verify codes, but SHA256 and SHA512 are also supported. To use a different algorithm, pass in the desired HashingAlgorithm
into the constructor:
CodeGenerator codeGenerator = new DefaultCodeGenerator(HashingAlgorithm.SHA512);
When verifying a given code, you must use the same hashing algorithm that was specified when the QR code was generated for the secret, otherwise the user submitted codes will not match.
The one time password codes generated in the authenticator apps only last for a certain time period before they are re-generated, and most implementations of TOTP allow room for codes that have recently expired, or will only "become valid" soon in the future to be accepted as valid, to allow for a small time drift between the server and the authenticator app (discrepancy).
By default on a DefaultCodeVerifier
the time period is set to the standard 30 seconds, and the discrepancy to 1, to allow a time drift of +/-30 seconds. These values can be changed by calling the appropriate setters:
DefaultCodeVerifier verifier = new DefaultCodeVerifier(codeGenerator, timeProvider);
// sets the time period for codes to be valid for to 60 seconds
verifier.setTimePeriod(60);
// allow codes valid for 2 time periods before/after to pass as valid
verifier.setAllowedTimePeriodDiscrepancy(2);
Like the hashing algorithm, the time period must be the same as the one specified when the QR code for the secret was created.
Most TOTP implementations generate codes that are 6 digits long, but codes can have a length of any positive non-zero integer. The default number of digits in a code generated by a DefaultCodeGenerator
instance is 6, but can be set to a different value by passing the number as the second parameter in the constructor:
CodeGenerator codeGenerator = new DefaultCodeGenerator(HashingAlgorithm.SHA1, 4);
The above generator will generate codes of 4 digits, using the SHA1 algorithm.
Once again, the number of digits must be the same as what was specified when the QR code for the secret was created.
When verifying user submitted codes with a DefaultCodeVerifier
, a TimeProvider
is needed to get the current time (unix) time. In the example code above, a SystemTimeProvider
is used, but this is not the only option.
Most applications should be able to use the SystemTimeProvider
class to provide the time, which gets the time from the system clock. If the system clock is reliable, it is reccomended that this provider is used.
If the system clock cannot be used to accurately get the current time, then you can fetch it from an NTP server with the dev.samstevens.totp.time.NtpTimeProvider
class, passing in the NTP server hostname you wish you use.
TimeProvider timeProvider = new NtpTimeProvider("pool.ntp.org");
The default timeout for the requests to the NTP server is 3 seconds, but this can be set by passing in the desired number of milliseconds as the second parameter in the constructor:
TimeProvider timeProvider = new NtpTimeProvider("pool.ntp.org", 5000);
Using this time provider requires that the Apache Commons Net library is available on the classpath. Add the dependency to your project with Maven/Gradle like this:
Maven:
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.6</version>
</dependency>
Gradle:
dependencies {
compile 'commons-net:commons-net:3.6'
}
Recovery codes can be used to allow users to gain access to their MFA protected account without providing a TOTP, bypassing the MFA process. This is usually given as an option to the user so that in the event of losing access to the device which they have registered the MFA secret with, they are still able to log in.
Usually, upon registering an account for MFA, several one-time use codes will be generated and presented to the user, with instructions to keep them very safe. When the user is presented with the prompt for a TOTP in the future, they can opt to enter one of the recovery codes instead to gain access to their account.
Most of the logic needed for implementing recovery codes (storage, associating them with a user, checking for existance, etc) is implementation specific, but the codes themselves can be generated via this library.
The default implementation provided in this library generates recovery codes :
- of 16 characters
- composed of numbers and lower case characters from latin alphabet (for a total of 36 possible characters)
- split in groups separated with dash for better readability
Thoses settings guarantees recovery codes security (with an entropy of 82 bits) while keeping codes simple to read and enter by end user when needed.
import dev.samstevens.totp.recovery.RecoveryCodeGenerator;
...
// Generate 16 random recovery codes
RecoveryCodeGenerator recoveryCodes = new RecoveryCodeGenerator();
String[] codes = recoveryCodes.generateCodes(16);
// codes = ["tf8i-exmo-3lcb-slkm", "boyv-yq75-z99k-r308", "w045-mq6w-mg1i-q12o", ...]
To run the tests for the library with Maven, run mvn test
.
This project is licensed under the MIT license.