I have questions regarding the best way to secure the authentication of users.
I have come across a web application that encrypts the user password in the back end. However this encryption is done with the same key to all passwords.
Also, this key is "hardcoded" in the back end.
They (the app developers) claim that this is perfectly secure. However I have my doubts. I believe that this method can cause two problems:
The reason to encrypt passwords is to avoid access to the passwords in the event of an unauthorized database access. However if you store the key in the same server chances are they will also be able to obtain the key.
The same password will yield the same encrypted value therefore it will be easier to attack the system.
My questions are the following:
Am I right about my claims? And if it is really that insecure, should I warn them about the possible threat?
What would be the pros and cons of using a hash + salt approach instead?
Thanks!
I'm not sure if you might mistakenly mixed up encryption and hashing together. If the user's password is be encrypted and not hashed then there is potential for an attacker to steal all the user password in the event of a data breach.
There are a number of factors that you seem to be looking over when it comes to authentication. Firstly, any hashing should be done in the back-end and never in the front-end. Hashing in the front-end still leaves you vulnerable to hash attacks.
Some developers adopt a double-hash approach in which they hash the password in the front-end and then re-hash it in the back-end. I believe this is unnecessary, the front-end password should be covered by the HTTPS layer (TLS), however that is subject to discussion.
First, let's clarify two key terms before explaining how to securely store and authenticate users.
Encryption
You specify that the user's passwords are being encrypted, rather than hashed. What encryption functions do is map an input (user's password) to an output (encrypted password) in a 1-to-1 fashion, meaning that this is reversible.
This means that if the hacker gains access to the encryption key (private key), they can reverse the entire process easily.
Hashing
Instead, the user's password should be hashed on the server-side. Why? Because you can get away with comparing two hashes to check whether they match without ever storing the plain-text representation of that value.
And once again, you may be asking, "Why"? Well because hashing functions are one-way, meaning that the plain-text value cannot be reversed (well, they are very hard to), I shall not be going into to much detail.
What should I do?
The user's passwords should never be stored as plain-text in any part of the web server. Instead, you should be storing the user's hash. When the user then tries to login, you receive their plain-text password securely over HTTPS/TLS, hash it and if both hashes match, authenticate the user.
So a database table might look like so:
+--------------------------------------+
| ID | Username | Password Hash |
+--------------------------------------+
| 1 | foo | $2a$04$/JicM |
| 2 | bar | $2a$04$cxZWT |
+--------------------------------------+
- Note, the hashes are truncated BCrypt hashes with 4 rounds (AKA - Invalid)
Now let's take an example, between Alice and our server. Don't take the data too literally.
Alice sends a request to login with her credentials, which first passes through our secure transport layer:
{username: "foo", password: "bar"} -> TLS() -> ZwUlLviJjtCgc1B4DlFnK -> | Server |
Our server receives this, then uses it's certificate key to decrypt this:
ZwUlLviJjtCgc1B4DlFnK -> KEY() -> {username: "foo", password: "bar"} -> Web Application
Great! Our credentials have been passed securely, now what? Hash that password and compare against what we got in our database.
BCRYPT('bar') -> $2a$04$/JicM
if ($2a$04$/JicM == user.get_password_hash) {
authenticate();
}
else {
return status_code(401);
}
We have now been able to authenticate a user, storing an irreversible hash value and without ever storing the plain-text value. This should have answered your first and second question.
Yes, your analysis is correct, this is insecure.
It would certainly fail any formal audit, e.g. PCI-DSS. the developers/operators may argue that the asset these accounts provide access to is of little value and hence they have no need to provide such a level of protection, however they still have a duty of care to their customers - and the majority of people will use the same password for different sites/services.
It does provide a means for users to "recover" their passwords without the complexity of creating an expiring OTP - however mailing a plain text password further undermines security.
Indeed, even if an attacker only had access to the encrypted password data (particularly if it contained a known encrypted value / does not use initialization vectors) it may be possible to derive the encryption key.