Imagine that you are faced with the task of organizing user authentication (in the mobile application, first of all) in the way it is done in Telegram / Viber / WhatsApp. Namely, to implement in the API the ability to perform the following steps:
It took me a certain amount of time to realize how to do it correctly. My task is to share the work with you in the hope that it will save someone time.
I will try to briefly outline the approach to this issue. This implies that you have an API, HTTPS, and probably REST . What kind of technology you have there is unimportant. If interested - welcome under cat.
We will talk about the changes that should be made in the API, how to implement one-time passwords on the server, how to ensure security (including brute force protection) and which way to look at when implementing this functionality on a mobile client.
In essence, you need to add three methods to your API:
Action corresponds to CREATE in CRUD .
POST /api/sms_authentications/ : phone : token
If everything went as expected, return the status code 200.
If not, then there is one reasonable exception (besides the standard 500 error for problems on the server, etc., the phone is incorrectly specified. In this case:
HTTP status code: 422 (Unprocessable Entity), in the response body: PHONE_NUMBER_INVALID.
The action corresponds to UPDATE in CRUD .
PUT /api/sms_authentications/<token>/ : sms_code
Similarly. If everything is OK - code 200.
If not, then the exception options are:
PUT /api/sms_authentications/<token>/resend
Similarly. If everything is OK - code 200.
If not, then the exception options are:
In addition, each API method that requires an authenticated user should receive an additional token
parameter that is associated with the user.
Literature:
You will need to store a special key for checking SMS codes. There is a TOTP algorithm that, I quote Wikipedia:
OATH algorithm for creating one-time passwords for secure authentication, which is an improvement in HOTP (HMAC-Based One-Time Password Algorithm). It is a one-way authentication algorithm - the server is authenticated by the client. The main difference between TOTP and HOTP is the generation of a password based on time, that is, time is a parameter [1]. In this case, not the exact indication of time is usually used, but the current interval with pre-established boundaries (for example, 30 seconds).
Roughly speaking, the algorithm allows you to create a one-time password, send it to SMS, and verify that the password sent is correct. And the generated password will work for a specified amount of time. With all this, it is not necessary to store these endless one-time passwords and the time when they will expire, all this is already laid in the algorithm and you only store the key.
Sample code in Ruby to make it clear:
totp = ROTP::TOTP.new("base32secret3232") totp.now # => "492039" # OTP verified for current time totp.verify("492039") # => true sleep 30 totp.verify("492039") # => false
The algorithm is described in the RFC6238 standard, and there is a mass implementation of this algorithm for many languages: for Ruby and Rails , for Python , for PHP , etc. .
Strictly speaking, Telegram and company do not use TOTP, because when registering there, you are not limited in time to 30 seconds. In this regard, it is proposed to consider an alternative OTP algorithm, which generates different passwords, based on a certain counter, but not on time. Meet HOTP :
HOTP (HMAC-Based One-Time Password Algorithm) is a secure authentication algorithm using a one-time password (One Time Password, OTP). Based on HMAC (SHA-1). It is a one-way authentication algorithm, namely: the server performs client authentication.
...
The HOTP generates a key based on a shared secret and a time-independent counter.
HOTP is described in RFC4226 and is supported by the same set of libraries that is presented above. Code example ruby:
hotp = ROTP::HOTP.new("base32secretkey3232") hotp.at(0) # => "260182" hotp.at(1) # => "055283" hotp.at(1401) # => "316439" # OTP verified with a counter hotp.verify("316439", 1401) # => true hotp.verify("316439", 1402) # => false
The first immutable rule is taken for granted: your API, where data goes to and fro and, most importantly, the token
must be wrapped in SSL. Therefore, only HTTPS, no HTTP.
Further, the most obvious attack vector is brute force. Here is what the authors of the HOTP standard (on which TOTP is based on) on this topic write in paragraph 7.3 :
Truncating the HMAC –SHA-1 value to a shorter value makes it possible. Therefore, brute force attacks.
We tried to set a password for your one-time password validation. If you haven’t been able to do this. In particular, it is not necessary to use it. SHOULD be set as low as possible, while still ensuring that usability is not significantly impacted.
This would make it possible to implement a delay scheme to avoid a brute force attack. After all the number of seconds, it can be a question. * 2 = 10 seconds, etc.
')
Prevent attacks based on multiple parallel guessing techniques.
In short, the algorithm does not protect a priori from direct brute force and such things should be prevented at the server level. The authors offer several solutions:
Track the number of unsuccessful attempts to enter the code, and block the possibility of authentication by exceeding a certain maximum limit. The limit is suggested to be made as small as it is still comfortable to use the service.
The opinion that you can only rely on the fact that the code lives for a limited number of seconds is safe, because code is reset - erroneously. Even if there is a fixed limit on the number of attempts per second.
Let's look at an example . Let the TOTP code consist of 6 digits - these are 1,000,000 possible variants. And let it be allowed to enter 1 code in 1 second, and the code lives 30 seconds.
The chance that in 30 attempts in 30 seconds a code will be guessed is 3/100000 ~ 0.003%. It would seem a little. However, there are 2880 such 30 second windows in a day. So, we have the probability to guess the code (even despite the fact that it changes) = 1 - (1 - 3/100000) ^ 2880 ~ 8.2%. 10 days of such attempts already give 57.8% of success. 28 days - 91% success.
So we must clearly realize that it is necessary to implement at least one (and better both) measures proposed by the authors of the standard.
Do not forget about the durability of the key. The authors in paragraph 4 oblige the length of the key to be at least 128 bits, and the recommended length is set to 160 bits (currently the non-attacking key length ).
R6 - The algorithm MUST use a strong shared secret. The length of the shared secret must be at least 128 bits. This document is RECOMMENDs a shared secret length of 160 bits.
So, in the model (or in the database table, if you like) you need to store:
In the case of Android, the resulting token can be stored in SharedPreferences ( why not AccountManager ), and for iOS in KeyChain . See the discussion on SoF .
The above approach will allow you to accomplish the specified task within your technology stack. If you have ideas on this approach or alternative approaches, please share in the comments. Similar request if you have examples of documentation for safe
Source: https://habr.com/ru/post/305694/
All Articles