-
Notifications
You must be signed in to change notification settings - Fork 129
Expand file tree
/
Copy pathDefaultCodeGenerator.java
More file actions
88 lines (71 loc) · 2.77 KB
/
DefaultCodeGenerator.java
File metadata and controls
88 lines (71 loc) · 2.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package dev.samstevens.totp.code;
import dev.samstevens.totp.exceptions.CodeGenerationException;
import org.apache.commons.codec.CodecPolicy;
import org.apache.commons.codec.binary.Base32;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.InvalidParameterException;
import java.security.NoSuchAlgorithmException;
public class DefaultCodeGenerator implements CodeGenerator {
private final HashingAlgorithm algorithm;
private final int digits;
public DefaultCodeGenerator() {
this(HashingAlgorithm.SHA1, 6);
}
public DefaultCodeGenerator(HashingAlgorithm algorithm) {
this(algorithm, 6);
}
public DefaultCodeGenerator(HashingAlgorithm algorithm, int digits) {
if (algorithm == null) {
throw new InvalidParameterException("HashingAlgorithm must not be null.");
}
if (digits < 1) {
throw new InvalidParameterException("Number of digits must be higher than 0.");
}
this.algorithm = algorithm;
this.digits = digits;
}
@Override
public String generate(String key, long counter) throws CodeGenerationException {
try {
byte[] hash = generateHash(key, counter);
return getDigitsFromHash(hash);
} catch (Exception e) {
throw new CodeGenerationException("Failed to generate code. See nested exception.", e);
}
}
/**
* Generate a HMAC-SHA1 hash of the counter number.
*/
private byte[] generateHash(String key, long counter) throws InvalidKeyException, NoSuchAlgorithmException {
byte[] data = new byte[8];
long value = counter;
for (int i = 8; i-- > 0; value >>>= 8) {
data[i] = (byte) value;
}
// Create a HMAC-SHA1 signing key from the shared key
Base32 codec = new Base32(0, null, false, (byte) '=', CodecPolicy.STRICT);
byte[] decodedKey = codec.decode(key);
SecretKeySpec signKey = new SecretKeySpec(decodedKey, algorithm.getHmacAlgorithm());
Mac mac = Mac.getInstance(algorithm.getHmacAlgorithm());
mac.init(signKey);
// Create a hash of the counter value
return mac.doFinal(data);
}
/**
* Get the n-digit code for a given hash.
*/
private String getDigitsFromHash(byte[] hash) {
int offset = hash[hash.length - 1] & 0xF;
long truncatedHash = 0;
for (int i = 0; i < 4; ++i) {
truncatedHash <<= 8;
truncatedHash |= (hash[offset + i] & 0xFF);
}
truncatedHash &= 0x7FFFFFFF;
truncatedHash %= Math.pow(10, digits);
// Left pad with 0s for a n-digit code
return String.format("%0" + digits + "d", truncatedHash);
}
}