Data breaches are costing companies more money each year. A study, published by IBM Security, reported the average cost of a data breach at $4.24m with 20% of data breaches due to compromised credentials. Due to the high cost and high probability of a data breach being caused by compromised credentials, credential security is of paramount importance to maintaining a company’s finances and brand image.
Actual company name has been redacted for privacy.
It’s been found that the [company] application is currently using outdated methods to secure user credentials. Passwords are currently salted and encrypted, but that isn’t enough to prevent a data breach in today’s world. This document should serve as a quick reference as to why encryption isn’t enough and what actions can be taken to enhance [company]'s credential security.
Why Shouldn’t You Encrypt Passwords?
Encryption is considered a two-way function. These two functions being encryption and decryption. Part of an encryption’s security comes from the need to use a key to allow for decryption. This usage of a key is what makes it undesirable as a password security method as it makes encryption reversible. If the key is compromised, decryption allows for password recovery in plaintext and without guessing.
The encryption key also requires maintenance and secure storage - adding to the already complex rules and configuration of an application. If an attacker were to gain access to the source code and gain access to the database, they would have access to all user passwords - and you should always work under the assumption that your code and/or database will be compromised.
Why Should You Hash Your Passwords?
Besides being recommended by NIST and OWASP, hashing is considered superior to encryption as it is a one-way function, and the plaintext password cannot be obtained by reversing the hash. After hashing, the password is stored as a fixed-length “fingerprint” of sorts. This “fingerprint” is great for security as it allows for protection of the password even if the codebase and/or database are compromised and still allows us to ensure a user’s password is correct.
Not All hashes Are Equally Secure
A good hashing algorithm should be slow and cryptographically secure. Although slow sounds like the opposite of what we’d want when considering application performance, “slow” roughly correlates to the speed in which the hash can be cracked.
Argon2id, bcrypt, scrypt, PBKDF2 are considered the most secure hashing algorithms to use as of 2021 as they have been designed to securely store passwords and have a configurable work factor.
Slow Hashes and Work Factors
Computer hardware is more powerful than ever, and Moore’s law ensures that it will only become more powerful year after year. This hardware can be used to perform dictionary or brute-force attacks - computing billions of hashes per second. To help mitigate these attacks, the recommended hashing algorithms have a configurable work factor (also known as an iteration count). The work factor is the number of hashing iterations performed for each password - the higher the work factor, the more computationally intensive it is to calculate the hash. A higher computational cost means a reduction in speed for an attacker trying to crack a password hash or an increase in the resources an attacker needs to try and crack the hash.
A work factor needs to be carefully considered though. Ideally one would configure the work factor of a hashing algorithm to its highest value to impede an attacker’s performance but doing so would also make verifying login attempts slower for the end user. A balance must be found between security and application performance. The password hashing should be slow enough to ward off attacks, but fast enough as to not degrade the user’s experience. This balance of speed vs. security will be determined by the performance of the server, the number of application users, and the maximum rate at which authentication is requested. One second or less is generally considered the optimal time it should take to calculate a hash.
Using Salt for More Secure Hashes
Although hashing a password is more secure than encryption for security, there are still vulnerabilities. Lookup tables, reverse lookup tables, and rainbow tables are the common ways to crack password hashes, but salting makes it significantly more difficult.
A salt is a randomly generated, unique string that can be prepended or appended to the plaintext password before hashing. Using a salt means that two passwords that are the same in plaintext will never have the same hash since a unique string was added to it. The salt doesn’t have to be treated as any kind of secret either. It is usually stored alongside the password hash to be used for authentication.
Salts are only effective when implemented correctly. Salts should always be unique per user (when creating and changing a password) and should always be long and cryptographically secure. APIs provided by languages to create random strings (like the NewGUID method in .NET) may look like a way to generate a secure, random number but have a level of predictability as they are created using a “standard” set of instructions. In order to create a unique, long, and cryptographically secure salt, a cryptographically secure pseudorandom number generator (CSPRNG) should be used. CSPRNGs are designed to generate a high level of randomness and unpredictability.
Current State
Salts are currently created in LQMS.ServiceLayer UserBL().GetSalt() generating Guid.NewGuid().ToString()
Passwords are encrypted and decrypted in LQMS.Common EncryptionProvider
The EncryptionProvider uses the managed version of the Rijndael algorithm from System.Security.Cryptography
The key is a byte[] converted from a hardcoded base 64 string
The initialization vector is a byte[] converted from a hardcoded base 64 string
The salt is stored in the users table
The encrypted password is stored in the users table
Issues
Salts are not generated using a CSPRNG
Passwords are encrypted instead of hashed
Even if encryption were okay to be used, the key and IV haven’t been changed/rotated and aren’t stored securely
Rijndael encryption (aka AES) has known attacks
Ideal State
Salts would be generated using a CSPRNG like System.Security.Cryptography.RNGCryptoServiceProvider
Salt would be prepended to the plaintext password
Salt + Password would be hashed using System.Web.Helpers.Crypto with an acceptable work factor
This Crypto class uses PBKDF2 with HMAC-SHA1 and is provided by Microsoft
In architecting the new solution:
An Authentication/UserAuthentication service can be created that solely handles authenticating users
Create a class for hashing and verifying hashed passwords
Create a class for validating password rules (should be uppercase, should have x number of characters, etc.)
Create a class for a login response (success, errors, failures)
Moving Existing Users to the New Hashing Algorithm
There are numerous options for upgrading the users to use the new hashing algorithm with the key differences being how convenient it is for the user:
Require all users to reset their password
Require users on a client-by-client basis to reset their password
Re-hash passwords using the new algorithm the next time the user logs in
Re-hash all passwords at once using a batch job
Commenti