bastiaanjansen / otp-java Goto Github PK
View Code? Open in Web Editor NEWA small and easy-to-use one-time password generator library for Java implementing RFC 4226 (HOTP) and RFC 6238 (TOTP).
License: MIT License
A small and easy-to-use one-time password generator library for Java implementing RFC 4226 (HOTP) and RFC 6238 (TOTP).
License: MIT License
If we create a TOTPGenerator with ".withPeriod()" of less than a second (e.g., ".withPeriod(Duration.ofMillis(1))" ), we get an exception:
java.lang.ArithmeticException: / by zero
at com.bastiaanjansen.otp.TOTPGenerator.calculateCounter(TOTPGenerator.java:164)
at com.bastiaanjansen.otp.TOTPGenerator.generate(TOTPGenerator.java:49)
.
.
The reason we are trying this is that, from our testing, it appears that when the TOTPGenerator .generate() method is called, it will generate the same/identical code if the calls are within that Duration.ofSeconds(), i.e., if the the generator is created with .withPeriod(DurationofSeconds(10)), all calls to .generate(), within a 10 second period, return the same code.
We are looking at OTP-Java to generate OTP email codes, so that would mean that if we had requests (from different users) for several OTP email codes within a period of time, then the OTP-Java .generate() method would potentially provide us with the same code for several users.
Am I misinterpreting how this works?
Thanks,
Jim
From wikipedia
Some authenticators allow values that should have been generated before or after the current time in order to account for slight clock skews, network latency and user delays.
Is this possible using your library to allow this flexability?
If so, could you post some code showing how this is possible.
Thanks
Hello
I would like to use your library for verifying ToTP. I see that the verification method allows delayWindow parameter.
Can you please add some documentation regarding what are the units for the delayWindow? Is it in milliseconds, seconds, minutes or something else?
Thanks.
Hey,
probably this is an issue on my end, but I didnt find a discussion forum here.
I try to generate codes, but the codes dont seem to be correct. I think it is related to any time zone issues or something. I am located in germany, means UTC+1, and I use the following code to generate the codes:
String code1 = totp.now(Clock.system(TimeZone.getTimeZone("UTC+1").toZoneId()));
It then generates a code, but when I try to verify it it fails.
E.g. current codes are (the second one is with 30 seconds delay):
Do you have any ideas on that?
Thanks!
I have a question, when I send the same secret and I want to generate an OTP code again, it generates the same as long as it is valid.
Attached capture
If we create a TOTPGenerator with ".withPeriod()" of less than a second (e.g., ".withPeriod(Duration.ofMillis(1))" ), we get an exception:
java.lang.ArithmeticException: / by zero
at com.bastiaanjansen.otp.TOTPGenerator.calculateCounter(TOTPGenerator.java:164)
at com.bastiaanjansen.otp.TOTPGenerator.generate(TOTPGenerator.java:49)
.
.
The reason we are trying this is that, from our testing, it appears that when the TOTPGenerator .generate() method is called, it will generate the same/identical code if the calls are within that Duration.ofSeconds(), i.e., if the the generator is created with .withPeriod(DurationofSeconds(10)), all calls to .generate(), within a 10 second period, return the same code.
We are looking at OTP-Java to generate OTP email codes, so that would mean that if we had requests (from different users) for several OTP email codes within a period of time, then the OTP-Java .generate() method would potentially provide us with the same code for several users.
Am I misinterpreting how this works?
Thanks,
Jim
Hello
I'm trying to implement a TOTP-Solution for Phone Logins for the STARFACE PBX as a Module.
The classes are all loaded by a Custom ClassLoader during runtime, which has caused issues with other implementations before.
I've tried this and the java-top from (https://github.com/samdjstevens/java-totp). in both cases int he end the TOTP Code from the Mobile app and the PBX never matched.
Some things to know.
The Variables are always passed between the functions with @InputVars and @OutputVars. Already set values are defined as "defaults"
The Module generates a secret for each user with a lenght of 256 Bits (32 Characters) and returns it as a string this is saved in a table on the pbx.
`
@InputVar(label="Secretlenght", description="",type=VariableType.NUMBER)
public Integer Secretlength=32;
@OutputVar(label="Generated_Secret", description="",type=VariableType.STRING)
public String Generated_Secret ="";
StarfaceComponentProvider componentProvider = StarfaceComponentProvider.getInstance();
//##########################################################################################
//################### Code Execution ############################
@Override
public void execute(IRuntimeEnvironment context) throws Exception
{
Generated_Secret = new String(SecretGenerator.generate((Secretlength*8)));
}//END OF EXECUTION
`
On demand the PBX generates a QR Code / URI which can be loaded in the Authenticator app.
This is the example from my Test-Environment:
otpauth://totp/STARFACE%20TOTP%20262?secret=PNOW3KSIX3DICLBGLZ4LF2BXTD2Y5APS6XU2UAEFRVKSZNMTZMKA%3D%3D%3D%3D&issuer=STARFACE%20PBX&algorithm=SHA512&digits=6&period=30
Or in short:
Secret: PNOW3KSIX3DICLBGLZ4LF2BXTD2Y5APS6XU2UAEFRVKSZNMTZMKA====
Algorithm: SHA512
Digits: 6
Period: 30
This generated URI is accepted by my Microsoft Authenticator App without issues.
In order to login using the TOTP, the user has to type *77[LoginID]*[TOTP associated with that LoginID] into a phone
For Example *77262*123456
I'm using this code:
`
public class ValidateTOTP implements IBaseExecutable
{
// ##########################################################################################
@InputVar(label = "LoginID", description = "", type = VariableType.STRING)
public String LoginID = "";
@InputVar(label = "Secret", description = "", type = VariableType.STRING)
public String Secret = "";
@InputVar(label = "Code", description = "", type = VariableType.STRING)
public String Code = "";
@InputVar(label = "Digits", description = "", type = VariableType.NUMBER)
public Integer Digits = 6;
@InputVar(label = "Period", description = "", type = VariableType.NUMBER)
public Integer Period = 30;
@InputVar(label = "AllowedTimeDiscrepancy", description = "", type = VariableType.NUMBER)
public Integer AllowedTimeDiscrepancy = 30;
@InputVar(label = "Algorithm", description = "", valueByReferenceAllowed = true)
public HMACAlgorithm HA = HMACAlgorithm.SHA512;
@OutputVar(label = "isValid", description = "", type = VariableType.BOOLEAN)
public boolean isValid = false;
StarfaceComponentProvider componentProvider = StarfaceComponentProvider.getInstance();
// ##########################################################################################
// ################### Code Execution ############################
@Override
public void execute(IRuntimeEnvironment context) throws Exception
{
Logger log = context.getLog();
log.debug("Validating One Time Passcode for: " + LoginID + " -> " + Code + "-> " + HA.toString()+ " ->" + Digits);
TOTPGenerator TOTP = new TOTPGenerator.Builder(Secret.getBytes())
.withHOTPGenerator(builder -> {
builder.withPasswordLength(Digits);
builder.withAlgorithm(HA);
})
.withPeriod(Duration.ofSeconds(Period))
.build();
String CodeNow = TOTP.now();
log.debug(CodeNow +" <==> " + Code);
log.debug(TOTP.getClock().getZone().getId() +" -> "+ TOTP.getPasswordLength() +" -> " + TOTP.getPeriod().toString());
SimpleDateFormat SDF = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss");
Date D = Date.from(TOTP.getClock().instant());
log.debug("TOTP Time: " + SDF.format(D));
isValid = TOTP.verify(Code, AllowedTimeDiscrepancy);
}// END OF EXECUTION
`
[2023-12-07T11:57:42,231] [DEBUG] [] [] [_CALL_ENTRYPOINT] Called Number:77262167741
[2023-12-07T11:57:42,232] [DEBUG] [] [] [_CALL_ENTRYPOINT] Triggered
[2023-12-07T11:57:42,232] [DEBUG] [] [] [_CALL_ENTRYPOINT] LoginID: 262
[2023-12-07T11:57:42,232] [DEBUG] [] [] [_CALL_ENTRYPOINT] Pin: 167741
[2023-12-07T11:57:42,232] [DEBUG] [] [] [ValidateTOTP] Validating One Time Passcode for: 262 -> 167741-> SHA512 ->6
[2023-12-07T11:57:42,232] [DEBUG] [] [] [ValidateTOTP] 279227 <==> 167741
[2023-12-07T11:57:42,232] [DEBUG] [] [] [ValidateTOTP] Europe/Berlin -> 6 -> PT30S
[2023-12-07T11:57:42,232] [DEBUG] [] [] [ValidateTOTP] TOTP Time: 07.12.2023 11:57:42
[2023-12-07T11:57:42,232] [DEBUG] [] [] [_CALL_ENTRYPOINT] 262 -> 167741 -> PNOW3KSIX3DICLBGLZ4LF2BXTD2Y5APS6XU2UAEFRVKSZNMTZMKA==== -> false
[2023-12-07T11:57:42,232] [DEBUG] [] [] [_CALL_ENTRYPOINT] Login incorrect!
The Time Shown on the PBX by the TOTP Clock is accurate and the same as my phone, but the TOTP still does not match.
I was wondering, if there was an easy way to debug this.
Or is there an issue with Converting the Secret Bytes to a String and back, that maybe causes this discrepancy?
Sincerely
Fabian95qw
Hi @BastiaanJansen, is it possible somehow to get the number of seconds remaining for the TOTP?
Consider a clustered RESTful application that generates and validates TOTP using this library.
Is it sufficient to use the same seed across all replicas of the application in order to produce a TOTP that would be validated by any other replica node? In other words, is it safe to assume that each replica node, given the same configuration, should produce the same TOTP and should be able to validate the TOTP produced by any other node?
If the above is true, how to deal with the fact that once the OTP is used on one node, being it a one-time password, no other replica node should accept it?
Is this something users would need to build on top of the library? How about defining a pluggable strategy that would allow your users to store the generated TOTP in a shared storage, for example a self-expiring distributed cache based on hazelcast? If a used token was stored in a shared map (user -> token) until it expires and is removed, no other node would be able to use it.
Hi,
If i give timestep as hours or days by converting to milliseconds, it was not validating properly(For example if i generate otp ar today 8 Am and it was expiring at day after tomorrow 5:30 AM (GMT time)) and also it takes the time zone calculation as well. Request you to update the code in this issue. I have used HMACSHA512 algorithm with 6 digits. Tried with 30 days but its validating upto 20 days only.
Hello, I was able to get the following code working.
TOTPGenerator totpGen = new TOTPGenerator.Builder(myKey) .withHOTPGenerator(builder -> { builder.withPasswordLength(6); builder.withAlgorithm(HMACAlgorithm.SHA1); }) .withPeriod(Duration.ofSeconds(30)) .build();
Since SHA1 is deprecated I wanted to switch to SHA256. I switched to use your SecretGenerator class for this like so...
byte[] secretKeyBytes = SecretGenerator.generate(256); Base32 base32 = new Base32(); String secretKey = base32.encodeAsString(secretKeyBytes);
This secret key is added to my otp string like so. I have set the algorithm to SHA256. I have tried keeping the secret as is, and removing the padding.
otpauth://totp/testCreator:user?secret=KJFVIRCKGRLVERRUI4ZUEMSUJQ2TIR2CJNITGMSQKI2FQR2JKNATMVKPGZDEOVSJG5LEENRWLJKFAWSUI5AT2PJ5HU&issuer=testCreator&algorithm=SHA256
otpauth://totp/testCreator:user?secret=LJEU4RJXIJMUWSSHINLDON2ZKRME2TKLJZCEKSSVLI2UYVKCI5LTOU2SKNMVISBSKBFVCT2IKBBEYM2FGRIT2PJ5HU======&issuer=testCreator&algorithm=SHA256
When I switch my validation code to use SHA256 as written below my codes never validate. I am guessing this is user error but if you have any ideas or an example it would be most appreciated.
TOTPGenerator totpGen = new TOTPGenerator.Builder(myKey) .withHOTPGenerator(builder -> { builder.withPasswordLength(6); builder.withAlgorithm(HMACAlgorithm.SHA256); }) .withPeriod(Duration.ofSeconds(30)) .build();
Thanks!
hi~
thank you for your very useful library!
There are some strange stuff about SecretGenerator Class,
it generate to much long bits than expected..
I think this code is weird.
byte[] bytes = new byte[bits * 8];
/**
* Generate an OTP base32 secret
*
* @param bits length, this should match the length of the HMAC algorithm type:
* SHA1: 160 bits
* SHA256: 256 bits
* SHA512: 512 bits
* @return generated secret
*/
public static byte[] generate(final int bits) {
if (bits <= 0)
throw new IllegalArgumentException("Bits must be higher than 0");
byte[] bytes = new byte[bits * 8];
SecureRandom random = new SecureRandom();
random.nextBytes(bytes);
Base32 encoder = new Base32();
return encoder.encode(bytes);
}
And I have one question about Custom Key.
is it okey to generate the combination key with custom seed ( like ID, email etc ) without using SecretGenerator.class
even if bit length is matched with algorithm type
for example,
in case SHA1 - [email protected]_MkpBoD5YCtZi/aTS1EdEHqJneNfsw3oG4.... ( 160bit )
in case SHA256 - [email protected]_MkpBoD5YCtZi/aTS1EdEHqJneNfsw3oG4dqhukGy9l .... ( 256bit )
As I khow Base32 from org.apache.commons.codec.binary.Base32 is threadsafe and we can create it ones and use all time.
Hi,
I'm trying to build my own OTP app using your library, and I'm using my current app (Authy) to check if the codes are the same.
Parsing the OTPAuth URI gives a different code from what Authy returns. I even tried building the TOTP from scratch using the URI content, and trying the different algorithms, but I never had the same code.
What would be the issue here (if it is an issue) ?
Thanks.
Take the following code:
byte[] secret = SecretGenerator.generate();
TOTPGenerator totp = TOTPGenerator.withDefaultValues(secret);
URI uri = totp.getURI("Acme Co", "myuser");
System.out.println(uri);
This will throw a java.net.URISyntaxException: Illegal character in path at index 19: otpauth://totp/Acme Co:myuser?period=30&digits=6&secret=<secret>&issuer=Acme+Co&algorithm=SHA1
If I escaped the issuer myself:
byte[] secret = SecretGenerator.generate();
TOTPGenerator totp = TOTPGenerator.withDefaultValues(secret);
URI uri = totp.getURI("Acme%20Co", "myuser");
System.out.println(uri);
It outputs otpauth://totp/Acme%20Co:myuser?period=30&digits=6&secret=<secret>&issuer=Acme%2520Co&algorithm=SHA1
Notice the double escaping of issuer=Acme%2520Co in the query parameters.
The path section (both issuer and account) should be escaped when generating the URI.
Hi @BastiaanJansen I am running your example:
https://github.com/BastiaanJansen/OTP-Java/blob/main/src/test/java/com/bastiaanjansen/otp/ExampleApp.java
But when verifying the generated otp it returns false.
Attached capture:
Java 11.0.10+9 AdoptOpenJDK
Java Version: OpenJDK 15
OS: Ubuntu Linux
For the application I am working with, I need to be able to generate the same code for a 15 minute interval. However, this library seems to only allow a max time of 5 minutes before it changes the code.
Here is the code I am using
@Autowired
private Cache<String, byte[]> otpKeyCache;
@Override
public String generate(String username, String emailAddress)
{
var otpGenerator = getOtpGenerator(emailAddress);
var now = Instant.now();
var otp = otpGenerator.now();
logger.info(String.format("Time Step is: %d", otpGenerator.getPeriod().getSeconds()));
logger.info(String.format("Code for 0 seconds: %s", otp));
logger.info(String.format("Code for 60 seconds: %s", otpGenerator.at(now.plusSeconds(60))));
logger.info(String.format("Code for 120 seconds: %s", otpGenerator.at(now.plusSeconds(120))));
logger.info(String.format("Code for 180 seconds: %s", otpGenerator.at(now.plusSeconds(180))));
logger.info(String.format("Code for 240 seconds: %s", otpGenerator.at(now.plusSeconds(240))));
logger.info(String.format("Code for 300 seconds: %s", otpGenerator.at(now.plusSeconds(300))));
logger.info(String.format("Code for 360 seconds: %s", otpGenerator.at(now.plusSeconds(360))));
logger.info(String.format("Code for 420 seconds: %s", otpGenerator.at(now.plusSeconds(420))));
logger.info(String.format("Code for 480 seconds: %s", otpGenerator.at(now.plusSeconds(480))));
logger.info(String.format("Code for 540 seconds: %s", otpGenerator.at(now.plusSeconds(540))));
logger.info(String.format("Code for 600 seconds: %s", otpGenerator.at(now.plusSeconds(600))));
return otp;
}
private TOTP getOtpGenerator(String emailAddress)
{
var secret = getSecret(emailAddress);
return createOtpGenerator(secret);
}
private byte[] getSecret(String emailAddress)
{
var secret = otpKeyCache.getIfPresent(emailAddress);
if (secret == null)
{
secret = SecretGenerator.generate();
otpKeyCache.put(emailAddress, secret);
}
return secret;
}
private TOTP createOtpGenerator(byte[] secret)
{
var builder = new TOTP.Builder(secret);
return builder.withPasswordLength(6)
.withAlgorithm(HMACAlgorithm.SHA512)
.withPeriod(Duration.ofMillis(otpDurationMs))
.build();
}
The attached image shows the code in execution. Please note that this is the first time this method is executed, so it is not getting a "cached" secret.
Hi @BastiaanJansen, I was testing your example and I find it very interesting.
I was wondering if with this I can generate an alphanumeric OTP code.?
This private method is never called. Although it is possible that the method will be invoked through reflection, it is more likely that the method is never used, and should be removed. Unless this method is intended to be used with reflection, it is recommended to remove it to increase โฆ
There is 1 occurrence of this issue in the repository.
See all occurrences on DeepSource โ deepsource.io/gh/BastiaanJansen/OTP-Java/issue/JAVA-S0324/occurrences/
Your library is for android developers or Java desktop applications developers?
Cause I want to use it for my Java Swing desktop application development.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.