crypt

This egg provides functions for generating secure password hashes.

This is done by providing Chicken bindings for the Unix crypt() function. It will attempt to use the system's crypt() for all available types, and supplies fallbacks when the native crypt does not implement a common scheme like Niels Provos' bcrypt() or Ulrich Drepper's SHA-2 based crypt().

If you need to forcefully override what native crypt() features are available (for example when you're cross-compiling to another platform), you can set the FORCE_CRYPT_HASHTYPES environment variable when invoking chicken-install. This should be a space-separated list of hash types. The allowed names are the same as the ones accepted by the crypt-gensalt procedure for the type argument (see below). If in doubt, set the variable to an empty string. This will force usage of all procedures provided by the egg only.

If you are cross-compiling from or to OpenBSD or some other system which has crypt() as part of libc (as opposed to being in libcrypt), you may additionally need to set the FORCE_CRYPT_USE_LIBCRYPT environment variable to TRUE/YES/1 or FALSE/NO/0 depending on whether the target system uses a separate libcrypt or not.

Basic usage

Basic usage is extremely simple (just like Unix crypt()):

Generating a new password hash

(import crypt)

(crypt "password") => "$2a$12$eeOD.RHX7kex47wGOu3ZVu2JhRyQBBOyORhd/mTWjQghMWbrxGNCy"

This will automatically use the hashing mechanism currently deemed most secure. At the moment that is the Blowfish hashing scheme ("bcrypt") with 2^12 rounds. A new random salt is automatically generated each time this procedure is invoked with only one argument.

If you want to create a crypt hash using a specific algorithm, you need to a little more work:

(import crypt)

(crypt "password" (crypt-gensalt type: 'sha512))
=> "$6$IDmt7XiTy5xZal/b$9Z3K/CpJsX8up48yY4ZeSlwSKEpAl73SJYzJTzX1x4/GodsKjrdRc7aLp7E4Tz6H8x04sYJx5quQgUHCMBelX/"

Checking whether a password matches a hash

This is done just like Unix crypt() by checking whether the generated hash matches the input hash:

(import crypt)

(define h "$2a$12$eeOD.RHX7kex47wGOu3ZVu2JhRyQBBOyORhd/mTWjQghMWbrxGNCy")

(string=? (crypt "password" h) h) => #t
(string=? (crypt "wrong" h) h) => #f

Why use crypt()?

First and foremost, crypt() is safe. Lots of thought has gone into the modern flavors of crypt. The UNIX family of operating systems have been using crypt() since 1976 and the algorithms used by crypt() have been constantly improved over time to keep up with improvements in password cracking. It provides stretching, salting and defeats parallel cracking by using a unique salt for each password which is then automatically added to the hash.

The main advantage of Unix crypt() over other safe password hashing schemes is that crypt() provides a way to upgrade the hashing mechanism to a more secure one without having to rehash all passwords; hashes are stored with a prefix code which indicates the hashing mechanism used to generate that hash, so they continue to work using the old algorithm while newly generated hashes are hashed using the stronger algorithm. It's still up to you to implement an update strategy and decide if and when to expire hashes that are no longer deemed secure enough, though!

Modern flavors of crypt() allow you to tweak the cost of generating a hash (the stretching factor), to compensate for Moore's law so that as computers get faster, you can make it more expensive to generate a hash. This thwarts even highly optimized password cracking tools. Remember: slower is better for password hashing schemes! Of course, this too can be done without rehashing older passwords.

Other languages might include just a library specifically for bcrypt(), but this egg's author thinks it is a bit silly to provide a separate library with a dedicated API, since crypt() is designed to transparently upgrade to stronger algorithms as time progresses. bcrypt(), because it is adaptive for CPU speed improvements, will provide good security for the foreseeable future as long as no weaknesses are discovered in the algorithm itself. Once that happens (or a substantially better scheme is developed, etc) you would need to replace all calls to that library with a new one, or change bcrypt() to include whatevercrypt() code so that the name is no longer descriptive. This defeats the point of having a modular hash syntax in the first place.

Background info

Crypt hashes come in three basic flavors:

Raw DES
rEK1ecacw.7.c   (salt: rE)

This is just a raw base64-encoded DES password hash. The first two characters encode the salt, the rest is the hashed password. This DES-based implementation uses a fixed number of 25 rounds.

Extended DES

Extended DES uses a variable number of encryption rounds and 24 bits of salt rather than 12 bits.

 _J9..K0AyUubDrfOgO4s     (prefix: _J9..  salt: K0Ay)

The leading underscore indicates we're using the extended DES scheme here. The first four characters after the underscore indicate the number of iterations to run the encryption algorithm, the next four represent the salt and the final eleven are the hashed password.

Modern, modular format

This looks like the following:

$ALG$ALGSPECIFIC

For example:

$1$O3JMY.Tw$AdLnLjQ/5jXF9.MTp3gHv/   (prefix: $1$  salt: O3JMY.Tw)

For more examples of hashes, see the OpenWall/John the Ripper community wiki page with sample hashes

The ALG encodes the algorithm used for generating the hash, the ALGSPECIFIC is usually the salt followed by the hash. Some schemes store some additional settings before the salt, and some separate the salt from the hash with a dollar sign.

Currently, the following values for ALG are standardized:

$1$
Paul Hennig-Kamp's MD5 scheme. This is a very baroque system, introduced in FreeBSD, which runs a convoluted series of MD5 calls for a large but fixed number of iterations.
$2a$
Niels Provos' Blowfish scheme, aka bcrypt. This is an adaptable scheme introduced in OpenBSD, which allows the system administrator to determine the number of rounds to run the algorithm. As hardware speed improves, this number can be increased to compensate.
$5$
Ulrich Drepper's SHA-256 scheme. This is also an adaptable scheme, introduced in glibc.
$6$
Ulrich Drepper's SHA-512 scheme. Same as the above, except with hashes of double the size :)

There are also some less common values:

$apr1$
Identical to $1$. This prefix is generated by the Apache Portable Runtime library (used by htpasswd, for example)
{SHA}
SHA-1 hash, also used by the Apache Portable Runtime library. (yes, this is not compatible with the standard dollar-sign prefix. Apparently these guys like being completely incompatible to the rest of the world)
$P$
MD5-based hash used by PHPass - the Portable PHP password hashing framework
$H$
Same as above, but used by PHPbb because, well, they're PHP developers. (I wonder if these developers are somehow related to the people working on APR...)
$2x$
"compatibility" option for OpenWall's bcrypt implementation (used as fallback for bcrypt in this egg) to trigger old buggy behavior that has a known vulnerability, only to be used when comparing values produced by the old version.
$2y$
"force correct algorithm" option for OpenWall's bcrypt implementation. When passed $2a$ normally bcrypt will sometimes fall back to the buggy algorithm, but in this egg it acts like $2y was passed. This is done to ensure compatibility with the BSD implementation, which dictates the standard. Currently there is no way to pass $2x$ or $2y$ to crypt even if the OpenWall version is used internally. If you really need this, please contact me and I'll try to figure out a way to do it.

Full API reference

The full API consists of two main procedures with some helpers and several algorithm-specific salt generation procedures. It's recommended you use only the algorithm-specific salt generation procedures when you have very specific needs. In the future these extra procedures might be dropped.

Main API

[procedure] (crypt plaintext-password [salt-or-hash])

This procedure returns the hashed version of the string plaintext-password based on the settings provided by the string salt-or-hash (typically this argument is called "setting" in most POSIX documentation). If salt-or-hash is not provided or #f, a salt will automatically be generated by calling (crypt-gensalt) with no arguments.

This means this procedure can be used in two ways: the first is to generate a hash for a new password and the second is to validate a password against a previously generated hash.

The return value of this procedure can be stored and later used as a salt-or-hash value in subsequent calls to this procedure to validate a user-supplied password; it is a string that contains the salt and algorithm specifications as a prefix of the hash. For more info about why this works, see the "Background Info" section.

[procedure] (crypt-gensalt #!key type random)

This procedure can be used to obtain a string you can pass to the crypt procedure as the salt-or-hash argument. The string generally starts with a dollar-sign followed by an algorithm specifier, followed by the salt. For more specific info about the format of this string, see the "Background Info" section.

The type argument selects the algorithm type to use. It can currently be one of the following symbols: blowfish, sha512, sha256, md5, des-extended or des. If not supplied or #f, the value of (crypt-default-implementation) is used.

The random argument can be used to supply a stronger randomization procedure. It should be a procedure that accepts two integers (a minimum and a maximum) and returns an u8vector with random values. The u8vector must have a length between the minimum and the maximum, both inclusive. The maximum can be #f when there is no upper bound. If random is not supplied or #f, the value of (crypt-default-random-u8vector) is used.

Generic configuration parameters
[parameter] (crypt-default-implementation [type])

The default implementation specifier to put in newly generated salt strings. Can be any symbol accepted by crypt-gensalt. It will always default to the strongest algorithm that was current at the time the egg was last updated. Currently that is blowfish.

[parameter] (crypt-default-random-u8vector [proc])

The default implementation to use by crypt-gensalt as a source of randomly-filled u8vectors. This procedure should accept two integer arguments; a minimum and a maximum length for the u8vector to return. The maximum may also be #f if there is no upper bound.

Defaults to crypt-maximum-random-u8vector, which uses the pseudo-random-integer procedure which is provided by the CHICKEN (chicken random) module. This is relatively strong, so you no longer need to override it for security reasons (in CHICKEN 4 this used to be necessary).

Utility procedures
[procedure] (crypt-prefix->type prefix)

Given a prefix string (which may also be a complete salted hash string), determine what algorithm type it specifies.

Note that because the historical UNIX crypt() had no prefix at all, 'des will be returned for arbitrary strings that don't start with a dollar, even if they're not actually proper DES strings!

Example:

(crypt-prefix->type "$1$") => 'md5
(crypt-prefix->type "whatever") => 'des
(crypt-prefix->type "$nonexistant$") => ERROR: Unknown crypt prefix type
[procedure] (crypt-maximum-random-u8vector min max)

The initial value of crypt-default-random-u8vector. This procedure simply calls Chicken's built-in pseudo-random-integer to get enough values between 0 and 255 to fill the u8vector. It will use the maximum length if available, otherwise the minimum length (this may change later).

Algorithm-specific procedures

You should only use these when you have specific needs for a particular type of algorithm. The defaults are supposed to be good enough for general use. If you disagree, please let me know so I can change this.

Blowfish
[procedure] (crypt-blowfish-gensalt random #!key logrounds)

Generates a salt string for the Blowfish-based crypt() implementation as introduced by Niels Provos and David Mazières in OpenBSD. This is sometimes referred to as bcrypt.

logrounds is the 2-based logarithm of the number of iterations to run the EksBlowfish algorithm. If not specified or #f it defaults to the value of crypt-blowfish-default-logrounds.

random is a procedure that returns a randomly filled u8vector. See the documentation on crypt-default-random-u8vector and crypt-gensalt for more information.

[parameter] (crypt-blowfish-default-logrounds logrounds)

The default value for crypt-blowfish-gensalt's logrounds argument. This is the 2-based logarithm of the number of rounds, so the number 10 indicates 1024 iterations of the blowfish encryption algorithm should be run.

Currently it is set to 12 by default, but this value should grow as computing power grows.

SHA-2
[procedure] (crypt-sha512-gensalt random #!key rounds)

Generates a salt string for the SHA-512 variant of the SHA-2-based crypt() implementation as introduced into GNU libc by Ulrich Drepper of Red Hat.

rounds is the number of iterations to run the SHA-2 algorithm. If not specified or #f it defaults to the value of crypt-sha512-default-rounds.

random is a procedure that returns a randomly filled u8vector. See the documentation on crypt-default-random-u8vector and crypt-gensalt for more information.

[parameter] (crypt-sha512-default-rounds [rounds])

The default value for crypt-sha512-gensalt's rounds argument. Currently defaults to 5000, but should be tweaked to compensate for increasing computing power.

[procedure] (crypt-sha256-gensalt random #!key rounds)

Generates a salt string for the SHA-256 variant of the SHA-2-based crypt() implementation as introduced into GNU libc by Ulrich Drepper of Red Hat.

rounds is the number of iterations to run the SHA-2 algorithm. If not specified or #f it defaults to the value of crypt-sha256-default-rounds.

random is a procedure that returns a randomly filled u8vector. See the documentation on crypt-default-random-u8vector and crypt-gensalt for more information.

[parameter] (crypt-sha256-default-rounds [rounds])

The default value for crypt-sha256-gensalt's rounds argument. Currently defaults to 5000, but should be tweaked to compensate for increasing computing power.

MD5
[procedure] (crypt-md5-gensalt random)

Generates a salt string for the MD5-based crypt() implementation as introduced by Paul Hennig-Kamp in FreeBSD.

random is a procedure that returns a randomly filled u8vector. See the documentation on crypt-default-random-u8vector and crypt-gensalt for more information.

DES
Extended BSDi DES
[procedure] (crypt-des-extended-gensalt random #!key rounds)

Generates a salt string for the extended DES-based crypt() implementation as introduced by BSDi.

rounds is the number of iterations to run the DES algorithm. If not specified or #f it defaults to the value of crypt-des-extended-default-rounds. Important note: DES has specific weaknesses that makes it easier to detect weak keys when it is run for an even number of rounds, so it's important to specify an odd number here.

random is a procedure that returns a randomly filled u8vector. See the documentation on crypt-default-random-u8vector and crypt-gensalt for more information.

[parameter] (crypt-des-extended-default-rounds [rounds])

The default value for crypt-des-extended-gensalt's rounds argument. Currently defaults to 725.

Original UNIX DES
[procedure] (crypt-des-gensalt random)

Generates a salt string for the original UNIX DES-based crypt() implementation. random is a procedure that returns a randomly filled u8vector. See the documentation on crypt-default-random-u8vector and crypt-gensalt for more information.

Repository

This egg is hosted on the CHICKEN Subversion repository:

https://anonymous@code.call-cc.org/svn/chicken-eggs/release/5/crypt

If you want to check out the source code repository of this egg and you are not familiar with Subversion, see this page.

License

 All code in this egg is explicitly placed in the public domain. You may do whatever you want with it.

This egg contains code written by the following people:

Keeping these credits in the source code is not required, but it would be appreciated.

Changelog

References