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
if __name__ == "__main__":
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
import datetime
def main():
print_keys()#printing passcode at start
while True:
time_sec = datetime.datetime.now().second
if(time_sec == 00 or time_sec == 30):
print_keys()
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
Name:SecretKey
Name:SecretKey
We have to clear terminal before starting
import os
def print_keys():
os.system('cls' if os.name == 'nt' else 'clear')
after that we will open secrets.txt file and save to table
longest = 0
table_data = list()
with open("secrets.txt", "r") as f:
for data in f.readlines():
data = data.replace("\n", "")#Removing newline from line
name, secret = data.split(":")
if len(name) > longest:
longest = len(name)
secret = hotp(secret)
table_data.append([name, secret])
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
+------+--------+
| Name | Key |
+------+--------+
| Name | 702837 |
| Name | 539807 |
| Name | 895028 |
+------+--------+
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.
import base64
import hmac
import struct
def hotp(secret):
padding = '=' * ((8 - len(secret)) % 8)
secret_bytes = base64.b32decode(secret.upper() + padding)
counter_bytes = struct.pack(">Q", int(time.time() / 30))
mac = hmac.digest(secret_bytes, counter_bytes, "sha1")
offset = mac[-1] & 0x0f
truncated = struct.unpack('>L', mac[offset:offset+4])[0] & 0x7fffffff
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
def print_table(data, longest_lenght):
if longest_lenght < 4:
longest_lenght = 4
print("+-{}-+--------+".format(longest_lenght*"-"))
print("| Name{} | Key |".format((longest_lenght-4)*" "))
print("+-{}-+--------+".format(longest_lenght*"-"))
Then we will print every name and passcode in the data list
for creds in data:
blank_space = str(" "*(longest_lenght-len(creds[0])))
print("| {} | {} |".format(creds[0]+blank_space, creds[1]))
and close our table
print("+-{}-+--------+".format(longest_lenght*"-"))
Now our final code looks like this
import datetime
import os
import time
import base64
import hmac
import struct
def print_table(data, longest_lenght):
if longest_lenght < 4:
longest_lenght = 4
print("+-{}-+--------+".format(longest_lenght*"-"))
print("| Name{} | Key |".format((longest_lenght-4)*" "))
print("+-{}-+--------+".format(longest_lenght*"-"))
for creds in data:
blank_space = str(" "*(longest_lenght-len(creds[0])))
print("| {} | {} |".format(creds[0]+blank_space, creds[1]))
print("+-{}-+--------+".format(longest_lenght*"-"))
def print_keys():
os.system('cls' if os.name == 'nt' else 'clear')
longest = 0
table_data = list()
with open("secrets.txt", "r") as f:
for data in f.readlines():
data = data.replace("\n", "")
name, secret = data.split(":")
if len(name) > longest:
longest = len(name)
secret = hotp(secret)
table_data.append([name, secret])
print_table(table_data, longest)
def hotp(secret):
padding = '=' * ((8 - len(secret)) % 8)
secret_bytes = base64.b32decode(secret.upper() + padding)
counter_bytes = struct.pack(">Q", int(time.time() / 30))
mac = hmac.digest(secret_bytes, counter_bytes, "sha1")
offset = mac[-1] & 0x0f
truncated = struct.unpack('>L', mac[offset:offset+4])[0] & 0x7fffffff
return str(truncated)[-6:].rjust(6, '0')
def main():
print_keys()
while True:
time_sec = datetime.datetime.now().second
if(time_sec == 00 or time_sec == 30):
print_keys()
time.sleep(1)
if __name__ == "__main__":
main()
If we run this we will get an error because we didn't put any secrets file lets quickly add example secrets file
Name1:JBSWY3DPEHPK3PXP
Name2:ACDWY3DAQHPK3ZDZ
Now if we did everything correctly our output will be like this
+-------+--------+
| Name | Key |
+-------+--------+
| Name1 | 155326 |
| Name2 | 446469 |
+-------+--------+
Looks like something missing We should add time bar to see how much time left to next password I Want something like
+-------+--------+
| Name | Key |
+-------+--------+
| Name1 | 155326 |
| Name2 | 446469 |
+-------+--------+
15########
We should add else to main function
def main():
print_keys()#printing passcode at start
while True:
time_sec = datetime.datetime.now().second
if(time_sec == 00 or time_sec == 30):
print_keys()
time.sleep(1)
else:
if time_sec <= 30:
remaining_time = 30-time_sec
else:
remaining_time = 60-time_sec
if remaining_time < 10:
remaining_time_str = "0{}".format(remaining_time)
else:
remaining_time_str = str(remaining_time)
table_len = longest_name+11
loading = "#"*(table_len-((remaining_time*table_len)//30))
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
return 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
longest_name = print_keys()
And were done our final code Should Look Like This
import datetime
import os
import time
import base64
import hmac
import struct
def print_table(data, longest_lenght):
if longest_lenght < 4:
longest_lenght = 4
print("+-{}-+--------+".format(longest_lenght*"-"))
print("| Name{} | Key |".format((longest_lenght-4)*" "))
print("+-{}-+--------+".format(longest_lenght*"-"))
for creds in data:
blank_space = str(" "*(longest_lenght-len(creds[0])))
print("| {} | {} |".format(creds[0]+blank_space, creds[1]))
print("+-{}-+--------+".format(longest_lenght*"-"))
def print_keys():
os.system('cls' if os.name == 'nt' else 'clear')
longest = 0
table_data = list()
with open("secrets.txt", "r") as f:
for data in f.readlines():
data = data.replace("\n", "")
name, secret = data.split(":")
if len(name) > longest:
longest = len(name)
secret = hotp(secret)
table_data.append([name, secret])
print_table(table_data, longest)
return longest
def hotp(secret):
padding = '=' * ((8 - len(secret)) % 8)
secret_bytes = base64.b32decode(secret.upper() + padding)
counter_bytes = struct.pack(">Q", int(time.time() / 30))
mac = hmac.digest(secret_bytes, counter_bytes, "sha1")
offset = mac[-1] & 0x0f
truncated = struct.unpack('>L', mac[offset:offset+4])[0] & 0x7fffffff
return str(truncated)[-6:].rjust(6, '0')
def main():
longest_name = print_keys()
while True:
time_sec = datetime.datetime.now().second
if(time_sec == 00 or time_sec == 30):
longest_name = print_keys()
time.sleep(1)
else:
if time_sec <= 30:
remaining_time = 30-time_sec
else:
remaining_time = 60-time_sec
if remaining_time < 10:
remaining_time_str = "0{}".format(remaining_time)
else:
remaining_time_str = str(remaining_time)
table_len = longest_name+11
loading = "#"*(table_len-((remaining_time*table_len)//30))
print(remaining_time_str + loading, end="\r")# \r takes cursor to beginning
if __name__ == "__main__":
main()
If we run this we will get this output
+-------+--------+
| Name | Key |
+-------+--------+
| Name1 | 817811 |
| Name2 | 854363 |
+-------+--------+
09############