Concept - how to strengthen the protection of passwords "12345" against bruteforce attacks (second attempt)

Updated: Attempted again failed .



I always wanted a hacker to be unable to crack someone else's password on the site.

For example, if a user brags to a hacker that his password consists only of numbers, then soon the user will lose his account.



But what if the user told his wife his password by phone, and the hacker heard it?



What?! Does the hacker know the password? All this is a fiasco. Can you help such a user to make it harder to hijack their account? I've always worried about this question and I think I found a way to do it. Or rediscovered it, as is often the case. After all, everything has long been invented before us.



Introductory



  • The user wants to have a password "12345" on the site.
  • A hacker can easily guess this password.
  • But the user has to log in, and the hacker doesn't. Even if the hacker knows the login and password.
  • and no SMS with secret codes and intermediaries in the form of additional services. Only the user and your site with a login page.
  • and it will also be relatively safe to say to your wife in a trolleybus: “Galya, I changed the password to 123456 on the site site for our login alice - they say it is more popular than our 12345”. And don't be afraid that your account will be hacked in a second.


How does the method work? All the specifics are under the cut.



What is required?



  • the concept only explains the authentication method
  • the implementation only requires to store " username ", " password ", " salt1 " and " salt2 ". Yes, two salts.
  • do without log tables and counters in redis
  • we will not keep tables with IP addresses
  • we will not use SMS
  • we will not block login attempts. As you know from my last unsuccessful attempt, it is useless to block the entrance - even if a hacker runs into a time limit, he will simply start bruising passwords from several users at once. In addition, the user himself will suffer from the restrictions. Do not call him in support to log in to your site with cool pictures?
  • the user can change the password at any time and make it invalid on other devices. This is a common rule, but I think it's worth mentioning.
  • you can make the process of guessing a password using a dictionary more difficult for a hacker (optional, will be mentioned below).


Method essence



Allow the user to have the password "12345", and cracking that password should be made more difficult. For example, how to guess a password that looks like a hash.



How?



Imagine if the browser always had a unique salt with which to salt the password. Salt for each user. Why is it needed? To encrypt. For example, if you encrypt the string "12345" with the salt "saltsalt" into argon2id, you get "$ argon2id $ v = 19 $ m = 16, t = 2, p = 1 $ c2FsdHNhbHQ $ jX94laSi6vo9AhS + bHwbkg". Change the salt and the hash will be different. One algorithm will encrypt the same passwords differently by using a different salt for each. Good.



But where to get this salt initially? Yes, here she is sitting in front of the monitor. Let him squeeze out two or three extra characters and log in at last humanly. Is there a cat running around? Well, let's have cat. What is cat? This is our secret word. We will send it to the server during registration, and it will generate salt for this word. And then he will send this salt to us. That's it - the browser has salt. Now the password. And we also encrypt the password and salt it with the salt that the server sent.



Now we do not helmet "12345". We send a hash, and since each user has his own salt, the hash is different.



It seems that brute-force will get sick now: not only will it have to do additional calculations and iterate over long strings of argon hashes instead of simple numbers, so also each user will have their own hash - now it is useless to try the same string as a password to check it for everyone users. Let's say three users have chosen the same password: 12345. But their hash will be different. Because everyone has a different salt.



  • The browser has to calculate the password hash using the salt that the server sent it earlier. It should send a hash, not the password itself.
  • The server sends salt using a secret word that is known only to the user. It can be simple. For example - "cat".
  • Each user should have their own salt.
  • Two users who have chosen the same secret word must have a different salt.
  • The server does not have to report whether the correct secret word was used and whether the salt is correct for this user - otherwise it will be brute-force two simple passwords instead of one.
  • If the user changes the secret word, the salt also changes.


That is, to protect his simple password, the user has to come up with another very simple word. He enters this word wherever he wants to be authenticated, and then only the password will need to be entered. Until he clears the cookies.



  • went to the site
  • entered login and secret word
  • entered password
  • ready


The password and secret word can be very simple. One or two characters. For example, the password is 12345 and the secret word 42. And if someone else comes up with the secret word 42, then it will not be scary.



How it works. Step by step concept



We have the following elements:



  • web server
  • database and users table:



    • login
    • password_hash
    • salt_unique_for_each_user
    • salt_for_password
  • user's browser
  • hacker browser
  • login and registration pages on the site
  • script that intercepts the submit event for the login form


Next, we need two different algorithms that can be implemented even on the same encryption system, simply with different parameters:



  • ALG1 is an asymmetric encryption algorithm that generates a hash from a string and salt. ALG1 (str, salt) = hash1. This algorithm is only used on the server.
  • ALG2 is an asymmetric encryption algorithm that generates a hash from a string and salt. ALG2 (str, salt) = hash2. This algorithm is used publicly and it should be possible to implement it on the client (in our example, in javascript).


In addition, we need two more simpler algorithms:



  • ALG_SALT is an algorithm that calculates a random salt as a character string. ALG_SALT () = salt. This algorithm is only used on the server.
  • ALG_PASS is an algorithm that generates a random simple password. ALG_PASS () = pass. This algorithm is only used on the server.


Events step by step



  • The user goes to the registration page, since he does not yet have a login.
  • The server displays a form with two fields: login + simple secret word.
  • User selects login - alice
  • The user chooses the secret word - cat
  • The user clicks the Submit button .


The server checks and makes sure that the user alice is not present in the database.



The server calculates the following values:



$salt_unique_for_each_user = ALG_SALT(); //  "saltsalt"
      
      





$salt_for_password = ALG1("cat", $salt_unique_for_each_user); //  "$argon2id$v=19$m=16,t=2,p=1$c2FsdHNhbHQ$jX94laSi6vo9AhS+bHwbkg"
      
      





$user_simple_password = ALG_PASS(); //  "12345"
      
      





$user_simple_password_hashed = ALG2($user_simple_password , $salt_for_password); //  "$argon2id$v=19$m=16,t=2,p=1$JGFyZ29uMmlkJHY9MTkkbT0xNix0PTIscD0xJGMyRnNkSE5oYkhRJGpYOTRsYVNpNnZvOUFoUytiSHdia2c$b+6ROJVsZ62UXA7hEAg0AQ"
      
      





The server creates a record in the users table and saves the data:



INSERT INTO `users` 
(
login, 
password_hashed, 
salt_unique_for_each_user, 
salt_for_password
) 
VALUES 
(
"alice", 
"$argon2id$v=19$m=16,t=2,p=1$JGFyZ29uMmlkJHY9MTkkbT0xNix0PTIscD0xJGMyRnNkSE5oYkhRJGpYOTRsYVNpNnZvOUFoUytiSHdia2c$b+6ROJVsZ62UXA7hEAg0AQ", 
"saltsalt", 
"$argon2id$v=19$m=16,t=2,p=1$c2FsdHNhbHQ$jX94laSi6vo9AhS+bHwbkg"
).

      
      





The server shows the user a registration success page with the message: “User alice has been successfully created. Use the temporary password 12345 to login. "



The user happily shouts: “Hurray, I registered on the site under the nickname alice and they gave me the password 12345. What a funny and simple password!”. But the user's apartment has very poor soundproofing, and his hacker-neighbor heard everything.



  • The hacker pushes the website address into his browser.
  • The hacker's browser sends out empty cookies.
  • The server checks the hacker's request to see if there is a "salt" cookie. Doesn't find her.
  • Before the hacker sends the stolen username and password, the browser needs to know the salt to encrypt the password with it.
  • The hacker's browser does not yet store the salt in the "salt" cookie.
  • The server sends a login form with two fields: login + secret word to enable the user to get the salt.


The hacker is confused. Let's leave him for now.



  • The user is returned to the login page.
  • The user's browser sends out empty cookies.
  • The server checks the user's request to see if there is a "salt" cookie. Doesn't find her.
  • Before the user sends a username and password, the browser must know the salt in order to encrypt the password with it.
  • The user's browser does not yet store the salt in the "salt" cookie.
  • The server sends a login form with two fields: login + secret word to enable the user to get the salt.
  • The user enters login - alice , secret - cat and clicks the " Submit " button .


The server receives the request and sees that instead of the password, a secret word was sent.



  • alice `salt_unique_for_each_user` -> $db_salt_unique_for_each_user `salt_for_password -> $db_salt_for_password`.
  • , . : $salt_for_password = ALG1(«cat», $db_salt_unique_for_each_user).
  • $salt_for_password . . 12345, , . — ` salt = $db_salt_for_password`. : ` login = «alice»`.


Explanation : The server does not notify in any way which salt was sent - correct or not. The result of its use will be clear when they try to log in with it with the correct username and password.



  • The user receives a server response. Its page either reloads or dynamically changes immediately.
  • User's browser sends cookies: login = alice , salt = "$ argon2id $ v = 19 $ m = 16, t = 2, p = 1 $ c2FsdHNhbHQ $ jX94laSi6vo9AhS + bHwbkg" .
  • The server checks the user's request to see if there is a "salt" cookie. Finds her.
  • The browser already has salt to encrypt the password.
  • The server sends a login form with two fields: login (already alice ) + password.
  • The user enters his simple password 12345 and clicks the " Submit " button .
  • The browser intercepts the onSubmit event .
  • Calculates $ password_hashed = ALG2 ("12345", "$ argon2id $ v = 19 $ m = 16, t = 2, p = 1 $ c2FsdHNhbHQ $ jX94laSi6vo9AhS + bHwbkg").
  • Sends the data "alice" / $ argon2id $ v = 19 $ m = 16, t = 2, p = 1 $ JGFyZ29uMmlkJHY9MTkkbT0xNix0PTIscD0xJGMyRnNkSE5oYkhRJGpYOTRsYVNpNnZvHOUFoAUyti $


The server receives an authentication request:



  • Login + password data: "alice" / $ password_hashed
  • Goes to the database, gets the value ` password_hashed` -> $ db_password_hashed.
  • Compares $ db_password_hashed === $ password_hashed?
  • Hashes match, authorization is successful.


Note: In my example, the server compares the hashes directly. But you cannot store strings in the database that are in fact already passwords. They can be stolen and then used in the form of a login-password. Therefore, you need to hash hashes - no matter how strange it sounds. This means you need a third salt. But it should be stored not in the database, but in the environment variable. However, these are already implementation details that I have left out for simplicity.



In the meantime, our hacker decides to test this strange login form:



  • The hacker enters login - alice , secret - dog and clicks the " Submit " button .
  • The server receives a hacker's request and sees that a secret word was sent instead of a password.
  • alice `salt_unique_for_each_user` -> $db_salt_unique_for_each_user `salt_for_password` -> $salt_for_password.


  • , , : $result_fake_salt = ALG1(«dog», $db_salt_unique_for_each_user). , .


The server sends the calculated salt value back to the user's browser. The headers indicate - `set cookie salt = $ result_fake_salt`. The login is also saved: `set the cookie login =" alice "`.



Explanation : To help the hacker with his hard work, the server sends him salt. But it is impossible to determine from the outside whether the secret word was correct or not.



  • The hacker receives the server's response. Its page either reloads or dynamically changes immediately.
  • The hacker's browser sends cookies: login = alice , salt = $ result_fake_salt .
  • The server checks the user's request to see if there is a "salt" cookie. Finds her.
  • The hacker's browser already has salt to encrypt the password.
  • : ( alice) + .
  • 12345 "".
  • onSubmit.
  • $password_hashed = ALG2(«12345», $result_fake_salt).
  • «alice»/$password_hashed.


The server receives an authentication request - "alice" / $ password_hashed.

Goes to the database, gets the value `password_hashed` -> $ db_password_hashed.

Compares: $ password_hashed === $ db_password_hashed? Nope.



The hashes of these initially identical passwords do not match. Because they were salted in different ways.



The hacker does not give up and goes to register another user on the site.



Quite by accident, he enters the same secret word as the user behind the wall - cat .



The hacker gets a valid salt for the password for the new account, and tries to substitute it in the hashing script.



Fortunately, the password salt generation used a second salt (`salt_unique_for_each_user`), which is generated in a new way for each user. So different users, even with the same passwords and - most importantly - secret words, will have different salts. And the user's salt with the same secret word will not match the salt of another. And matching passwords won't be a problem either.



Now, regarding the complication of brute-force passwords in a dictionary. If we modify ALG2, which is common for both the server and the client, and make it laborious, it will seriously complicate the attack for the hacker. Let me remind you that ALG2 is the process of obtaining a password hash that is sent to the server. On the server, this hash has already been calculated and stored in the database:



  • the server will perform the ALG2 operation only once when writing a password to the database or changing the password to a new one
  • the client will only perform the ALG2 operation during authentication (which should not be confused with authorization). Let's say the client made a mistake a couple of times when entering the password - that's okay.
  • The hacker will do this all the time for each password, which he can be congratulated on. It is especially cynical that a titanic effort would be expended on passwords like 123/1234/12345.


Weak machines can take much longer to complete the operation than fast machines. This can be a problem. So you don't need to complicate the algorithm.



I'll finish the concept description with a barrel of tar:



  • If a user accidentally enters a secret word incorrectly, he will be in a situation where he cannot enter using his password. You will have to reset the secret word (in our case, delete cookies) and send the request again. This can be implemented transparently by pressing one button, but before that the user has to guess. Can be forced to reset on 5 incorrect login attempts.
  • Two users on the same computer will have to constantly dump each other's salt.
  • Two different computers will receive the same password salt
  • If the salt is changed on the server through one computer, the other computer with the old salt will not know that it needs to be changed
  • You can steal salt from your computer and use it to carry out a very quick attack on your account, knowing that the password is very simple.


... and a spoonful of honey:



  • . , "cat" , "termorectal" — . , . , . , .
  • . `salt_for_password` , , , . .



All Articles