Using sam stevens totp - can't get it to work

1.3k Views Asked by At

I found a project on GitHub to generate and check tokens (TOTP). I tried to get it working but failed. Here is the code:

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dev.samstevens.totp.code.CodeGenerator;
import dev.samstevens.totp.code.CodeVerifier;
import dev.samstevens.totp.code.DefaultCodeGenerator;
import dev.samstevens.totp.code.DefaultCodeVerifier;
import dev.samstevens.totp.code.HashingAlgorithm;
import dev.samstevens.totp.exceptions.CodeGenerationException;
import dev.samstevens.totp.exceptions.QrGenerationException;
import dev.samstevens.totp.qr.QrData;
import dev.samstevens.totp.qr.QrGenerator;
import dev.samstevens.totp.qr.ZxingPngQrGenerator;
import dev.samstevens.totp.secret.DefaultSecretGenerator;
import dev.samstevens.totp.secret.SecretGenerator;
import dev.samstevens.totp.time.SystemTimeProvider;
import dev.samstevens.totp.time.TimeProvider;
import dev.samstevens.totp.recovery.RecoveryCodeGenerator;

import static dev.samstevens.totp.util.Utils.getDataUriForImage;

/**
 * Servlet implementation class TwoFactorAuthentication
 */
public class TwoFactorAuthentication extends HttpServlet {
    private static final long serialVersionUID = 1L;
       
    /**
     * @see HttpServlet#HttpServlet()
     */
    public TwoFactorAuthentication() {
        super();
        // TODO Auto-generated constructor stub
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        response.getWriter().append("Served at: ").append(request.getContextPath());
        
        
        //SecretGenerator secretGenerator = new DefaultSecretGenerator();
        //String secret = secretGenerator.generate();
        String secret = "XAFXRG3TNMLHENVAQTD5ZJOTC2MHTIVE";
        
        QrData data = new QrData.Builder()
                   .label("[email protected]")
                   .secret(secret)
                   .issuer("PORTAL")
                   .algorithm(HashingAlgorithm.SHA256) // More on this below
                   .digits(6)
                   .period(60)
                   .build();
        
        String code = request.getQueryString().replace("code=", "");
        response.getWriter().append("\r\nCode: " + code);//.append(request.getContextPath());
        
        TimeProvider timeProvider = new SystemTimeProvider();
        CodeGenerator codeGenerator = new DefaultCodeGenerator(HashingAlgorithm.SHA256);
        DefaultCodeVerifier verifier = new DefaultCodeVerifier(codeGenerator, timeProvider);
        verifier.setTimePeriod(60);
        verifier.setAllowedTimePeriodDiscrepancy(2);

        // secret = the shared secret for the user
        // code = the code submitted by the user
        boolean successful = verifier.isValidCode(secret, code);
        if (successful) System.out.println(successful);
        response.getWriter().append("\r\nResult: " + successful);//.append(request.getContextPath());
        
        try {
            QrGenerator generator = new ZxingPngQrGenerator();
            byte[] imageData = generator.generate(data);
            String mimeType = generator.getImageMimeType();
            String dataUri = getDataUriForImage(imageData, mimeType);
            response.getWriter().append("\r\ndataUri: " + dataUri);//.append(request.getContextPath());
        } catch (QrGenerationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
    }

    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        doGet(request, response);
    }

}

I generated the QR code using the project as you can see above, and scanned it to an authenticator app to generate tokens for me. Whatever code I give the form, the authenticator app fails. Can anyone explain to me what am I doing wrong here?

2

There are 2 best solutions below

2
On

I ran into the same issue with Google Authenticator app recently. It turns out, some authenticator apps silently ignore the period and digits parameters from the URI. In case of Google Authenticator, it simply defaults to 30 and 6, respectively.

This causes code verification to fail, as the app generating the code and the verifying utility work with different parameters.

0
On

Take into account that the codeVerifier.isValidCode method receives the secret as the first parameter and the otp code as the second parameter, in my case it was doing the inverse.

My code:

public UserDto validateOtp(Long userId, LoginDto loginDto) {
        var user = findUserIfExists(userId);
        var isValidOtp = codeVerifier.isValidCode(user.getSecret2FA(),loginDto.getOtp().toString());
        if (!isValidOtp) {
            throw new UnauthorizedException("Invalid Otp");
        }

        return converterObject(user, UserDto.class);
    }