@@ -33,110 +33,61 @@ final public function now(): string
33
33
return $ this ->at ();
34
34
}
35
35
36
- /**
37
- * @return string
38
- * return TOTP at previous timeframe
39
- */
40
36
final public function last (): string
41
37
{
42
38
return $ this ->at (-1 );
43
39
}
44
40
45
- /**
46
- * @return string
47
- * return TOTP at next timeframe
48
- */
49
41
final public function next (): string
50
42
{
51
43
return $ this ->at (1 );
52
44
}
53
45
54
- /**
55
- * @param int $offset
56
- * @return string
57
- * return TOTP at custom timeframe
58
- */
59
46
final public function at (int $ offset = 0 ): string
60
47
{
61
48
return $ this ->generateTOTP ($ this ->secret , $ offset );
62
49
}
63
50
64
- /**
65
- * @param int $length
66
- * @return string
67
- * Generate Secret Key
68
- */
69
51
final public function generateSecretKey (): string
70
52
{
71
53
return substr (str_shuffle ('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567 ' ), 0 , 32 );
72
54
}
73
55
74
- /**
75
- * @return int
76
- * Return current time
77
- */
78
56
private function getCurrentTimestamp (): int
79
57
{
80
- return floor (time () / 30 ); // Time steps of 30 seconds
58
+ return floor (time () / 30 );
81
59
}
82
60
83
- /**
84
- * @param int $counter
85
- * @return string
86
- * Return HOTP at specific counter
87
- */
88
61
final public function atCounter (int $ counter ): string
89
62
{
90
63
return $ this ->generateHOTP ($ this ->secret , $ counter );
91
64
}
92
65
93
- /**
94
- * @param string $secretKey
95
- * @param int $timeStepOffset
96
- * @return string
97
- * Generate TOTP at specific timeframe
98
- */
99
66
private function generateTOTP (string $ secretKey , int $ timeStepOffset = 0 ): string
100
67
{
101
68
$ timestamp = $ this ->getCurrentTimestamp () + $ timeStepOffset ;
102
69
103
- return $ this ->generateHOTP ($ secretKey , $ timestamp ); // TOTP is HOTP with a time-based counter
70
+ return $ this ->generateHOTP ($ secretKey , $ timestamp );
104
71
}
105
72
106
- /**
107
- * @param string $secretKey
108
- * @param int $counter
109
- * @return string
110
- * Generate HOTP at specific counter
111
- */
112
73
private function generateHOTP (string $ secretKey , int $ counter ): string
113
74
{
114
- // Decode the base32 secret key
115
75
$ key = $ this ->base32_decode ($ secretKey );
76
+ $ counter = pack ('J ' , $ counter );
116
77
117
- // Pack the counter into an 8-byte binary string (big endian)
118
- $ counter = pack ('N* ' , 0 ) . pack ('N* ' , $ counter );
78
+ $ hash = hash_hmac ('sha512 ' , $ counter , $ key , true );
119
79
120
- // Generate HMAC-SHA1 hash using the secret key and packed counter
121
- $ hash = hash_hmac ('sha1 ' , $ counter , $ key , true );
122
-
123
- // Extract dynamic binary code (truncated hash)
124
- $ offset = ord ($ hash [19 ]) & 0xf ;
80
+ $ offset = ord ($ hash [63 ]) & 0xf ;
125
81
$ binaryCode = (
126
82
((ord ($ hash [$ offset ]) & 0x7f ) << 24 ) |
127
83
((ord ($ hash [$ offset + 1 ]) & 0xff ) << 16 ) |
128
84
((ord ($ hash [$ offset + 2 ]) & 0xff ) << 8 ) |
129
85
(ord ($ hash [$ offset + 3 ]) & 0xff )
130
86
);
131
87
132
- // Convert binary code into a 6-digit OTP
133
88
return str_pad ($ binaryCode % 1000000 , 6 , '0 ' , STR_PAD_LEFT );
134
89
}
135
90
136
- /**
137
- * @param string $input
138
- * @return string
139
- */
140
91
private function base32_decode (string $ input ): string
141
92
{
142
93
$ alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567 ' ;
@@ -147,7 +98,6 @@ private function base32_decode(string $input): string
147
98
foreach (str_split ($ input ) as $ char ) {
148
99
$ buffer = ($ buffer << 5 ) | strpos ($ alphabet , $ char );
149
100
$ bitsLeft += 5 ;
150
-
151
101
if ($ bitsLeft >= 8 ) {
152
102
$ bitsLeft -= 8 ;
153
103
$ output .= chr (($ buffer >> $ bitsLeft ) & 0xff );
@@ -157,63 +107,34 @@ private function base32_decode(string $input): string
157
107
return $ output ;
158
108
}
159
109
160
- /**
161
- * @param string $otp
162
- * @param string|null $secret
163
- * @return bool
164
- * Verify TOTP
165
- */
166
110
final public function verifyTOTP (string $ otp , string $ secret = null ): bool
167
111
{
168
112
if ($ secret !== null ) {
169
113
$ this ->secret = $ secret ;
170
114
}
171
115
172
- $ secretKey = $ this ->secret ;
173
-
174
- // Check the OTP for the current time step, the previous, and the next one
175
116
for ($ i = -1 ; $ i <= 1 ; $ i ++) {
176
- $ calculatedOtp = $ this ->generateTOTP ($ secretKey , $ i );
177
- if ( $ calculatedOtp === $ otp ) {
178
- return true ; // OTP is valid
117
+ if ( $ this ->generateTOTP ($ this -> secret , $ i ) === $ otp ) {
118
+
119
+ return true ;
179
120
}
180
121
}
181
122
182
- return false ; // OTP is invalid
123
+ return false ;
183
124
}
184
125
185
- /**
186
- * @param string $otp
187
- * @param int $counter
188
- * @param string|null $secret
189
- * @return bool
190
- * Verify HOTP at specific counter
191
- */
192
126
final public function verifyHOTP (string $ otp , int $ counter , string $ secret = null ): bool
193
127
{
194
128
if ($ secret !== null ) {
195
129
$ this ->secret = $ secret ;
196
130
}
197
131
198
- $ calculatedOtp = $ this ->generateHOTP ($ this ->secret , $ counter );
199
- return $ calculatedOtp === $ otp ; // Return true if OTP matches, otherwise false
132
+ return $ this ->generateHOTP ($ this ->secret , $ counter ) === $ otp ;
200
133
}
201
134
202
-
203
- /**
204
- * @param string $label
205
- * @param string $issuer
206
- * @param string|null $secretKey
207
- * @param int|null $counter
208
- * @return string
209
- * Generate URL to be used on QR Codes for Authenticator apps
210
- */
211
135
final public function generateUrl (string $ label , string $ issuer , string $ secretKey = null , int $ counter = null ): string
212
136
{
213
- $ method = 'totp ' ;
214
- if ($ counter !== null ) {
215
- $ method = 'hotp ' ;
216
- }
137
+ $ method = $ counter !== null ? 'hotp ' : 'totp ' ;
217
138
return "otpauth:// " . $ method . '/ ' . $ label . "?secret= " . ($ secretKey ?? $ this ->secret ) . '&issuer= ' . $ issuer . ($ counter ? '&counter= ' . $ counter : '' );
218
139
}
219
140
}
0 commit comments