Add methods for validating one-time passwords#56
Conversation
| */ | ||
| public boolean validateOneTimePassword(final Key key, final long counter, final String oneTimePassword) throws InvalidKeyException { | ||
| if (oneTimePassword == null) { | ||
| throw new NullPointerException("One-time password must not be null"); |
There was a problem hiding this comment.
If we're explicitly checking for null here, we should probably do it everywhere.
There was a problem hiding this comment.
Also, broadly, there are two strategies we could use for checking that the provided one-time password matches the expected password:
We can do what we're doing here where we parse the given one-time password as an integer, then compare it to the integer value of the expected password
We can generate the expected one-time password as a string, then do a direct string comparison
The integer comparison approach is locale-agnostic (Integer.parseInt can parse numbers from any character set), but a little more complicated. Its execution time is also "constant-time" in the sense that none of its execution time is dependent upon any property of the expected one-time password.
EDIT: Actually, on a second read, this isn't quite true—if we bail out early with the length check, an attacker can learn the expected length of the password by observing execution time. I'll come back and iterate on this, though reasonable minds might disagree about whether the expected password length is actually a secret. (EDIT EDIT: it's constant-time now!)
The string comparison approach is much simpler and, assuming we use something like MessageDigest#isEqual, also constant-time:
return MessageDigest.isEqual(oneTimePassword.getBytes(StandardCharsets.UTF_8),
generateOneTimePasswordString(key, counter, locale).getBytes(StandardCharsets.UTF_8));The problem (or maybe advantage, depending on who you ask?) is that string comparison is locale-sensitive. In other words, even though "003784" and "००३७८४" represent the same number in their respective locales, they're clearly not the same string, and a caller would have to specify which locale they're expecting for validation to pass.
I think I like the locale-agnostic approach, but am very open to feedback and suggestions!
There was a problem hiding this comment.
If we're explicitly checking for
nullhere, we should probably do it everywhere.
Actually, existing coverage here is solid. The main gap I was noticing was passing null for keys, but those turn into InvalidKeyExceptions, which makes sense to me.
2d69000 to
1435cb3
Compare
I've been reluctant to add methods to validate one-time passwords because the security considerations that come with validating passwords are non-trivial (how does rate-limiting work, for example?), and I didn't want to give the impression that java-otp was providing a complete solution. I think I've convinced myself to add them, though, because: