DEV Community

Anam Ahmed
Anam Ahmed

Posted on

OTP verification without any Database

I have created many web applications that uses OTP verification via SMS. It works by sending a 4 to 6 digit number to the user's phone number via SMS. And the user has to enter the OTP in order to verify the phone number.
In most of the cases this is done using a simple technique, by keeping the OTP in the database or memory storage and matching it with the user input. In this article, I will try to explain how we can achieve the same result without any database of sorts.

But before we start:

let's talk about the motivation behind this approach. Database operation is expensive, usually slower and because it relies on another application or service, has a probability to fail. Another good reasons is, OTP is a temporary data. It does not belong to the database.

I will create an application that sends an OTP to the user using a SMS provider. I will not cover the code involved to send SMS, because that varies from provider to provider. Instead, I will only focus on the verification code. The language of choice is JavaScript, but the code should be simple enough to translate to any other programming language without breaking a sweat.

The Basic Idea:

The technique involves cryptography, in a sense It's quite similar to how JWT tokens are verified. But also very different from JWT because of the way data is handled. This technique is done using the following steps:

  1. Create a cryptographic hash of the phone number, the generated OTP and the expiry timestamp combined.

  2. Append the expiry timestamp with the hash and Send the hash to the user as the response of the first request.

  3. Once the user gets the SMS, the user sends back the hash, the phone number and the OTP in the second request.

  4. The server verifies the OTP by hashing the phone number, OTP sent by the user, and the expiry timestamp that was appended with the hash, the user sent back. Using the same key and same algorithm.

  5. If the expiry timestamp is valid and still in the future. And the newly generated hash matches the one sent by the user. Then the OTP is authentic.

Lot to take in? Let's see it in Action.

The code in this article is also available in NPM

We will create two functions, one for creating the hash and and sending the SMS OTP, another for verifying the OTP.
We will use the awesome package otp-generator to generate OTP codes in NodeJS. you need to install this package using npm or yarn.I've also omitted the package.json file in the example. Let's take a look at the hash generator code, every line is commented so that they are easy to understand:

This function will return a hash. The hash needs to be sent back to the user as HTTP response. when the user requests for an OTP. the sendSMS function, which is a dummy function in this example. In the real world needs to be implemented depending on the SMS provider's API. This function will send the SMS to the user with the OTP.
Email can be used as well.

The user, once received the OTP, will send the hash that came from the first request, phone number and OTP to the server and the below function will verify it. Let's take a look at the source code:

This method uses the SHA256 hashing (HMAC) mechanism to ensure data integrity and is almost always faster and more efficient than a database based OTP verification system.

This does not take into account stuffs like: error handling, http framework or routing etc. just to avoid unnecessary noise.
The full sample source code is available From this link

Top comments (5)

Collapse
 
infamousmrs profile image
InfamousMrS

Hi @Anam - interesting idea.

Because you provide the signature hash, and all components used to generate that hash except for the OTP digits, and because you don't store anything ever, how do you stop brute force where an attacker gets the first response and then tries 999999 times to guess the OTP (the only part the client doesn't know) via brute force?

To detect multiple attempts you would need to store something, and if you were subject of such attack it will also kill all your performance gains from not using data storage. Or you'll need to block spamming attempts in an outer http layer like load balancer, proxy etc.

Collapse
 
mkotzjan profile image
Michael Kotzjan

Hello @infamousmrs ,

@Anam explained his method using a hash algorithm but implemented it using a MAC, more specifically the HMAC algorithm. A hash would open his method for the attack suggested by you, but a MAC algorithm uses a cryptographic key to ensure that only the owner of the key is able to create a MAC. An attacker could therefore only try to brute force the complete MAC (at least 32 Bytes using HMAC).

The term Hash should be replaced by MAC in this article to avoid confusion

Collapse
 
ianmacartney profile image
Ian Macartney

Building on what's already been said, you are providing the expiry and the target hash, and they already know the phone. So an attacker can (on their own computer) generate the SHA256 for all of ${phone}.${0-999999}.${expires} and see which one matches, and then make a single request with the correct code. If you salted the hash it would make it harder to brute force, but as it stands this seems very very insecure, unless I'm missing something.

Collapse
 
ianmacartney profile image
Ian Macartney

I wrote some python and generated all hashes in under a second locally:

from hashlib import sha256
phone="+18001234567"
expiry= challenge.split(".")[1]
start = time.time();
for i in range(999999):
    sha256(f"{phone}{i:06d}{expiry}".encode('utf-8'))
print(time.time() - start)
> 0.3987562656402588
Enter fullscreen mode Exit fullscreen mode
Collapse
 
gunjanmsoni profile image
gunjan soni

Expiring the OTP after one time use will be a challenge here.