All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.mangoo.utils.totp.TotpUtils Maven / Gradle / Ivy

The newest version!
package io.mangoo.utils.totp;

import io.mangoo.constants.Hmac;
import io.mangoo.constants.NotNull;
import io.mangoo.utils.CodecUtils;
import net.glxn.qrgen.QRCode;
import org.apache.commons.codec.binary.Base32;
import org.apache.commons.lang3.RegExUtils;

import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Objects;
import java.util.Random;
import java.util.concurrent.TimeUnit;

public class TotpUtils {
    private static final Base32 base32 = new Base32();
    private static final Random random = new SecureRandom();
    private static final String ALGORITHM = Hmac.SHA512;
    private static final int DIGITS = 6;
    private static final int MAX_CHARACTERS = 32;
    private static final int PERIOD = 30;
    private static final int ITERATIONS = 26;
    private static final int BYTES_SECRET = 64;
    
    private TotpUtils() {
    }
    
    /**
     * Generates a 64 byte (512 bit) secret
     * 
     * @return A 64 characters random string based on SecureRandom
     */
    public static String createSecret() {
        var buffer = new StringBuilder(BYTES_SECRET);
        for (var i = 0; i < BYTES_SECRET; i++) {
            var value = random.nextInt(MAX_CHARACTERS);
            if (value < ITERATIONS) {
                buffer.append((char) ('A' + value));
            } else {
                buffer.append((char) ('2' + (value - ITERATIONS)));
            }
        }
        
        return buffer.toString();
    }
    
    /**
     * Creates the current TOTP based on the following default values:
     * SHA512 algorithm, 6 digits, 30 seconds time period
     * 
     * @param secret The secret to use
     * 
     * @return The totp value or null if generation failed
     */
    public static String getTotp(String secret) {
        Objects.requireNonNull(secret, NotNull.SECRET);
        
        Totp builder = Totp.key(secret.getBytes(StandardCharsets.US_ASCII))
                .timeStep(TimeUnit.SECONDS.toMillis(PERIOD))
                .digits(DIGITS)
                .hmacSha(ALGORITHM)
                .build();

        return builder.value();
    }
    
    /**
     * Creates the current TOTP based on the given parameters
     * 
     * @param secret The secret to use
     * @param algorithm The algorithm to use
     * @param digits The digits to use (6 or 8)
     * @param period The time period in seconds
     * 
     * @return The totp value or null if generation failed
     */
    public static String getTotp(String secret, String algorithm, int digits, int period) {
        Objects.requireNonNull(secret, NotNull.SECRET);
        Objects.requireNonNull(algorithm, NotNull.ALGORITHM);
        
        Totp builder = Totp.key(secret.getBytes(StandardCharsets.US_ASCII))
                .timeStep(TimeUnit.SECONDS.toMillis(period))
                .digits(digits)
                .hmacSha(algorithm)
                .build();


        return builder.value();
    }
    
    /**
     * Verifies a given TOTP based on the following default values:
     * SHA512 algorithm, 6 digits, 30 seconds time period
     * 
     * @param secret The secret to use
     * @param totp The TOTP to verify
     * 
     * @return True if the TOTP is valid, false otherwise
     */
    public static boolean verifiedTotp(String secret, String totp) {
        Objects.requireNonNull(secret, NotNull.SECRET);
        Objects.requireNonNull(totp, NotNull.TOTP);
        
        Totp builder = Totp.key(secret.getBytes(StandardCharsets.US_ASCII))
            .timeStep(TimeUnit.SECONDS.toMillis(PERIOD))
            .digits(DIGITS)
            .hmacSha(ALGORITHM)
            .build();

        return totp.equals(builder.value());
    }

    /**
     * Generates a QR code image as a base64 PNG
     *
     * @param name The name of the account
     * @param issuer The name of the issuer
     * @param secret The secret to use
     * @param algorithm The algorithm to use
     * @param digits The number of digits to use
     * @param period The period to use
     *
     * @return The QR code as a base64 PNG image
     */
    public static String getQRCode(String name, String issuer, String secret, String algorithm, String digits, String period) {
        Objects.requireNonNull(name, NotNull.NAME);
        Objects.requireNonNull(issuer, NotNull.ISSUER);
        Objects.requireNonNull(secret, NotNull.SECRET);
        Objects.requireNonNull(algorithm, NotNull.ALGORITHM);
        Objects.requireNonNull(digits, NotNull.DIGITS);
        Objects.requireNonNull(period, NotNull.PERIOD);

        String text = getOtpauthURL(name, issuer, secret, algorithm, digits, period);
        ByteArrayOutputStream qrCodeOutputStream = QRCode.from(text)
                .withSize(250, 250)
                .stream();

        // Convert byte array output stream to a byte array
        byte[] qrCodeBytes = qrCodeOutputStream.toByteArray();

        return new String(CodecUtils.encodeToBase64(qrCodeBytes), StandardCharsets.UTF_8);
    }

    /**
     * Generates an otpauth code to share a secret with a user
     * 
     * @param name The name of the account
     * @param issuer The name of the issuer
     * @param secret The secret to use
     * @param algorithm The algorithm to use
     * @param digits The number of digits to use
     * @param period The period to use
     * 
     * @return An otpauth url
     */
    public static String getOtpauthURL(String name, String issuer, String secret, String algorithm, String digits, String period) {
        Objects.requireNonNull(name, NotNull.ACCOUNT_NAME);
        Objects.requireNonNull(secret, NotNull.SECRET);
        Objects.requireNonNull(issuer, NotNull.ISSUER);
        Objects.requireNonNull(algorithm, NotNull.ALGORITHM);
        Objects.requireNonNull(digits, NotNull.DIGITS);
        Objects.requireNonNull(period, NotNull.PERIOD);
        
        var buffer = new StringBuilder();
        buffer.append("otpauth://totp/")
            .append(name)
            .append("?secret=")
            .append(RegExUtils.replaceAll(base32.encodeAsString(secret.getBytes(StandardCharsets.UTF_8)), "=", ""))
            .append("&algorithm=")
            .append(algorithm)
            .append("&issuer=")
            .append(issuer)
            .append("&digits=")
            .append(digits)
            .append("&period=")
            .append(period);

        return buffer.toString();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy