Skip to content

Mutludev's Blog

Time Based One Time Password using Python

2 min read

What is a Time Based One Time Password

Time Based One Time Password(aka TOTP) is a common method of two-factor authentication. Used to create temporary passcodes from time and Secret key using HOTP algorithm. Generated passcode expires after some time, most common is every 30 seconds.

How to create a python app to display passcodes in the terminal?

Before starting lets add script to run our main function

1if __name__ == "__main__":
2 main()

That way we will run our main function every time we start this code from terminal

First of all, we will need the datetime library to get the time and a function named main to print passcodes every 30 seconds

1import datetime
2
3def main():
4 print_keys()#printing passcode at start
5 while True:
6 time_sec = datetime.datetime.now().second
7 if(time_sec == 00 or time_sec == 30):
8 print_keys()
9 time.sleep(1)

Next, we should function to convert secret keys to passcodes To store secret keys we will use secrets.txt named text file format will be like this

1Name:SecretKey
2Name:SecretKey

We have to clear terminal before starting

1import os
2def print_keys():
3 os.system('cls' if os.name == 'nt' else 'clear')

after that we will open secrets.txt file and save to table

1longest = 0
2table_data = list()
3with open("secrets.txt", "r") as f:
4 for data in f.readlines():
5 data = data.replace("\n", "")#Removing newline from line
6 name, secret = data.split(":")
7 if len(name) > longest:
8 longest = len(name)
9 secret = hotp(secret)
10 table_data.append([name, secret])
11 print_table(table_data, longest)#prints passcode in table

Now you see 2 diffrent things here longest and hotp function We use hotp function to convert secret keys to passcodes but what about longest Well i decided it will be more cool if we add table like

1+------+--------+
2| Name | Key |
3+------+--------+
4| Name | 702837 |
5| Name | 539807 |
6| Name | 895028 |
7+------+--------+

To create this without using external libraries I have to get the length of the longest name Now let's create hotp function. For this function, I used susam's mintotp repository and modified a little bit to fit in one function.

1import base64
2import hmac
3import struct
4
5def hotp(secret):
6 padding = '=' * ((8 - len(secret)) % 8)
7 secret_bytes = base64.b32decode(secret.upper() + padding)
8 counter_bytes = struct.pack(">Q", int(time.time() / 30))
9 mac = hmac.digest(secret_bytes, counter_bytes, "sha1")
10 offset = mac[-1] & 0x0f
11 truncated = struct.unpack('>L', mac[offset:offset+4])[0] & 0x7fffffff
12 return str(truncated)[-6:].rjust(6, '0')

Now lets create table Because of the headers name lenght should not be smaller than 4 characters We will be create table header like this

1def print_table(data, longest_lenght):
2 if longest_lenght < 4:
3 longest_lenght = 4
4 print("+-{}-+--------+".format(longest_lenght*"-"))
5 print("| Name{} | Key |".format((longest_lenght-4)*" "))
6 print("+-{}-+--------+".format(longest_lenght*"-"))

Then we will print every name and passcode in the data list

1for creds in data:
2 blank_space = str(" "*(longest_lenght-len(creds[0])))
3 print("| {} | {} |".format(creds[0]+blank_space, creds[1]))

and close our table

1print("+-{}-+--------+".format(longest_lenght*"-"))

Now our final code looks like this

1import datetime
2import os
3import time
4import base64
5import hmac
6import struct
7
8
9def print_table(data, longest_lenght):
10 if longest_lenght < 4:
11 longest_lenght = 4
12 print("+-{}-+--------+".format(longest_lenght*"-"))
13 print("| Name{} | Key |".format((longest_lenght-4)*" "))
14 print("+-{}-+--------+".format(longest_lenght*"-"))
15
16 for creds in data:
17 blank_space = str(" "*(longest_lenght-len(creds[0])))
18 print("| {} | {} |".format(creds[0]+blank_space, creds[1]))
19
20 print("+-{}-+--------+".format(longest_lenght*"-"))
21
22
23def print_keys():
24 os.system('cls' if os.name == 'nt' else 'clear')
25 longest = 0
26 table_data = list()
27 with open("secrets.txt", "r") as f:
28 for data in f.readlines():
29 data = data.replace("\n", "")
30 name, secret = data.split(":")
31 if len(name) > longest:
32 longest = len(name)
33 secret = hotp(secret)
34 table_data.append([name, secret])
35 print_table(table_data, longest)
36
37def hotp(secret):
38 padding = '=' * ((8 - len(secret)) % 8)
39 secret_bytes = base64.b32decode(secret.upper() + padding)
40 counter_bytes = struct.pack(">Q", int(time.time() / 30))
41 mac = hmac.digest(secret_bytes, counter_bytes, "sha1")
42 offset = mac[-1] & 0x0f
43 truncated = struct.unpack('>L', mac[offset:offset+4])[0] & 0x7fffffff
44 return str(truncated)[-6:].rjust(6, '0')
45
46def main():
47 print_keys()
48 while True:
49 time_sec = datetime.datetime.now().second
50 if(time_sec == 00 or time_sec == 30):
51 print_keys()
52 time.sleep(1)
53
54
55if __name__ == "__main__":
56 main()

If we run this we will get an error because we didn't put any secrets file lets quickly add example secrets file

1Name1:JBSWY3DPEHPK3PXP
2Name2:ACDWY3DAQHPK3ZDZ

Now if we did everything correctly our output will be like this

1+-------+--------+
2| Name | Key |
3+-------+--------+
4| Name1 | 155326 |
5| Name2 | 446469 |
6+-------+--------+

Looks like something missing We should add time bar to see how much time left to next password I Want something like

1+-------+--------+
2| Name | Key |
3+-------+--------+
4| Name1 | 155326 |
5| Name2 | 446469 |
6+-------+--------+
715########

We should add else to main function

1def main():
2 print_keys()#printing passcode at start
3 while True:
4 time_sec = datetime.datetime.now().second
5 if(time_sec == 00 or time_sec == 30):
6 print_keys()
7 time.sleep(1)
8 else:
9 if time_sec <= 30:
10 remaining_time = 30-time_sec
11 else:
12 remaining_time = 60-time_sec
13 if remaining_time < 10:
14 remaining_time_str = "0{}".format(remaining_time)
15 else:
16 remaining_time_str = str(remaining_time)
17 table_len = longest_name+11
18 loading = "#"*(table_len-((remaining_time*table_len)//30))
19 print(remaining_time_str + loading, end="\r")# \r takes cursor to beginning

We still need the longest name we can get this from where we defined it first, from print keys Now we will return the longest name just by adding this to end of print_keys function

1return longest

and lastly, in our main function, we have to create longest_name variable from print_keys we will add longest_name in front of print_keys function like this

1longest_name = print_keys()

And were done our final code Should Look Like This

1import datetime
2import os
3import time
4import base64
5import hmac
6import struct
7
8
9def print_table(data, longest_lenght):
10 if longest_lenght < 4:
11 longest_lenght = 4
12 print("+-{}-+--------+".format(longest_lenght*"-"))
13 print("| Name{} | Key |".format((longest_lenght-4)*" "))
14 print("+-{}-+--------+".format(longest_lenght*"-"))
15
16 for creds in data:
17 blank_space = str(" "*(longest_lenght-len(creds[0])))
18 print("| {} | {} |".format(creds[0]+blank_space, creds[1]))
19
20 print("+-{}-+--------+".format(longest_lenght*"-"))
21
22
23def print_keys():
24 os.system('cls' if os.name == 'nt' else 'clear')
25 longest = 0
26 table_data = list()
27 with open("secrets.txt", "r") as f:
28 for data in f.readlines():
29 data = data.replace("\n", "")
30 name, secret = data.split(":")
31 if len(name) > longest:
32 longest = len(name)
33 secret = hotp(secret)
34 table_data.append([name, secret])
35 print_table(table_data, longest)
36 return longest
37
38def hotp(secret):
39 padding = '=' * ((8 - len(secret)) % 8)
40 secret_bytes = base64.b32decode(secret.upper() + padding)
41 counter_bytes = struct.pack(">Q", int(time.time() / 30))
42 mac = hmac.digest(secret_bytes, counter_bytes, "sha1")
43 offset = mac[-1] & 0x0f
44 truncated = struct.unpack('>L', mac[offset:offset+4])[0] & 0x7fffffff
45 return str(truncated)[-6:].rjust(6, '0')
46
47def main():
48 longest_name = print_keys()
49 while True:
50 time_sec = datetime.datetime.now().second
51 if(time_sec == 00 or time_sec == 30):
52 longest_name = print_keys()
53 time.sleep(1)
54 else:
55 if time_sec <= 30:
56 remaining_time = 30-time_sec
57 else:
58 remaining_time = 60-time_sec
59 if remaining_time < 10:
60 remaining_time_str = "0{}".format(remaining_time)
61 else:
62 remaining_time_str = str(remaining_time)
63 table_len = longest_name+11
64 loading = "#"*(table_len-((remaining_time*table_len)//30))
65 print(remaining_time_str + loading, end="\r")# \r takes cursor to beginning
66
67
68
69if __name__ == "__main__":
70 main()

If we run this we will get this output

1+-------+--------+
2| Name | Key |
3+-------+--------+
4| Name1 | 817811 |
5| Name2 | 854363 |
6+-------+--------+
709############

Github Repository

Thanks For Reading

© 2020 by Mutludev's Blog. All rights reserved.
Theme by LekoArts