Wednesday, July 09, 2014

PBKDF2 (Password-Based Key Derivation Function 2) for Pharo

Introduction

I just extracted some PBKDF2 code I’m using in a project to it’s own package. The implementation is directly based on RFC 2898. The only requirement is the Cryptography package. I’m using this code to store hashed+salted passwords. Simply using “hash-only” approaches isn’t secure anymore (and maybe never was). Some pretty good articles do outline this in greater detail:

  • 3 Wrong Ways to Store a Password
  • Salted Password Hashing - Doing it Right

The package is hosted on SmalltalkHub at http://smalltalkhub.com/#!/~udos/PBKDF2. You can install the package using

Gofer new
    url:  'http://smalltalkhub.com/mc/UdoSchneider/PBKDF2/main';
    package: 'ConfigurationOfPBKDF2';
    load.

(Smalltalk at: #ConfigurationOfPBKDF2) project stableVersion load.

If you want to load the tests as well use:

(Smalltalk at: #ConfigurationOfPBKDF2) project stableVersion load: 'Tests'.

Usage

derivedKey := PBKDF2 new
    hashFunction: SHA1;
    password: 'password';
    salt: 'salt';
    iterations: 4096;
    length: 256;
    deriveKey.

You can also use some convenience class methods. E.g.:

 derivedKey := PBKDF2 derivedKeySHA1Password: password salt: salt.

Some settings are using defaults unless otherwise specified:

Defaults

  • prf: HMAC-SHA-1
  • iterations: 1000
  • length: 16 Bytes

Security

My current rule of thumb is to “1 second per hash”: So I configure the hash-function/iterations to consume about 1 second of time on the deployment server. So when a user logs on the server needs 1 second to validate the password. That’s no big deal - neither for the user nor for the server (unless you have hundreds of logins per second). On the other hand this also means that an attacker will need about the same time to check the password.

Of course the rate of passwords for PBKDF2 on a GPU is much higher than the Smalltalk implementation. But even if the GPU based approach is faster by a factor of let’s say one million this means 1.000.000 passwords per second. Using a 16 bytes derived key size this still means around 1*1025 years (((2 raisedTo: (16*8)) / 1000000) seconds asDays / 365) to crack the password.

Even if we only assume simple passwords (8 characters, only letters and numbers) we get around 6 years ((((26+26+10) raisedTo: 8) / 1000000) seconds asDays / 365) …

Conclusion

Configuring PBKDF2 to use 1 second per check on the deployment platform provides “enough” security for my needs … and the next deployment platform will be faster and use more iterations.

How to not do it …

Last but not least: Even “just” one second is much more secure than simply hashing passwords (let’s say with SHA-1 or MD5, especially w/o salts) … those can be broken instantly using rainbow tables. Just try the following with your favorite password:

(MD5 hashMessage: 'admin123') hex. "print-it" '0192023a7bbd73250516f069df18b500'

Google for 0192023a7bbd73250516f069df18b500, first hit and bingo! You just broke an MD5 hashed password …

Benchmarks

All Benchmarks were done on an old Windows 7 Laptop (Core2Duo, 2.54GHz, 8GB):

SystemVersion current. "print-it" Pharo3.0 of 18 March 2013 update 30851 
Smalltalk vm version. "print-it" 'NBCoInterpreter NativeBoost-CogPlugin-GuillermoPolito.19 uuid: acc98e51-2fba-4841-a965-2975997bba66 Dec  7 2013
NBCogit NativeBoost-CogPlugin-GuillermoPolito.19 uuid: acc98e51-2fba-4841-a965-2975997bba66 Dec  7 2013
https://github.com/pharo-project/pharo-vm.git Commit: 71adca113a9aff2876288927e5c1c86bf7ac13e2 Date: 2013-12-07 05:00:47 -0800 By: Philippe Back <philippeback@gmail.com> Jenkins build #14776
'  

Derived Key Size 16 Bytes, SHA1, 10.000 iterations

[PBKDF2 derivedKeyHashFunction: SHA1
 password: 'aSimplePassword'
 salt: 16rFEDCA9876543210
 iterations: 10000
 length: 16] timeToRun. "print-it" 0:00:00:00.902
Derived Key Size 256 Bytes, SHA1, 4.096 iterations (as used by WPA2)

Derived Key Size 256 Bytes, SHA1, 4.096 iterations (as used by WPA2)

[PBKDF2 derivedKeyHashFunction: SHA1
 password: 'aSimplePassword'
 salt: 16rFEDCA9876543210
 iterations: 4096
 length: 256] timeToRun. "print-it" 0:00:00:02.328
Derived Key Size 16 Bytes, SHA256, 1.000 iterations

Derived Key Size 16 Bytes, SHA256, 1.000 iterations

[PBKDF2 derivedKeyHashFunction: SHA256
 password: 'aSimplePassword'
 salt: 16rFEDCA9876543210
 iterations: 1000
 length: 16] timeToRun. "print-it" 0:00:00:01.303
Derived Key Size 16 Bytes, SHA256, 10.000 iterations

Derived Key Size 16 Bytes, SHA256, 10.000 iterations

[PBKDF2 derivedKeyHashFunction: SHA256
 password: 'aSimplePassword'
 salt: 16rFEDCA9876543210
 iterations: 10000
 length: 16] timeToRun. "print-it" 0:00:00:12.772.

Written with StackEdit.

1 comment:

Unknown said...

Rather than setting the iterations and length based on how long it takes the Smalltalk implementation to hash as password it will be better and safer to base it on the fastest implementations out there as those will be the ones used to crack your passwords.


In 2000 the iteration count was recommended to be 1000, so its certainly much higher now (https://en.wikipedia.org/wiki/PBKDF2)


Also - this is the reason I stopped working on the Smalltalk bcrypt implementation. To match the C version of bcrypt I had to take 5,000x longer at the same work level.


I did not convert it to use NativeBoost and maybe that would help but then why bother when its just an FFI call away.