PerlDancer/Dancer — pull request #997 by jfharden (2014-02-11 12:44:19+00:00)
Title: Change session ID generation to speed up and reduce chance of collisions

At work we had very occasional strange reports of a logged in user suddenly having another users credentials. I spent a while debugging this and eventually tracked it down to a collision in session IDs, we are using Dancer::Session::Memcached to share our session storage across multiple front end servers. This got me to looking at the session ID generation.

The ultimate cause of our collision was that the perl PRNG was seeded before apache forks processes (which other people around the web seem to have also experienced), with the current method of session ID generation if the PRNG is seeded with the same seed and the time difference in seconds is equal to PID A - PID B (being the PIDs of the 2 apache processes) then the session ID is identical, this is especially likely just after apache has been restarted. This comes from the method of generating the session ID being to add the various parts of variation together so

PID A + timestamp X == PID B + timestamp Y
if A = B+1 and X = Y -1

Rather than add them together it would be better to concatenate them, but this in turn would give away system information, so using a hashing algorithm without known collisions (such as SHA256) would stop these collisions.

Since I was looking at the build_id method anyway I optimised it a little and benchmarks (1million iterations of the current method and my proposed method using Benchmark) show me that on Windows, Linux, and OSX this new method is faster than the old method by a considerable margin. The main speed up comes from moving the path ordinal value generation out of the for loop (after all the directory isn't changing when the session IDs are being generated so it seemed to make sense to memoize its generation).

There doesn't seem to be any disadvantage to using Time::HiRes (even on windows it gives millisecond precision) which means that even in the event that the random seed isn't good the session ids have a huge chance still to be different.

I added the hostname in since it helps ensure session IDs will be generated differently on multiple servers which are all sharing the same session storage engine (whether that's NFS mounted file systems, Memcached etc etc).

I realise that Digest::SHA is not included in the core of perl, if it's not acceptable to create that dependency Digest::MD5 is included in the core but is known to have collisions. I could also use the old method but add in the hostname and use HiRes time instead of normal time (I can submit a different pull request with that and the optimisations).