Skip to content

Instantly share code, notes, and snippets.

@laura240406
Last active November 24, 2025 20:13
Show Gist options
  • Select an option

  • Save laura240406/3ac44a50901e85e2b48de99fb232391f to your computer and use it in GitHub Desktop.

Select an option

Save laura240406/3ac44a50901e85e2b48de99fb232391f to your computer and use it in GitHub Desktop.
Really simple dependency-free TOTP cli tool. Reads base32 secret from $HOME/.config/totp and prints the TOTP to stdout.
#include <ctype.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
static inline int b32_val(char c) {
if (c >= 'A' && c <= 'Z')
return c - 'A';
if (c >= '2' && c <= '7')
return c - '2' + 26;
return -1;
}
static size_t base32_decode(char *input, uint8_t *output) {
uint32_t buffer = 0;
int bits = 0;
size_t ptr = 0;
for (; *input; input++) {
int c = b32_val(*input);
if (c == -1)
continue;
buffer = (buffer << 5) | c;
bits += 5;
if (bits >= 8) {
bits -= 8;
output[ptr] = (buffer >> bits) & 0xff;
ptr++;
}
}
return ptr;
}
static inline uint32_t r(uint32_t x, int n) { return (x << n) | (x >> (32 - n)); }
static void pb(const uint8_t b[64], uint32_t s[5]) {
uint32_t w[80], a = s[0], bb = s[1], c = s[2], d = s[3], e = s[4];
for (int i = 0; i < 16; i++) {
int j = i * 4;
w[i] = ((uint32_t) b[j] << 24) | ((uint32_t) b[j + 1] << 16) | ((uint32_t) b[j + 2] << 8) | b[j + 3];
}
for (int i = 16; i < 80; i++)
w[i] = r(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);
for (int i = 0; i < 80; i++) {
uint32_t f, k;
if (i < 20) {
f = (bb & c) | ((~bb) & d);
k = 0x5a827999u;
} else if (i < 40) {
f = bb ^ c ^ d;
k = 0x6ed9eba1u;
} else if (i < 60) {
f = (bb & c) | (bb & d) | (c & d);
k = 0x8f1bbcdcu;
} else {
f = bb ^ c ^ d;
k = 0xca62c1d6u;
}
uint32_t t = r(a, 5) + f + e + k + w[i];
e = d;
d = c;
c = r(bb, 30);
bb = a;
a = t;
}
s[0] += a;
s[1] += bb;
s[2] += c;
s[3] += d;
s[4] += e;
}
static void sha1_two(const uint8_t *a, size_t al, const uint8_t *b, size_t bl, uint8_t out[20]) {
uint32_t s[5] = {0x67452301u, 0xefcdab89u, 0x98badcfeu, 0x10325476u, 0xc3d2e1f0u};
uint8_t tmp[64];
size_t rem = 0;
const uint8_t *ptr;
size_t len;
ptr = a;
len = al;
while (len >= 64) {
pb(ptr, s);
ptr += 64;
len -= 64;
}
if (len) {
memcpy(tmp, ptr, len);
rem = len;
}
ptr = b;
len = bl;
if (rem) {
size_t need = 64 - rem;
if (len >= need) {
memcpy(tmp + rem, ptr, need);
pb(tmp, s);
ptr += need;
len -= need;
rem = 0;
} else {
memcpy(tmp + rem, ptr, len);
rem += len;
ptr += len;
len = 0;
}
}
while (len >= 64) {
pb(ptr, s);
ptr += 64;
len -= 64;
}
if (len) {
memcpy(tmp + rem, ptr, len);
rem += len;
}
tmp[rem] = 0x80;
for (size_t i = rem + 1; i < 64; i++)
tmp[i] = 0;
if (rem + 1 > 56) {
pb(tmp, s);
for (size_t i = 0; i < 56; i++)
tmp[i] = 0;
}
uint64_t bits = ((uint64_t) al + (uint64_t) bl) * 8ULL;
tmp[56] = (bits >> 56) & 0xff;
tmp[57] = (bits >> 48) & 0xff;
tmp[58] = (bits >> 40) & 0xff;
tmp[59] = (bits >> 32) & 0xff;
tmp[60] = (bits >> 24) & 0xff;
tmp[61] = (bits >> 16) & 0xff;
tmp[62] = (bits >> 8) & 0xff;
tmp[63] = bits & 0xff;
pb(tmp, s);
for (int i = 0; i < 5; i++) {
out[i * 4] = (s[i] >> 24) & 0xff;
out[i * 4 + 1] = (s[i] >> 16) & 0xff;
out[i * 4 + 2] = (s[i] >> 8) & 0xff;
out[i * 4 + 3] = s[i] & 0xff;
}
}
static void sha1(const uint8_t *d, size_t l, uint8_t out[20]) { sha1_two(d, l, 0, 0, out); }
static void hmac_sha1(const uint8_t *k, size_t kl, const uint8_t *d, size_t dl, uint8_t o[20]) {
uint8_t k0[64], ip[64], op[64], ih[20], tmp[64 + 20];
memset(k0, 0, 64);
if (kl > 64)
sha1(k, kl, k0);
else if (kl)
memcpy(k0, k, kl);
for (int i = 0; i < 64; i++) {
ip[i] = k0[i] ^ 0x36u;
op[i] = k0[i] ^ 0x5cu;
}
sha1_two(ip, 64, d, dl, ih);
memcpy(tmp, op, 64);
memcpy(tmp + 64, ih, 20);
sha1(tmp, 64 + 20, o);
}
static uint32_t totp(char *ssecret) {
// https://drewdevault.com/2022/10/18/TOTP-is-easy.html
uint64_t tm = time(NULL) / 30;
int t = (strlen(ssecret) * 5 + 4) / 8;
uint8_t *secret = (uint8_t *) malloc(t);
memset(secret, 0, t);
mlock(secret, t);
size_t slen = base32_decode(ssecret, secret);
uint8_t b[8];
b[0] = (tm >> 56) & 0xff;
b[1] = (tm >> 48) & 0xff;
b[2] = (tm >> 40) & 0xff;
b[3] = (tm >> 32) & 0xff;
b[4] = (tm >> 24) & 0xff;
b[5] = (tm >> 16) & 0xff;
b[6] = (tm >> 8) & 0xff;
b[7] = (tm >> 0) & 0xff;
uint8_t hm[20];
hmac_sha1(secret, slen, (const uint8_t *) &b, 8, hm);
int offset = hm[19] & 0x0f;
uint32_t code = 0;
code |= hm[offset + 0] << 24;
code |= hm[offset + 1] << 16;
code |= hm[offset + 2] << 8;
code |= hm[offset + 3] << 0;
code &= 0x7fffffff;
code %= 1000000;
memset(secret, 0, t);
munlock(secret, t);
free(secret);
return code;
}
void wipe_stack() {
// dirty little hack
void *stack = alloca(4096);
memset(stack, 0, 4096);
}
int main() {
char *env = getenv("HOME");
if (env == NULL) {
fprintf(stderr, "HOME not set!\n");
return 1;
}
if (chdir(env) != 0) {
fprintf(stderr, "Cannot chdir to HOME: %s!\n", strerror(errno));
return 1;
}
if (chdir(".config") != 0) {
fprintf(stderr, "Cannot chdir to .config: %s!\n", strerror(errno));
return 1;
}
FILE *fd = fopen("totp", "r");
if (fd == NULL) {
fprintf(stderr, "Cannot open totp secret file: %s!\n", strerror(errno));
return 1;
}
char *key = (char *) malloc(257);
mlock(key, 257);
memset(key, 0, 257);
fread(key, 1, 256, fd);
fclose(fd);
for (int i = 256; key[i] != 0x00 && key[i] != 0x0a; i--)
key[i] = 0x00;
printf("%06u\n", totp(key));
memset(key, 0, 256);
munlock(key, 257);
free(key);
wipe_stack();
}
@laura240406
Copy link
Author

licensed under CC0/Public Domain

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment