Update
Thanks to eadmund and glv2 the issue described in this article is now fixed and documented clearly. The fixed version of Ironclad should find its way into the Quicklisp release soon.
Note that there are other objects in Ironclad which are still not thread-safe. Refer to the documentation on how to handle them.
Whenever you write a program that uses cryptographic tools you will use cryptographically secure random numbers. Since most people never write security related software they may be surprised to learn how often they are in this situation.
Cryptographically secure pseudo random number generators (PRNG) is a core building block in cryptographic algorithms which include things like hashing algorithms and generation algorithms for random identifiers with low probably of repetition. The two main uses are to securely store hashed passwords (e.g. PBKDF2, bcrypt, scrypt) and to generate random UUIDs. Most web applications with user accounts fall into this category and many other non-web software too.
If your program falls into this group you are almost certainly using Ironclad. The library tries hard to be easy to use even for those without cryptography knowledge. To that end it uses a global PRNG instance with a sensible setting for each particular target OS and expects that most users should never bother to learn about PRNGs.
The Ironclad documentation is clear, don’t change the default PRNG! First “You should very rarely need to call make-prng; the default OS-provided PRNG should be appropriate in nearly all cases.” And then “You should only use the Fortuna PRNG if your OS does not provide a sufficiently-good PRNG. If you use a Unix or Unix-like OS (e.g. Linux), macOS or Windows, it does.”
These two quotes are sufficient to discourage any idle experimentation with PRNG settings, especially if you only want to get the password hashed and move on.
The ease of use comes to a sudden stop if you try to use PRNGs in a threaded application on CCL. The first thread works fine but all others raise error conditions about streams being private to threads. On SBCL the problem is much worse. No error is signaled and everything appears to work but the PRNG frequently returns repeated “random” numbers.
These repeated numbers may never be detected if they are only used for password hashing. If however you use random UUIDs you may from time-to-time get duplicates which will cause havoc in any system expecting objects to have unique identifiers. It will also be extremely difficult to find the cause of the duplicate IDs.
How often do people write multi-threaded CL programs? Very often. By default Hunchentoot handles each HTTP request in its own thread.
The cause of this problem is that Ironclad’s default PRNG, :OS
, is not
implemented to be thread safe. This is the case on Unix where it is a stream to
/dev/urandom
. I have not checked the thread-safety on Windows where it uses
CryptGenRandom
.
Solutions
There exists a bug report for Ironclad about the issue but it won’t be fixed.
Two options to work around the issue are:
-
Change the global
*PRNG*
to Fortuna(setf ironclad:*PRNG* (ironclad:make-prng :fortuna))
- Advantage:
- It is quick to implement and it appears to be thread safe.
- Disadvantage:
:FORTUNA
is much slower than:OS
-
Use a thread-local instance of
:OS
(make-thread (let ((ironclad:*PRNG* (ironclad:make-prng :os))) (use-prng)))
- Advantage:
:OS
is significantly faster that:FORTUNA
. It is also Ironclad’s recommended PRNG.- Disadvantages:
- When the PRNG is only initialized where needed it is easy to miss places where it should be initialized..
- When the PRNG is initialized in every thread it causes unnecessary processing overhead in threads where it is not used.
Summary
It is not safe to use Irondclad dependent libraries in multi-threaded programs with the default PRNG instance. On SBCL it may appear to work but you will eventually run into hard-to-debug problems with duplicate “random” numbers. On CCL the situation is better because it will signal an error.