cl-bcrypt: A first attempt
First, a disclaimer: I am new to Common Lisp; in fact, this is the first Lisp project in which I’ve completed a working prototype. My previous experience is largely in Python and Ruby. I do not represent that the code below is ready for general use. If I’m doing something wrong, please educate me.
The introduction: What is bcrypt?
I want to write a web application in Common Lisp. Part of this task is implementing authentication. The accepted state of the art for safely storing passwords is bcrypt.[1] Just one little problem; if you click on that link, right at the top you will find links to implementations in many languages, but Common Lisp is not among them. However, I know that most of these implementations are wrappers around the C implementation, and I am informed that Common Lisp has excellent FFI support, so even though I’m not very experienced with C, this shouldn’t be too hard.
The FFI approach
And indeed it wasn’t: here’s the complete code for cl-bcrypt:
This code may be used under the terms of the X11 license. (To summarize, do what you like, but keep my name on it, there’s no warranty, and you probably can’t use my name to advertise your wares, not that you’d want to anyway.)
It works on my system (Mac OS X 10.6, SBCL 1.0.39) and is compatible with the Python bcrypt library (which I used for testing). There’s just a few small big problems.
- The Common Lisp specification defines a random function, but makes no guarantee that this is a cryptographically strong PRNG. In the absence of such a guarantee, I fear I must assume that it is not suitable. The predominant Lisp crypto package, Ironclad, lists “random number generation” in its TODO file. So I use a crude hack: I open /dev/urandom and just read bytes straight from it. This is obviously only feasible on Unix-like systems which have a /dev/urandom. I am open to suggestions for improvements.
- The C bcrypt implementations (yes, there are two independent implementations) are poorly packaged.[2] Lisp’s CFFI, much like Python’s ctypes, provides a pure-Lisp method of interacting with shared libraries. The library is simply expected to be present where the system can find it. Unfortunately, both C implementations are designed primarily for integration into libc. One of them, the OpenBSD implementation, is integrated with OpenBSD’s libc. While it seems some wrappers, like py-bcrypt have extracted it, I didn’t feel confident in my ability to do this myself. I chose to use the Openwall implementation. This also provides a glibc integration, but here, the actual bcrypt implementation is already separated out. The Python wrapper targeting this implementation (yes, there are two independent Python bcrypt libraries) was useful in figuring out how to use it, since Openwall’s source lacks docs and helpful comments. Unfortunately, the provided makefile does not even build a shared library, let alone install it, so you presently have to do this yourself. In Python/Ruby, it’s relatively easy to tell the build system to build a C shared library and include it with the native library, but ASDF, the Lisp package system, appears to have no support for this (likely because Lisp so rarely needs the shim C code that Python libraries use to integrate.) For the time being, you will need to build a bcrypt shared library by hand. Some Linux distributions offer a package called ‘bcrypt’; this is usually a crypto utility that happens to use Blowfish. Completely different program with a coincidentally identical name.
How to use it
These directions worked for me on my system. I hope they will work for you.
- Download the Openwall bcrypt source, extract it in a directory of your choice, and run make.
- Now create a shared library:
gcc -shared -W1,-soname,libbcrypt.so.1 -o libbcrypt.so.1.0.4 crypt_blowfish.o x86.o(of course, replace 1 and 1.0.4 with the correct version numbers). - Place the resulting libbcrypt.so.1.0.4 somewhere where Lisp can find it. The *foreign-library-directories* variable may be useful here.
- Compile and load the code.
(cl-bcrypt:encode "foo")or, optionally, specify a strength:(cl-bcrypt:encode "foo" 12). The default strength is 10. A strength of 16 takes approximately 6 seconds to process on my 2.66Ghz Intel i7 Macbook Pro.encodeproduces the hashed password in a format that includes the random salt, so you merely have to pass the hashed password and the candidate intocheckto verify:(cl-bcrypt:check (cl-bcrypt:encode "foo") "foo"). It returns t or nil.
What about a native approach?
People in freenode’s #lisp kept suggesting that I implement bcrypt myself, perhaps using parts of the blowfish source from Ironclad. I kept trying to explain the First Law of Crypto[3], but they didn’t think much of this objection. Also, I actually went and looked at the blowfish source; it seems to differ substantially from the eksblowfish algorithm bcrypt uses. About the only thing I could reuse without modification would be the s-box constants. Again, as per the First Law, this would be unwise for me to attempt.
That said, I would gladly donate a bit of money if someone qualified wanted to implement a native version.
[1] As Thomas Ptack puts it, “To optimize Blowfish to run much faster, you’d have to contribute a major advance to cryptography. We security practioners are all ‘betting people’, and we usually like to place our bets on the side that ‘demands major advances in cryptography’.” Colin Percival has presented what may be an even stronger scheme, but as I am not a crypto professional, I don’t think I should adopt scrypt until it’s received more scrutiny. ↩
[2] Percival’s scrypt is also poorly packaged; the only download provided is for a symmetric encryption package designed as example code for the algorithm; there is certainly no easy way for an end-user to install a scrypt library. ↩
[3] The First Law of Crypto reads as follows: “Unless you are a crypto professional, you do not implement your own cryptographic primitives.” (The Second Law is the same as the first law, but with more boldface.) The hazards of violating this law are well known. ↩