PostgreSQL Password Encryption
I’m working on a side project to gain more exposure to different kinds of projects in Go. This one is a pretty typical web application involving multiple authenticated users. For data storage I’m using PostgreSQL for its UUID type and cryptographic extensions. The cryptographic extensions are interesting, because of things like chkpass1.
My project right now is using chkpass for storing user passwords. Almost immediately after hooking this up I started wondering how chkpass actually works. Let’s take a look.
The encryption uses the standard Unix function crypt(), and so it suffers from all the usual limitations of that function; notably that only the first eight characters of a password are considered.
So already we see something special:
only the first eight characters of a password are considered
Interesting!
And let’s look at the documentation for crypt2:
key is a user’s typed password.
salt is a two-character string chosen from the set [a-zA-Z0-9./]. This string is used to perturb the algorithm in one of 4096 different ways.
By taking the lowest 7 bits of each of the first eight characters of the key, a 56-bit key is obtained. This 56-bit key is used to encrypt repeatedly a constant string (usually a string consisting of all zeros). The returned value points to the encrypted password, a series of 13 printable ASCII characters (the first two characters represent the salt itself). The return value points to static data whose content is overwritten by each call.
Warning: the key space consists of 2**56 equal 7.2e16 possible values. Exhaustive searches of this key space are possible using massively parallel computers. Software, such as crack(1), is available which will search the portion of this key space that is generally used by humans for passwords. Hence, password selection should, at minimum, avoid common words and names. The use of a passwd(1) program that checks for crackable passwords during the selection process is recommended.
The DES algorithm itself has a few quirks which make the use of the crypt() interface a very poor choice for anything other than password authentication. If you are planning on using the crypt() interface for a cryptography project, don’t do it: get a good book on encryption and one of the widely available DES libraries.
There’s a lot there that’s good to know. A short summary here based on my reading:
-
key
is the user’s password andsalt
is a two character string selected independently of user input. -
The first 8 characters of the user’s password are used to generate a new key that is used to iteratively encrypt a pre-set, constant string.
-
The result of the encryption step above is the new encrypted value we store.
This is very neat, but reading this makes me lean towards not using it for password storage, especially with statements like:
If you are planning on using the crypt() interface for a cryptography project, don’t do it
I understand that “true cryptography” probably requires a different level of cryptographic security than authentication credentials in most cases, but I don’t like to think of it that way. In my opinion, if it’s not good enough for cryptography, then it’s not good enough for the protection of my users' privacy. Not to mention, the cryptographic security of the password is fixed independently of the size of the password entered. That’s not good.
So what do I recommend?
I recommend encrypting with bcrypt3 4 in your language of choice and a per-row unique salt per password5. Store the encrypted password in a regular varchar column.
Also read Jeff Atwood’s post entitled “You’re Probably Storing Passwords Incorrectly”6.
Footnotes
1chkpass
2crypt
3bcrypt
4bcrypt vs. scrypt
5Where to store password salts
6You’re Probably Storing Passwords Incorrectly