I've read about the concepts presented in these two questions:
Pre-hash password before applying bcrypt to avoid restricting password length
Would it make sense to use Bcrypt and PBKDF2 together?
I think I've come up with an implementation that sort of combines the concepts presented in both questions, while possibly providing several layers of security.
Questions
- Is this implementation relatively secure compared to most others?
- Have I shot myself in the foot by chaining multiple functions in this fashion?
- Is there a better way to take advantage of current 'approved', 'proven', and memory-expensive algorithms?
- How can I improve this implementation?
- Is there a point where the salt and pepper values are large enough to make cracking the hashes more difficult by requiring more memory and/or processing?
Concerns
- Man in the middle attacks
- SSLStrip
- ARP spoofing, DNS hijacking, and acting as HTTP proxy
- Terminating SSL connection on attacker’s side and sending HTTP traffic to user
- Plain text password being handled by server code
- Rainbow tables and birthday problem
- Unproven algorithms
- Quick and efficient computation of hashes
- Really long credentials
- Lack of suitable entropy
- Authentication database dump results in disclosure of salts and peppers
- Plain text storage of email
Solutions
- Never send plaintext password over the wire, and utilize SSL only across site
- Salt and hash the password before sending it to the server
- User unique peppers are applied on the server
- Hashing chain ends with PBKDF2, an algorithm vetted by RSA
- Hashing chain utilizes bcrypt, as it requires 4kB of memory
- Any input string to bcrypt is hashed with SHA-512 to keep the number of input characters under the maximum
- Salt and peppers will be long, random strings
- Store private salts and peppers on another service only accessible from internal API
- Store hashes of email addresses as well as passwords on servers that handle public traffic
Components
Algorithms
SHA-512, bcrypt, PBKDF2
Public salts
public_salt
Private pepper
email_pepper, bcrypt_pepper, pbkdf2_pepper
User global
public_salt, email_pepper
User unique
bcrypt_pepper, pbkdf2_pepper
Process
- User enters email address and password in a web form, and submits them to log in.
- Email address and Password string is hashed using PBKDF2 (separately) with public_salt, and the two resulting 1024-bit keys (email_key, password_key) are POSTed to the server over SSL.
- Server hashes email_key again with PBKDF2 using email_pepper with a higher iteration count to get stored_email_key.
- The server then queries an internal only service to request the stored unique peppers for stored_email_key, and receives bcrypt_pepper and pbkdf2_pepper.
- password_key is hashed using SHA-512 to reduce the key length, so bcrypt doesn't truncate the input, resulting in reduced_hash.
- reduced_hash is then hashed using bcrypt, with bcrypt_pepper, resulting in bcrypt_hash.
- bcrypt_hash is then hashed using PBKDF2 with pbkdf2_pepper, resulting in the final, stored password hash.
Overview
email_key = PBKDF2(email, public_salt)
password_key = PBKDF2(password, public_salt)
POST: keys -> SSL -> server
stored_email_key = PBKDF2(email_key, email_pepper)
server gets bcrypt_pepper, and pbkdf2_pepper
reduced_hash = SHA-512(password_key)
bcrypt_hash = bcrypt(reduced_hash, bcrypt_pepper)
pbkdf2_hash = PBKDF2(bcrypt_hash, pbkdf2_pepper)
Compounded
Password - pbkdf2(bcrypt(sha512(pbkdf2(password, public_salt)), bcrypt_pepper), pbkdf2_pepper)
Email address - pbkdf2(pbkdf2(email_address, public_salt), email_pepper)
Regarding email address storage
- Email addresses are only needed for notifications, password resets, and newsletters.
- Email addresses will be stored as a PBKDF2 hash on any system or cluster that handles any public traffic.
- Email addresses will be stored encrypted on a service accessible through an API available only to internal code that needs to send emails to recipients.
I realize this might be overkill, but I really want to ensure passwords as well as email addresses are properly handled and stored.
Specifics
I'm still debating on what values to use for the number of iterations on each step of the process. Obviously, I'm going to be using a larger number of iterations on the server than in javascript.
I'm thinking about generating 1024-bit keys where PBKDF2 is used, as well as using 2048-bit or larger salts.