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 }