@@ -37,61 +37,41 @@ const DEFAULT_PERIOD = 30
3737 * @param {string } [options.charSet='0123456789'] - The character set to use, defaults to the numbers 0-9.
3838 * @returns {Promise<string> } The generated HOTP.
3939 */
40- export async function generateHOTP (
40+ async function generateHOTP (
4141 secret ,
4242 {
4343 counter = 0 ,
4444 digits = DEFAULT_DIGITS ,
4545 algorithm = DEFAULT_ALGORITHM ,
4646 charSet = DEFAULT_CHAR_SET ,
47- } = { } ,
47+ } = { }
4848) {
4949 const byteCounter = intToBytes ( counter )
5050 const key = await crypto . subtle . importKey (
5151 'raw' ,
5252 secret ,
5353 { name : 'HMAC' , hash : algorithm } ,
5454 false ,
55- [ 'sign' ] ,
55+ [ 'sign' ]
5656 )
5757 const signature = await crypto . subtle . sign ( 'HMAC' , key , byteCounter )
5858 const hashBytes = new Uint8Array ( signature )
59- // offset is always the last 4 bits of the signature; its value: 0-15
59+
60+ // Use more bytes for longer OTPs
61+ const bytesNeeded = Math . ceil ( ( digits * Math . log2 ( charSet . length ) ) / 8 )
6062 const offset = hashBytes [ hashBytes . length - 1 ] & 0xf
6163
64+ // Convert bytes to BigInt for larger numbers
6265 let hotpVal = 0n
63- if ( digits === 6 ) {
64- // stay compatible with the authenticator apps and only use the bottom 32 bits of BigInt
65- hotpVal =
66- 0n |
67- ( BigInt ( hashBytes [ offset ] & 0x7f ) << 24n ) |
68- ( BigInt ( hashBytes [ offset + 1 ] ) << 16n ) |
69- ( BigInt ( hashBytes [ offset + 2 ] ) << 8n ) |
70- BigInt ( hashBytes [ offset + 3 ] )
71- } else {
72- // otherwise create a 64bit value from the hashBytes
73- hotpVal =
74- 0n |
75- ( BigInt ( hashBytes [ offset ] & 0x7f ) << 56n ) |
76- ( BigInt ( hashBytes [ offset + 1 ] ) << 48n ) |
77- ( BigInt ( hashBytes [ offset + 2 ] ) << 40n ) |
78- ( BigInt ( hashBytes [ offset + 3 ] ) << 32n ) |
79- ( BigInt ( hashBytes [ offset + 4 ] ) << 24n ) |
80- // we have only 20 hashBytes; if offset is 15 these indexes are out of the hashBytes
81- // fallback to the bytes at the start of the hashBytes
82- ( BigInt ( hashBytes [ ( offset + 5 ) % 20 ] ) << 16n ) |
83- ( BigInt ( hashBytes [ ( offset + 6 ) % 20 ] ) << 8n ) |
84- BigInt ( hashBytes [ ( offset + 7 ) % 20 ] )
66+ for ( let i = 0 ; i < Math . min ( bytesNeeded , hashBytes . length - offset ) ; i ++ ) {
67+ hotpVal = ( hotpVal << 8n ) | BigInt ( hashBytes [ offset + i ] )
8568 }
8669
8770 let hotp = ''
8871 const charSetLength = BigInt ( charSet . length )
8972 for ( let i = 0 ; i < digits ; i ++ ) {
9073 hotp = charSet . charAt ( Number ( hotpVal % charSetLength ) ) + hotp
91-
92- // Ensures hotpVal decreases at a fixed rate, independent of charSet length.
93- // 10n is compatible with the original TOTP algorithm used in the authenticator apps.
94- hotpVal = hotpVal / 10n
74+ hotpVal = hotpVal / charSetLength
9575 }
9676
9777 return hotp
@@ -126,7 +106,7 @@ async function verifyHOTP(
126106 algorithm = DEFAULT_ALGORITHM ,
127107 charSet = DEFAULT_CHAR_SET ,
128108 window = DEFAULT_WINDOW ,
129- } = { } ,
109+ } = { }
130110) {
131111 for ( let i = counter - window ; i <= counter + window ; ++ i ) {
132112 if (
0 commit comments