Simple Analysis of the TOTP Algorithm#
What is TOTP?#
TOTP, which stands for Time-based One-Time Password, follows the RFC 6238 standard and is based on the HOTP algorithm defined in RFC 4226, with the addition of time as an incremental counter for the message.
Where is TOTP applied?#
The most intuitive applications are Microsoft Authenticator and Google's Authenticator, which allow users to add corresponding accounts by scanning a QR code or entering a secret key.
How is it implemented?#
First, let's assume we have a BASE32 string that can be used to generate a key: 4FCDTLHR446DPFCKUA46UFIAYTQIDSZ2.
Convert this string into bytecode:
34 46 43 44 54
4c 48 52 34 34
36 44 50 46 43
4b 55 41 34 36
55 46 49 41 59
54 51 49 44 53
5a 32
Then perform BASE32 decoding to obtain 20 hexadecimal sequences:
0xE1 0x44 0x39 0xAC 0xF1
0xE7 0x3C 0x37 0x94 0x4A
0xA0 0x39 0xEA 0x15 0x00
0xC4 0xE0 0x81 0xCB 0x3A
Next, use today's noon as the time factor: June 26, 2023, 12:00:00 (timestamp: 1687752000 (seconds)).
With the above two factors, we can start the calculation.
In the second step, divide the timestamp of the time factor by 30 for redundancy (30-second window): 1687752000 / 30 = 56258400, then convert it to hexadecimal: 0x035A6F60.
Note: Here, we need to pad with zeros to a length of 8 bytes: 00 00 00 00 03 5A 6F 60.
In the third step, use the private key from the previous text as the HMAC-SHA-1 key and encrypt the result of the time factor as the message, resulting in:
ee 91 c2 ea 63
f2 e4 9c 65 5d
c5 2e c4 9f b0
dd 45 f6 09 09
In the fourth step, take the last byte of the obtained hash and perform a bitwise AND with 0x0F to get the offset: 0x09 & 0x0F = 0x09.
Substituting the offset into the following formula:
Hash result is HASH
Offset is OFFSET = 0x09
(HASH[OFFSET] & 0x7F) << 24
= (0x5D & 0x7F) << 24
= 0x5D << 24
= 0x5D000000
(HASH[OFFSET + 1] & 0xFF) << 16
= (0xC5 & 0xFF) << 16
= 0xC5 << 16
= 0xC50000
(HASH[OFFSET + 2] & 0xFF) << 8
= (0x2E & 0xFF) << 8
= 0x2E << 8
= 0x2E00
HASH[OFFSET + 3] & 0xFF
= 0xC4 & 0xFF
= 0xC4
Perform a bitwise OR on the above results and take the last 6 digits (decimal):
(0x5D000000 | 0xC50000 | 0x2E00 | 0xC4) % 0xF4240 = 0x5DC52EC4 % 0xF4240
= 1573203652 % 1000000 = 203652
Thus, 203652 is the TOTP code we generated.
Code (C Language)#
Note: OpenSSL needs to be included.
#include <stdio.h>
#include "string.h"
#include "time.h"
#include "math.h"
#include "openssl/hmac.h"
void generate_totp_code(const uint8_t * secret, uint32_t counter, int digits) {
uint8_t counter_bytes[8];
for (int i = 1; i <= 8; ++i) {
counter_bytes[8 - i] = (u_int8_t) (counter % 256) & 0xff;
counter /= 256;
}
unsigned char hmac_result[20];
HMAC(EVP_sha1(), secret, 20, counter_bytes,
sizeof(counter_bytes), hmac_result, NULL);
int offset = hmac_result[19] & 0x0f;
int truncated_hash = (hmac_result[offset] & 0x7f) << 24 |
(hmac_result[offset + 1] & 0xff) << 16 |
(hmac_result[offset + 2] & 0xff) << 8 |
(hmac_result[offset + 3] & 0xff);
int otp = truncated_hash % 0xF4240;
printf("TOTP: %0*d\n", digits, otp);
}
int main() {
// 4FCDTLHR446DPFCKUA46UFIAYTQIDSZ2
// to bytes
// 34 46 43 44 54 4c 48 52 34 34 36 44 50 46 43 4b 55 41 34 36 55 46 49 41 59 54 51 49 44 53 5a 32
// base32 decode
// e1 44 39 ac f1 e7 3c 37 94 4a a0 39 ea 15 00 c4 e0 81 cb 3a
uint8_t secret[] = {0xe1, 0x44, 0x39, 0xac,
0xf1, 0xe7, 0x3c, 0x37,
0x94, 0x4a, 0xa0, 0x39,
0xea, 0x15, 0x00, 0xc4,
0xe0, 0x81, 0xcb, 0x3a};
uint32_t counter = time(NULL) / 30;
int digits = 6;
generate_totp_code(secret, counter, digits);
return 0;
}