bcrypt password hashing in Common Lisp
Native support for bcrypt password hashes was somewhat missing in the Common Lisp ecosystem, unless you count the various CFFI wrappers.
In recent releases of ironclad you can now find native
Common Lisp implementations for bcrypt
and bcrypt-pbkdf
. Both of
these have been implemented as part of this
request.
Given the recently added support for bcrypt
in ironclad
I’ve
packaged things up in a new Common Lisp system, which makes it easy to
work with bcrypt
password hashes in Common Lisp -
cl-bcrypt.
With cl-bcrypt we can now generate, parse and verify bcrypt password hashes.
First, lets load the cl-bcrypt
system.
CL-USER> (ql:quickload :cl-bcrypt)
The supported hash algorithm identifiers by cl-bcrypt
are 2a
and
2b
.
In order to create a new bcrypt password you need to use the
BCRYPT:MAKE-PASSWORD
function, e.g.
CL-USER> (defparameter *password*
(bcrypt:make-password "my-secret-password"))
*PASSWORD*
BCRYPT:MAKE-PASSWORD
accepts keyword parameters, which allow you to
specify a different salt (e.g. obtained by BCRYPT:GENERATE-SALT
),
different cost factor than the default, and a different algorithm
identifier than the default (e.g. 2a
).
If you don’t specify explicitely a salt, a random one will be
generated for you by the BCRYPT:GENERATE-SALT
function.
This example specifies a cost factor of 16
and a hash algorithm
identifier 2a
.
CL-USER> (defparameter *password*
(bcrypt:make-password "my-secret-password" :cost 16 :identifier "2a"))
*PASSWORD*
You can use the BCRYPT:ALGORITHM-IDENTIFIER
, BCRYPT:COST-FACTOR
,
BCRYPT:SALT
and BCRYPT:PASSWORD-HASH
readers to inspect the
returned BCRYPT:PASSWORD
instance from the BCRYPT:MAKE-PASSWORD
function, e.g.
CL-USER> (bcrypt:algorithm-identifier *password*)
"2a"
CL-USER> (bcrypt:cost-factor *password*)
16
CL-USER> (bcrypt:salt *password*)
#(18 117 245 59 29 97 63 72 199 11 254 164 52 87 213 169)
CL-USER> (bcrypt:password-hash *password*)
#(94 0 171 116 90 235 30 220 57 45 147 214 210 77 244 223 63 14 153 13 140 213 183)
The BCRYPT:SALT
and BCRYPT:PASSWORD-HASH
readers return the raw
bytes of the salt and the password hash respectively.
In order to encode a BCRYPT:PASSWORD
instance into its text
representation you need to use the BCRYPT:ENCODE
function.
CL-USER> (bcrypt:encode *password*)
"$2a$16$ClVzMvzfNyhFA94iLDdToOVeApbDppFru3JXNUyi1y1x6MkO0KzZa"
A bcrypt password hash can be decoded using the BCRYPT:DECODE
function,
which will return a new instance of BCRYPT:PASSWORD
, e.g.
CL-USER> (bcrypt:decode "$2a$16$ClVzMvzfNyhFA94iLDdToOVeApbDppFru3JXNUyi1y1x6MkO0KzZa")
#<CL-BCRYPT:PASSWORD {1002207AD3}>
If you encode back the returned instance you should get the same hash string as the one that was decoded.
The BCRYPT:PARSE-HASH
function returns a property list of the
parts that comprise the bcrypt hash string.
CL-USER> (bcrypt:parse-hash "$2a$16$ClVzMvzfNyhFA94iLDdToOVeApbDppFru3JXNUyi1y1x6MkO0KzZa")
(:ALGORITHM-IDENTIFIER "2a"
:COST-FACTOR "16"
:SALT "ClVzMvzfNyhFA94iLDdToO"
:PASSWORD-HASH "VeApbDppFru3JXNUyi1y1x6MkO0KzZa")
When you need to test whether a given bcrypt hash matches a given
password you can use the BCRYPT:PASSWORD=
predicate, e.g.
CL-USER> (bcrypt:password= "my-secret-password"
"$2a$16$ClVzMvzfNyhFA94iLDdToOVeApbDppFru3JXNUyi1y1x6MkO0KzZa")
T
For more information, please refer to the cl-bcrypt repo.