2020
2121package com .eatthepath .otp ;
2222
23- import java .nio .ByteBuffer ;
23+ import javax .crypto .Mac ;
24+ import javax .crypto .ShortBufferException ;
2425import java .security .InvalidKeyException ;
2526import java .security .Key ;
2627import java .security .NoSuchAlgorithmException ;
2728
28- import javax .crypto .Mac ;
29-
3029/**
3130 * <p>Generates HMAC-based one-time passwords (HOTP) as specified in
3231 * <a href="https://tools.ietf.org/html/rfc4226">RFC 4226</a>.</p>
@@ -39,6 +38,7 @@ public class HmacOneTimePasswordGenerator {
3938 private final Mac mac ;
4039 private final int passwordLength ;
4140
41+ private final byte [] buffer ;
4242 private final int modDivisor ;
4343
4444 /**
@@ -115,6 +115,9 @@ protected HmacOneTimePasswordGenerator(final int passwordLength, final String al
115115 }
116116
117117 this .passwordLength = passwordLength ;
118+
119+ // We need at least 8 bytes to store the 64-bit counter value
120+ this .buffer = new byte [Math .max (8 , this .mac .getMacLength ())];
118121 }
119122
120123 /**
@@ -131,21 +134,32 @@ protected HmacOneTimePasswordGenerator(final int passwordLength, final String al
131134 public synchronized int generateOneTimePassword (final Key key , final long counter ) throws InvalidKeyException {
132135 this .mac .init (key );
133136
134- final ByteBuffer buffer = ByteBuffer .allocate (8 );
135- buffer .putLong (0 , counter );
136-
137- final byte [] hmac = this .mac .doFinal (buffer .array ());
138- final int offset = hmac [hmac .length - 1 ] & 0x0f ;
139-
140- for (int i = 0 ; i < 4 ; i ++) {
141- // Note that we're re-using the first four bytes of the buffer here; we just ignore the latter four from
142- // here on out.
143- buffer .put (i , hmac [i + offset ]);
137+ this .buffer [0 ] = (byte ) ((counter & 0xff00000000000000L ) >>> 56 );
138+ this .buffer [1 ] = (byte ) ((counter & 0x00ff000000000000L ) >>> 48 );
139+ this .buffer [2 ] = (byte ) ((counter & 0x0000ff0000000000L ) >>> 40 );
140+ this .buffer [3 ] = (byte ) ((counter & 0x000000ff00000000L ) >>> 32 );
141+ this .buffer [4 ] = (byte ) ((counter & 0x00000000ff000000L ) >>> 24 );
142+ this .buffer [5 ] = (byte ) ((counter & 0x0000000000ff0000L ) >>> 16 );
143+ this .buffer [6 ] = (byte ) ((counter & 0x000000000000ff00L ) >>> 8 );
144+ this .buffer [7 ] = (byte ) (counter & 0x00000000000000ffL );
145+
146+ this .mac .update (this .buffer , 0 , 8 );
147+
148+ try {
149+ this .mac .doFinal (this .buffer , 0 );
150+ } catch (final ShortBufferException e ) {
151+ // We allocated the buffer to (at least) match the size of the MAC length at construction time, so this
152+ // should never happen.
153+ throw new RuntimeException (e );
144154 }
145155
146- final int hotp = buffer . getInt ( 0 ) & 0x7fffffff ;
156+ final int offset = this . buffer [ this . mac . getMacLength () - 1 ] & 0x0f ;
147157
148- return hotp % this .modDivisor ;
158+ return ((this .buffer [offset ] & 0x7f ) << 24 |
159+ (this .buffer [offset + 1 ] & 0xff ) << 16 |
160+ (this .buffer [offset + 2 ] & 0xff ) << 8 |
161+ (this .buffer [offset + 3 ] & 0xff )) %
162+ this .modDivisor ;
149163 }
150164
151165 /**
0 commit comments