HMac.java 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. /* HMac.java --
  2. Copyright (C) 2001, 2002, 2003, 2006 Free Software Foundation, Inc.
  3. This file is a part of GNU Classpath.
  4. GNU Classpath is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation; either version 2 of the License, or (at
  7. your option) any later version.
  8. GNU Classpath is distributed in the hope that it will be useful, but
  9. WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  11. General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with GNU Classpath; if not, write to the Free Software
  14. Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
  15. USA
  16. Linking this library statically or dynamically with other modules is
  17. making a combined work based on this library. Thus, the terms and
  18. conditions of the GNU General Public License cover the whole
  19. combination.
  20. As a special exception, the copyright holders of this library give you
  21. permission to link this library with independent modules to produce an
  22. executable, regardless of the license terms of these independent
  23. modules, and to copy and distribute the resulting executable under
  24. terms of your choice, provided that you also meet, for each linked
  25. independent module, the terms and conditions of the license of that
  26. module. An independent module is a module which is not derived from
  27. or based on this library. If you modify this library, you may extend
  28. this exception to your version of the library, but you are not
  29. obligated to do so. If you do not wish to do so, delete this
  30. exception statement from your version. */
  31. package gnu.javax.crypto.mac;
  32. import gnu.java.security.Registry;
  33. import gnu.java.security.hash.IMessageDigest;
  34. import gnu.java.security.hash.MD5;
  35. import gnu.java.security.util.Util;
  36. import java.security.InvalidKeyException;
  37. import java.util.HashMap;
  38. import java.util.Map;
  39. /**
  40. * The implementation of the <i>HMAC</i> (Keyed-Hash Message Authentication
  41. * Code).
  42. * <p>
  43. * <i>HMAC</i> can be used in combination with any iterated cryptographic hash
  44. * function. <i>HMAC</i> also uses a <i>secret key</i> for calculation and
  45. * verification of the message authentication values. The main goals behind this
  46. * construction are:
  47. * <ul>
  48. * <li>To use, without modifications, available hash functions. In particular,
  49. * hash functions that perform well in software, and for which code is freely
  50. * and widely available.</li>
  51. * <li>To preserve the original performance of the hash function without
  52. * incurring a significant degradation.</li>
  53. * <li>To use and handle keys in a simple way.</li>
  54. * <li>To have a well understood cryptographic analysis of the strength of the
  55. * authentication mechanism based on reasonable assumptions on the underlying
  56. * hash function.</li>
  57. * <li>To allow for easy replaceability of the underlying hash function in case
  58. * that faster or more secure hash functions are found or required.</li>
  59. * </ul>
  60. * <p>
  61. * References:
  62. * <ol>
  63. * <li><a href="http://www.ietf.org/rfc/rfc-2104.txt">RFC 2104</a>HMAC:
  64. * Keyed-Hashing for Message Authentication.<br>
  65. * H. Krawczyk, M. Bellare, and R. Canetti.</li>
  66. * </ol>
  67. */
  68. public class HMac
  69. extends BaseMac
  70. implements Cloneable
  71. {
  72. public static final String USE_WITH_PKCS5_V2 = "gnu.crypto.hmac.pkcs5";
  73. private static final byte IPAD_BYTE = 0x36;
  74. private static final byte OPAD_BYTE = 0x5C;
  75. /** caches the result of the correctness test, once executed. */
  76. private static Boolean valid;
  77. protected int macSize;
  78. protected int blockSize;
  79. protected IMessageDigest ipadHash;
  80. protected IMessageDigest opadHash;
  81. protected byte[] ipad;
  82. /**
  83. * Trivial constructor for use by concrete subclasses.
  84. *
  85. * @param underlyingHash the underlying hash algorithm instance.
  86. */
  87. protected HMac(IMessageDigest underlyingHash)
  88. {
  89. super(Registry.HMAC_NAME_PREFIX + underlyingHash.name(), underlyingHash);
  90. this.blockSize = underlyingHash.blockSize();
  91. this.macSize = underlyingHash.hashSize();
  92. ipadHash = opadHash = null;
  93. }
  94. public Object clone() throws CloneNotSupportedException
  95. {
  96. HMac result = (HMac) super.clone();
  97. if (this.ipadHash != null)
  98. result.ipadHash = (IMessageDigest) this.ipadHash.clone();
  99. if (this.opadHash != null)
  100. result.opadHash = (IMessageDigest) this.opadHash.clone();
  101. if (this.ipad != null)
  102. result.ipad = (byte[]) this.ipad.clone();
  103. return result;
  104. }
  105. public void init(Map attributes) throws InvalidKeyException,
  106. IllegalStateException
  107. {
  108. Integer ts = (Integer) attributes.get(TRUNCATED_SIZE);
  109. truncatedSize = (ts == null ? macSize : ts.intValue());
  110. if (truncatedSize < (macSize / 2))
  111. throw new IllegalArgumentException("Truncated size too small");
  112. else if (truncatedSize < 10)
  113. throw new IllegalArgumentException("Truncated size less than 80 bits");
  114. // we dont use/save the key outside this method
  115. byte[] K = (byte[]) attributes.get(MAC_KEY_MATERIAL);
  116. if (K == null)
  117. { // take it as an indication to re-use previous key if set
  118. if (ipadHash == null)
  119. throw new InvalidKeyException("Null key");
  120. // we already went through the motions; ie. up to step #4. re-use
  121. underlyingHash = (IMessageDigest) ipadHash.clone();
  122. return;
  123. }
  124. // for HMACs used in key-derivation functions (e.g. PBKDF2) the key material
  125. // need not be >= the (output) block size of the underlying algorithm
  126. Boolean pkcs5 = (Boolean) attributes.get(USE_WITH_PKCS5_V2);
  127. if (pkcs5 == null)
  128. pkcs5 = Boolean.FALSE;
  129. if (K.length < macSize && ! pkcs5.booleanValue())
  130. throw new InvalidKeyException("Key too short");
  131. if (K.length > blockSize)
  132. {
  133. // (0) replace K with HASH(K) if K is larger than the hash's block size.
  134. // Then pad with zeros until it is the correct size (the next `if').
  135. underlyingHash.update(K, 0, K.length);
  136. K = underlyingHash.digest();
  137. }
  138. if (K.length < blockSize)
  139. {
  140. // (1) append zeros to the end of K to create a B byte string (e.g., if
  141. // K is of length 20 bytes and B=64, then K will be appended with 44
  142. // zero bytes 0x00)
  143. int limit = (K.length > blockSize) ? blockSize : K.length;
  144. byte[] newK = new byte[blockSize];
  145. System.arraycopy(K, 0, newK, 0, limit);
  146. K = newK;
  147. }
  148. underlyingHash.reset();
  149. opadHash = (IMessageDigest) underlyingHash.clone();
  150. if (ipad == null)
  151. ipad = new byte[blockSize];
  152. // (2) XOR (bitwise exclusive-OR) the B byte string computed in step (1)
  153. // with ipad
  154. // (3) append the stream of data 'text' to the B byte string resulting from
  155. // step (2)
  156. // (4) apply H to the stream generated in step (3)
  157. for (int i = 0; i < blockSize; i++)
  158. ipad[i] = (byte)(K[i] ^ IPAD_BYTE);
  159. for (int i = 0; i < blockSize; i++)
  160. opadHash.update((byte)(K[i] ^ OPAD_BYTE));
  161. underlyingHash.update(ipad, 0, blockSize);
  162. ipadHash = (IMessageDigest) underlyingHash.clone();
  163. K = null;
  164. }
  165. public void reset()
  166. {
  167. super.reset();
  168. if (ipad != null)
  169. {
  170. underlyingHash.update(ipad, 0, blockSize);
  171. ipadHash = (IMessageDigest) underlyingHash.clone();
  172. }
  173. }
  174. public byte[] digest()
  175. {
  176. if (ipadHash == null)
  177. throw new IllegalStateException("HMAC not initialised");
  178. byte[] out = underlyingHash.digest();
  179. // (5) XOR (bitwise exclusive-OR) the B byte string computed in step (1)
  180. // with opad
  181. underlyingHash = (IMessageDigest) opadHash.clone();
  182. // (6) append the H result from step (4) to the B byte string resulting from
  183. // step (5)
  184. underlyingHash.update(out, 0, macSize);
  185. // (7) apply H to the stream generated in step (6) and output the result
  186. out = underlyingHash.digest(); // which also resets the underlying hash
  187. // truncate and return
  188. if (truncatedSize == macSize)
  189. return out;
  190. byte[] result = new byte[truncatedSize];
  191. System.arraycopy(out, 0, result, 0, truncatedSize);
  192. return result;
  193. }
  194. public boolean selfTest()
  195. {
  196. if (valid == null)
  197. {
  198. try
  199. {
  200. IMac mac = new HMac(new MD5()); // use rfc-2104 test vectors
  201. String tv1 = "9294727A3638BB1C13F48EF8158BFC9D";
  202. String tv3 = "56BE34521D144C88DBB8C733F0E8B3F6";
  203. byte[] k1 = new byte[] {
  204. 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B,
  205. 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B };
  206. byte[] k3 = new byte[] {
  207. (byte) 0xAA, (byte) 0xAA, (byte) 0xAA, (byte) 0xAA,
  208. (byte) 0xAA, (byte) 0xAA, (byte) 0xAA, (byte) 0xAA,
  209. (byte) 0xAA, (byte) 0xAA, (byte) 0xAA, (byte) 0xAA,
  210. (byte) 0xAA, (byte) 0xAA, (byte) 0xAA, (byte) 0xAA };
  211. byte[] data = new byte[50];
  212. for (int i = 0; i < 50;)
  213. data[i++] = (byte) 0xDD;
  214. HashMap map = new HashMap();
  215. // test vector #1
  216. map.put(MAC_KEY_MATERIAL, k1);
  217. mac.init(map);
  218. mac.update("Hi There".getBytes("ASCII"), 0, 8);
  219. if (! tv1.equals(Util.toString(mac.digest())))
  220. valid = Boolean.FALSE;
  221. // test #2 is not used since it causes a "Key too short" exception
  222. // test vector #3
  223. map.put(MAC_KEY_MATERIAL, k3);
  224. mac.init(map);
  225. mac.update(data, 0, 50);
  226. if (! tv3.equals(Util.toString(mac.digest())))
  227. valid = Boolean.FALSE;
  228. valid = Boolean.TRUE;
  229. }
  230. catch (Exception x)
  231. {
  232. x.printStackTrace(System.err);
  233. valid = Boolean.FALSE;
  234. }
  235. }
  236. return valid.booleanValue();
  237. }
  238. }