Security-hardened TOTP for Java
Enterprise-grade, RFC 6238 compliant Time-based One-Time Password (TOTP) library for Java. Zero required dependencies. Constant-time verification. Replay attack prevention.
- 🔒 Security First: Constant-time verification, replay attack prevention, secure memory handling
- 📋 RFC Compliant: Full RFC 6238 (TOTP) and RFC 4226 (HOTP) compliance
- 🚀 Zero Dependencies: Core functionality requires no external libraries
- ⚡ High Performance: Thread-safe, stateless design for concurrent use
- 📱 App Compatible: Works with Google Authenticator, Microsoft Authenticator, and others
- 🧪 Well Tested: Comprehensive test suite with RFC test vectors
Maven:
<dependency>
<groupId>io.github.pratiyush</groupId>
<artifactId>totp-lib</artifactId>
<version>1.0.3</version>
</dependency>Gradle (Kotlin DSL):
implementation("io.github.pratiyush:totp-lib:1.0.3")Gradle (Groovy):
implementation 'io.github.pratiyush:totp-lib:1.0.3'import io.github.pratiyush.totp.Algorithm;
import io.github.pratiyush.totp.SecretGenerator;
import io.github.pratiyush.totp.TOTP;
// Generate a secret for a new user
String secret = SecretGenerator.generate(Algorithm.SHA256);
// Create TOTP instance
TOTP totp = TOTP.defaultInstance();
// Generate a code (for testing/admin purposes)
String code = totp.generate(secret);
// Verify a code from user input
boolean valid = totp.verify(secret, userProvidedCode);import io.github.pratiyush.totp.TOTP;
import java.time.Duration;
// Create TOTP with replay attack prevention
TOTP totp = TOTP.builder()
.withReplayProtection(Duration.ofMinutes(2))
.build();
// Verify with user ID for per-user tracking
boolean valid = totp.verify(secret, code, userId);import io.github.pratiyush.totp.QRCodeGenerator;
import java.nio.file.Path;
// Generate QR code for authenticator apps
QRCodeGenerator.saveToFile(
secret,
"user@example.com",
"MyApp",
Path.of("qr.png"),
250
);
// Or get as Base64 for embedding in HTML
String base64 = QRCodeGenerator.generateBase64(
secret, "user@example.com", "MyApp", 250);
String html = "<img src='data:image/png;base64," + base64 + "'/>";// SHA-1 (default, widest compatibility)
TOTP totp = TOTP.builder()
.algorithm(Algorithm.SHA1)
.build();
// SHA-256 (recommended for new implementations)
TOTP totp = TOTP.builder()
.algorithm(Algorithm.SHA256)
.build();
// SHA-512 (maximum security)
TOTP totp = TOTP.builder()
.algorithm(Algorithm.SHA512)
.build();TOTPConfig config = TOTPConfig.builder()
.algorithm(Algorithm.SHA256)
.digits(8) // 6-8 digits
.periodSeconds(30) // 15-120 seconds
.allowedDrift(1) // Time window tolerance
.build();
TOTP totp = TOTP.builder()
.config(config)
.build();// Default (Google Authenticator compatible)
TOTPConfig.defaultConfig();
// SHA-256 with standard settings
TOTPConfig.sha256Config();
// High security (SHA-512, 8 digits)
TOTPConfig.highSecurityConfig();All code comparisons use constant-time algorithms to prevent timing attacks:
// Verification time is independent of whether the code is correct
boolean valid = totp.verify(secret, code);Prevent the same code from being used twice:
// In-memory implementation (single instance)
ReplayGuard guard = new InMemoryReplayGuard(Duration.ofMinutes(2));
// For distributed systems, implement ReplayGuard with Redis/database
public class RedisReplayGuard implements ReplayGuard {
// Your implementation
}Secrets are cleared from memory automatically:
// Internal implementation uses SecureBytes
try (SecureBytes secret = SecureBytes.wrap(secretBytes)) {
// Use secret
} // Automatically cleared here| Method | Description |
|---|---|
generate(secret) |
Generate code for current time |
generateAt(secret, instant) |
Generate code for specific time |
verify(secret, code) |
Verify a code |
verify(secret, code, userId) |
Verify with replay protection |
verifyWithDetails(secret, code) |
Verify and get time offset |
getSecondsRemaining() |
Seconds until current code expires |
getCurrentCounter() |
Current TOTP counter value |
| Method | Description |
|---|---|
generate() |
Generate 160-bit secret |
generate(algorithm) |
Generate algorithm-appropriate secret |
generate(lengthBytes) |
Generate custom length secret |
isValid(secret) |
Validate a secret |
| Method | Description |
|---|---|
generateImage(...) |
Generate BufferedImage |
generateBase64(...) |
Generate Base64 PNG |
generateDataUri(...) |
Generate data URI |
saveToFile(...) |
Save to file |
buildOtpauthUri(...) |
Build otpauth:// URI |
| Algorithm | Key Size | Use Case |
|---|---|---|
| SHA-1 | 20 bytes | Legacy compatibility |
| SHA-256 | 32 bytes | Recommended for new apps |
| SHA-512 | 64 bytes | Maximum security requirements |
# Compile and run tests
mvn clean verify
# Generate coverage report
mvn jacoco:report
# Build uber-JAR with all dependencies
mvn package -Pshade- Java 21 or higher
- Maven 3.6+ (for building)
| Dependency | Scope | Purpose |
|---|---|---|
| jSpecify | Required | Null safety annotations |
| ZXing (com.google.zxing) | Optional | QR code generation |
| SLF4J | Optional | Logging |
This library uses jSpecify annotations for null safety:
@NullMarkedat package level (all types non-null by default)@Nullableto mark nullable parameters and return types
Compatible with tools like NullAway, Error Prone, and IntelliJ IDEA.
We welcome contributions! Please see CONTRIBUTING.md for guidelines and coding standards.
This project follows the Contributor Covenant code of conduct.
MIT License - see LICENSE for details.