What is MFA or 2FA?
As per Wikipedia
Multi-factor authentication (MFA) is a method of computer access control in which a user is only granted access after successfully presenting several separate pieces of evidence to an authentication mechanism - typically at least two of the following categories: knowledge (something they know); possession (something they have), and inherence (something they are).
2FA or Two-factor authentication is a subset of that. Just a type of MFA where you only need two pieces of evidence.
Apart from having a secure and strong password, different developers use different approaches to enhance the security of their applications. Some applications send OTP (one-time-password) via SMS, while some applications send the OTP or a unique link via email. Some applications even call on your mobile. Some applications may ask to answer a security question.
Every approach has its pros and cons. Sending OTP via SMS or email incurs additional costs. Users have to remember the answer to questions in the security question approach.
TOTP or time-based one-time password approach has the advantage over both of these approaches. There is no additional cost involved and users do not have to remember anything. TOTP is widely used in 2FA. In this article, we will see how to implement TOTP in your Django application.
What are HOTP and TOTP:
HOTP meaning HMAC-based One-Time Password is the One-Time Password algorithm and relies on two pieces of information. The first is the secret key, called the "seed", which is known only by the token and the server that validates submitted OTP codes. The second piece of information is the moving factor which is a counter. The counter is stored in the token and on the server. The counter in the token increments when the button on the token is pressed, while the counter on the server is incremented only when an OTP is successfully validated.
To calculate an OTP the token feeds the counter into the HMAC algorithm using the token seed as the key. HOTP uses the SHA-1 hash function in the HMAC. This produces a 160-bit value which is then reduced down to the 6 (or 8) decimal digits displayed by the token.
hmac_sha1 = hmac.new(key, msg=None, digestmod='').hexdigest()
hotp = truncate(hmac_sha1, length=6)
hamc.new() method parameter 'key' is a byte or bytearray object giving a secret key. msg is the counter and digestmod is the name of the hash algorithm e.g. hashlib.sha1.
Time-based OTP (TOTP), is based on HOTP but where the moving factor is time instead of the counter. TOTP uses time in increments called the timestep, which is usually 30 or 60 seconds. This means that each OTP is valid for the duration of the timestep.
The benefit of using TOTP instead of HOTP is that the TOTP passwords are short-lived, they only apply for a given amount of human time. HOTP passwords are potentially longer lived, they apply for an unknown amount of human time.
Generating TOTP in Python:
To generate TOTP, we start with a random key and then generate the base32-encoded token from that random key. We use the hmac.new() function to generate hmac object. But instead of counter, we pass the timestep (not timestamp) as msg parameter.
Since hmac object's hexdigest() returns a long string which is impossible to enter by the user when prompted, we are going to truncate the output to 6 digits.
Step 1: Generating a base32-encoded token
# length of OTP in digits
length = 6
# timestep or time-window for which the token is valid
step_in_seconds = 30
# randon key
key = b"123123123djwkdhawjdk"
token = base64.b32encode(key)
This will print the base32-encoded token, GEZDGMJSGMYTEM3ENJ3WWZDIMF3WUZDL in our case, which we will use later on.
Step 2: Generating hmac hexdigest
t = math.floor(time.time() // step_in_seconds)
hmac_object = hmac.new(key, t.to_bytes(length=8, byteorder="big"), hashlib.sha1)
hmac_sha1 = hmac_object.hexdigest()
# truncate to 6 digits
offset = int(hmac_sha1[-1], 16)
binary = int(hmac_sha1[(offset * 2):((offset * 2) + 8)], 16) & 0x7fffffff
totp = str(binary)[-length:]
This will print the TOTP generated at that particular time (valid for 30 seconds).
Step 3: Verifying that the TOTP generated is correct.
There are multiple mobile applications available online which are used to set up 2FA and generate the TOTP. I am using Microsoft Authenticator.
Install and open the application and add an account. The application will ask you to scan the QR code. We will generate the QR code in the next steps. For now, click the 'Enter code manually' at the bottom. Provide the account name (anything) and then Secret Key. The Secret Key is the base32-encoded token generated in Step 1 above i.e. GEZDGMJSGMYTEM3ENJ3WWZDIMF3WUZDL.
After the account is added, a new OTP will be displayed by the application. Run your code (Step 2 above) and it should generate the same OTP.
Step 4: Generating QR code
Typing the secret key or token is a tedious task and prone to human errors. Hence all applications (like Google's Authenticator, Duo) provide the functionality to scan the QR code.
Every security application may accept tokens or security keys and account names in different formats. We are going to use Microsoft's Authenticator format below to generate the QR code.
# Generating QR Code
image_path = "/tmp/token_qr.png"
qr_string = "otpauth://totp/Example:email@example.com?secret=" + token.decode("utf-8") +"&issuer=Example&algorithm=SHA1&digits=6&period=30"
img = qrcode.make(qr_string)
This code snippet will save the image in
/tmp/ directory. Now open the image, scan the QR code while adding the account in the Authenticator app and you will have a fresh OTP. You can again compare the OTP generated by your code with the one generated by the authenticator application.
If you are using Jupyter to write the code, you can display the image using the code given below.
from IPython.display import Image
Adding TOTP based 2FA in Django application
To enable 2FA for a user in your Django application, follow these steps:
- Generate the base32-encoded secret key or token. Either use a random string to generate the token or derive the string from the user's email address. Derive means use some secure methods to generate a random string where the seed is email. Do not use the email or any login information directly.
- Generate a QR code image and display it to the user. Ask the user to scan the image using any authenticator application and add the account.
- Now on the login screen, in addition to username and password, ask for the 6 digit TOTP.
- When the user submits the login information, match the password with the once stored in the database. Django automatically takes care of authentication, password hashing, and login.
- If the username and password matches, proceed to the next step else return to the login screen with an appropriate error message.
- Now pick the random string i.e. key of that user (from database) or derive it dynamically from the email as you would have done in the first step above and generate the TOTP.
- Match the TOTP generated by the application with the one submitted by the user.
- If the match is successful, let the user login, else return to the login screen with the appropriate error message.
The above code is available on Github.
- Here is a list of some awesome python books
- Host your Django Application for free on PythonAnyWhere.
- If you want full control of your application and server, you should consider DigitalOcean.
- Create a DigitalOcean account with this link and get $100 credits.