1 /**
2 * Portions of this software was developed by employees of the National Institute
3 * of Standards and Technology (NIST), an agency of the Federal Government and is
4 * being made available as a public service. Pursuant to title 17 United States
5 * Code Section 105, works of NIST employees are not subject to copyright
6 * protection in the United States. This software may be subject to foreign
7 * copyright. Permission in the United States and in foreign countries, to the
8 * extent that NIST may hold copyright, to use, copy, modify, create derivative
9 * works, and distribute this software and its documentation without fee is hereby
10 * granted on a non-exclusive basis, provided that this notice and disclaimer
11 * of warranty appears in all copies.
12 *
13 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
14 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
15 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
16 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
17 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
18 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT
19 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
20 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
21 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
22 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
23 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
24 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
25 */
26
27 package gov.nist.secauto.swid.client.totp;
28
29 import java.nio.ByteBuffer;
30 import java.security.InvalidKeyException;
31 import java.security.NoSuchAlgorithmException;
32
33 import javax.crypto.Mac;
34 import javax.crypto.spec.SecretKeySpec;
35
36 /**
37 * A direct implementation of https://tools.ietf.org/html/rfc4226 and
38 * https://tools.ietf.org/html/rfc6238 C = counter K = key Digits = length of OTP Note that a strict
39 * implementation of RFC 4226 will only support HMAC-SHA-1.
40 */
41 public class Hotp {
42
43 public static enum HashAlgorithm {
44 SHA1("HMACSHA1"),
45 SHA256("HMACSHA256"),
46 SHA512("HMACSHA512");
47
48 private String name;
49
50 private HashAlgorithm(String name) {
51 this.name = name;
52 }
53
54 public String getAlgorithmName() {
55 return name;
56 }
57 }
58
59 private static class Digits {
60 private long value;
61 private int length;
62
63 /**
64 * @param length
65 * the number of digits in the OTP
66 */
67 private Digits(int length) {
68 if (length < 6 && length > 9) {
69 // 6 is the minimum in the RFC and 19 is the maximum that a long
70 // can represent
71 // 9 is the maximum number of digits supported in the RFC
72 throw new IllegalArgumentException("the length must be between 6 and 9 digits");
73 }
74 this.length = length;
75 StringBuilder builder = new StringBuilder("1");
76 for (int i = 0; i < length; i++) {
77 builder.append('0');
78 }
79 value = Long.valueOf(builder.toString());
80 }
81
82 public int length() {
83 return length;
84 }
85
86 public long mod(long input) {
87 return input % value;
88 }
89 }
90
91 /** the hash algorithm */
92 private HashAlgorithm algorithm;
93 /** the number of digits */
94 private Digits digits;
95
96 /**
97 * Creates a new instance
98 *
99 * @param algorithm
100 * the algorithm to use
101 * @param length
102 * the number of digits
103 */
104 public Hotp(HashAlgorithm algorithm, int length) {
105 this.algorithm = algorithm;
106 this.digits = new Digits(length);
107 }
108
109 /**
110 * Generates an HOTP value according to RFC 4226
111 *
112 * @param key
113 * the shared secret ('K')
114 * @param counter
115 * the counter ('C') value
116 * @return the HOTP value
117 */
118 public String generate(byte[] key, long counter) {
119 try {
120 byte[] hs = hashString(key, counter);
121 long hotp = truncate(hs);
122 return pad(Long.toString(hotp), this.digits.length());
123 } catch (InvalidKeyException e) {
124 throw new RuntimeException("configuration error, invalid key", e);
125 } catch (NoSuchAlgorithmException e) {
126 throw new RuntimeException("configuration error, missing algorithm support", e);
127 }
128 }
129
130 /**
131 * A helper function for padding a String up to a minimum number of characters
132 *
133 * @param s
134 * the string to pad
135 * @param minLength
136 * the minimum
137 * @return the string or a string padded up to the minimum length
138 */
139 private String pad(String s, int minLength) {
140 int padding = minLength - s.length();
141 if (padding <= 0) {
142 // nothing to do
143 return s;
144 }
145 StringBuilder builder = new StringBuilder();
146 for (int i = 0; i < padding; i++) {
147 builder.append("0");
148 }
149 builder.append(s);
150 return builder.toString();
151 }
152
153 /**
154 * Performs dynamic truncation as described in section 5.3
155 *
156 * @param hash
157 * the input hash
158 * @return the truncated value
159 */
160 private long truncate(byte[] hash) {
161 // put selected bytes into result int
162 int offset = hash[hash.length - 1] & 0xf;
163 int binary = ((hash[offset] & 0x7f) << 24) | ((hash[offset + 1] & 0xff) << 16) | ((hash[offset + 2] & 0xff) << 8)
164 | (hash[offset + 3] & 0xff);
165 return digits.mod(binary);
166 }
167
168 /**
169 * Generates HS as described in step 1. of section 5.3
170 *
171 * @param key
172 * the secret Key or 'K' value
173 * @param counter
174 * the counter or 'C' value
175 * @return hash string
176 * @throws NoSuchAlgorithmException
177 * @throws InvalidKeyException
178 */
179 private byte[] hashString(byte[] key, long counter) throws NoSuchAlgorithmException, InvalidKeyException {
180 Mac mac = Mac.getInstance(algorithm.getAlgorithmName());
181 SecretKeySpec macKey = new SecretKeySpec(key, algorithm.getAlgorithmName());
182 ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
183 buffer.putLong(counter);
184 mac.init(macKey);
185 return mac.doFinal(buffer.array());
186 }
187
188 }