Session::Token(3pm)
π NAME π
Session::Token β secure, efficient, simple random session token generation. That's the whole vibe. Literally what it says on the tin, bestie. π
πΈ SYNOPSIS πΈ
β¨ Simple 128-bit session token
my $token = Session::Token->new->get;
## 74da9DABOqgoipxqQDdygw
One line, one token, zero stress. Iconic behaviour honestly.
π Keep generator around
my $generator = Session::Token->new;
my $token = $generator->get;
## bu4EXqWt5nEeDjTAZcbTKY
my $token2 = $generator->get;
## 4Vez56Zc7el5Ggx4PoXCNL
Same generator, two different tokens. She's serving consistency AND freshness. π
β‘ Custom minimum entropy in bits
my $token = Session::Token->new(entropy => 256)->get;
## WdLiluxxZVkPUHsoqnfcQ1YpARuj9Z7or3COA4HNNAv
π€ Custom alphabet and length
my $token = Session::Token->new(alphabet => 'ACGT', length => 100_000_000)->get;
## AGTACTTAGCAATCAGCTGGTTCATGGTTGCCCCCATAG...
A hundred million characters of ACGT. That's not a token that's a whole genome bestie π
β¨ DESCRIPTION β¨
So Session::Token gives you a secure, efficient, and simple interface for making session tokens, password reset codes, temporary passwords, random identifiers, and honestly anything else your heart desires. β¨
When a Session::Token object gets created, 1024 bytes are read from /dev/urandom (that's Linux, Solaris, and most BSDs), or /dev/arandom (some of the older BSDs), or Crypt::Random::Source::Strong::Win32 on Windows. Those bytes get used to seed the ISAAC-32 <http://www.burtleburtle.net/bob/rand/isaacafa.html> pseudo random number generator. π±
Once a generator exists, you just keep calling the get method on the generator object and it hands you a brand new token every single time. Repeat forever, no cap. π₯
IMPORTANT: if your application callsfork, make sure your generators get re-created in one of the processes after the fork. Because forking duplicates the generator state, so both the parent and the child will go on to produce identical tokens β literally the same fate as perl'srandafter it's been seeded. π Don't let that be you.
After the generator context is created, no system calls are used to generate tokens. That's one of the ways Session::Token keeps things efficient. But real talk β this only matters for certain use cases (generally not web sessions).
ISAAC is a cryptographically secure PRNG that improves on the well-known RC4 algorithm in some important areas. Like, it doesn't have short cycles or the initial bias that RC4 has. The theoretical shortest possible cycle in ISAAC is 2**40, though no cycles that short have ever actually been found (and they probably don't exist at all, ngl). On average, ISAAC cycles are 2**8295. π
π GENERATORS AND URANDOM π
So here's the decision every dev has to make: do you keep one token generator around and use it for all your tokens, or do you make a fresh Session::Token object every time you need a token? As mentioned up above β¬οΈ, this module hits urandom in its constructor for seeding purposes, but not afterwards while generating tokens.
Generally speaking β keep the generator around and re-use it. Probably the single most important reason is that generating a new token from an existing generator cannot fail due to a full file-descriptor table. Creating a new Session::Token object for every token can fail, because (as described above) the constructor needs to open /dev/urandom, and that's not gonna succeed if all the allotted descriptors are in use, or if the read gets interrupted by a signal. In either of those events, a perl exception gets thrown. π
Programs that re-use a generator are also way more likely to be portable to chrooted environments where /dev/urandom might not even be present. And finally, hitting urandom constantly is just inefficient β it requires system calls, and (at least on linux) reading from urandom acquires a system-wide kernel lock. That's a lot of drama for a token. π€
On the other hand, re-using a generator can be undesirable, because servers are typically started immediately after a system reboot and the kernel's randomness pool might be poorly seeded at that point. Same energy with starting a virtual machine β a previously used entropy pool state might get restored. In those cases, every token you generate afterwards is derived from a weak/predictable seed. π So for that reason you might choose to:
- defer creating the generator until the first request actually comes in,
- periodically re-create the generator object,
- and/or manually handle seeding in some other way.
Programs that assume opening /dev/urandom will always succeed can end up returning session tokens based only on the contents of nulled or uninitialised memory. That is not the case with Session::Token, because its constructor will always throw an exception if it can't seed itself. π«Ά Some modern systems provide system calls with fewer failure modes β namely getentropy(2) on OpenBSD and getrandom(2) on linux. Future versions of Session::Token will likely use these system calls when available. π
π CUSTOM ALPHABETS π
Being able to choose exactly which characters show up in your token is sometimes super useful. That set of characters is called the alphabet. The default alphabet size is 62 characters: uppercase letters, lowercase letters, and digits (a-zA-Z0-9). π
For some purposes, base-62 is a total sweet spot. It's more compact than hexadecimal encoding, which helps with efficiency, because session tokens are usually transferred over the network many times during a session (often uncompressed in HTTP headers). Compact = less bytes = happy network. β¨
Also, base-62 tokens don't use "wacky" characters the way base-64 encodings do. Those characters sometimes cause encoding/escaping problems (like when they get embedded in URLs) and they're just annoying because often you can't select tokens by double-clicking on them. Not cute. π ββοΈ
Even though the default is base-62, there are all kinds of reasons to use another alphabet. One example: if your users are reading tokens off a print-out or an SMS or whatever, you might choose to omit characters like o, O, and 0 that can easily get confused with each other. π
To set a custom alphabet, just pass in either a string or an array of characters to the alphabet parameter of the constructor:
Session::Token->new(alphabet => '01')->get;
Session::Token->new(alphabet => ['0', '1'])->get; # same thing
Session::Token->new(alphabet => ['a'..'z'])->get; # character range
Constructor args can be a hash-ref too:
Session::Token->new({ alphabet => ['a'..'z'] })->get;
Both ways work, both ways slay. Pick your fighter. π₯
π¦ ENTROPY π¦
There are two ways to specify the length of tokens. The most primitive one is in terms of characters:
print Session::Token->new(length => 5)->get;
## -> wpLH4
But the primary way is to specify their minimum entropy in terms of bits:
print Session::Token->new(entropy => 24)->get;
## -> Fo5SX
In the example above, the resulting token contains at least 24 bits of entropy. Given the default base-62 alphabet, we can compute the exact entropy of a 5 character token like this:
$ perl -E 'say 5 * log(62)/log(2)'
29.7709815519344
So those tokens have about 29.8 bits of entropy. And notice β if we removed one character from this token, it would drop below our desired 24 bits of entropy:
$ perl -E 'say 4 * log(62)/log(2)'
23.8167852415475
The default minimum entropy is 128 bits. Default tokens are 22 characters long, and therefore have about 131 bits of entropy:
$ perl -E 'say 22 * log(62)/log(2)'
130.992318828511
An interesting observation: in base-64 representation, 128-bit minimum tokens also require 22 characters, and those tokens contain only 1 more bit of entropy. π€― Math is wild.
Another Session::Token design criterion is that all tokens should be the same length. The default token length is 22 characters, and the tokens are always exactly 22 characters β no more, no less. Instead of tokens that are exactly N characters, some libraries that use arbitrary precision arithmetic end up creating tokens of at most N characters. Different vibe entirely.
A fixed token length is nice because:
- it makes writing matching regular expressions easier π§
- it simplifies storage (you never have to store the length)
- it causes various log files and things to line up neatly on your screen β¨
- and it ensures that encrypted tokens won't leak token entropy due to length (see "VARIABLE LENGTH TOKENS")
In summary, the default token length of exactly 22 characters is a consequence of these three decisions: base-62 representation, 128 bit minimum token entropy, and fixed token length. Periodt. π
π MOD BIAS π
Some token generation libraries that implement custom alphabets will generate a random value, compute its modulus over the size of an alphabet, and then use this modulus to index into the alphabet to determine an output character. Sounds fine, right? It is not fine, bestie.
Assume we have a uniform random number source that generates values in the set [0,1,2,3] (most PRNGs provide sequences of bits β in other words, power-of-2 size sets) and we want to use the alphabet abc.
If we use that naΓ―ve modulus algorithm described above, then 0 maps to a, 1 maps to b, 2 maps to c, and 3 also maps to a. Which gives us this biased distribution for each character in the token:
P(a) = 2/4 = 1/2
P(b) = 1/4
P(c) = 1/4
Of course in an unbiased distribution, every character would have the same chance:
P(a) = 1/3
P(b) = 1/3
P(c) = 1/3
Bias is undesirable because certain tokens become obvious starting points when someone's guessing tokens, while certain other tokens are wildly unlikely. Unbiased tokens are equally likely, so there's no obvious starting point with them. Perfectly balanced, as all things should be. βοΈ
Session::Token provides unbiased tokens regardless of the size of your alphabet (though do peep the "INTRODUCING BIAS" section for a mis-use warning π). It pulls this off the same way you'd simulate producing unbiased random numbers from 1 to 5 given an unbiased 6-sided die: re-roll every time a 6 comes up. π²
In the example above, Session::Token eliminates bias by only using values of 0, 1, and 2 (the t/no-mod-bias.t test contains some more notes on this topic β go read it if you're that girl π
).
Note that mod bias can be made arbitrarily small by increasing the amount of data consumed from a random number generator (provided that arbitrary precision modulus is available). Because this module fundamentally avoids mod bias, it can use each of the 4 bytes from an ISAAC-32 word for a separate character (excepting "re-rolls"). Efficient AND unbiased. She has it all. π
β¨ EFFICIENCY OF RE-ROLLING β¨
okay so bestie, throwing away a lil bit of random data to avoid mod bias is slightly inefficient β but like, how inefficient are we talking? How many bytes from ISAAC do we expect to consume for every character in the token? it depends on the size of the alphabet π
Session::Token masks off each byte using the smallest power of two greater than or equal to the alphabet size minus one, so the probability that any particular byte can be used is:
P = alphabet_size / next_power_of_two(alphabet_size)
so for example, with the default base-62 alphabet, P is 62/64 β¨
In order to find the average number of bytes consumed for each character, calculate the expected value E. There is a probability P that the first byte will be used and therefore only one byte will be consumed, and a probability 1 - P that 1 + E bytes will be consumed:
E = P*1 + (1 - P)*(1 + E)
E = P + 1 + E - P - P*E
0 = 1 - P*E
P*E = 1
E = 1/P
So for the default base-62 alphabet, the average number of bytes consumed for each character in a token is:
E = 1/(62/64) = 64/62 β
1.0323
Because of the next power of two masking optimisation described above, E will always be less than 2. In the worst case scenario of an alphabet with 129 characters, E is roughly 1.9845 π«Ά
This minor inefficiency isn't an issue because the ISAAC implementation used is quite fast and this module is very thrifty in how it uses ISAAC's output. periodt π
π INTRODUCING BIAS π
okay so if your alphabet contains the same character two or more times, that character will be more biased than a character that only occurs once. ngngl you should be careful that your alphabets don't repeat in this way if you're trying to create random session tokens, bestie π
However, if you wish to introduce bias, this library doesn't try to stop you. (maybe it should print a warning?? just saying π)
Session::Token->new(alphabet => '0000001', length => 5000)->get; # don't do this
## -> 0000000000010000000110000000000000000000000100...
Due to a limitation discussed below, alphabets larger than 256 aren't currently supported so your bias can't get very granular π
Aside: if you have a constant-biased output stream like the above example produces, then you can re-construct an un-biased bit sequence with the von neumann algorithm π«. This works by comparing pairs of bits. If the pair consists of identical bits, it is discarded. Otherwise the order of the different bits is used to determine an output bit, ie 00 and 11 are discarded but 01 and 10 are mapped to output bits of 0 and 1 respectively. This only works if the bias in each bit is constant (like all characters in a Session::Token are).
π ALPHABET SIZE LIMITATION π
Due to a limitation in this module's code, alphabets can't be larger than 256 characters. Everywhere the above manual says "characters" it actually means bytes. This isn't a Unicode limitation per se, just the maximum size of the alphabet. If you like, you can map tokens onto new alphabets as long as they aren't more than 256 characters long π¦
Here's how to generate a 128-bit minimum entropy token using the lowercase greek alphabet (note that both forms of lowercase sigma are included which may not be desirable):
use utf8;
my $token = Session::Token->new(alphabet => [map {chr} 0..25])->get;
$token = join '', map {chr} map {ord($_) + ord('Ξ±')} split //, $token;
# ΟΟνδαΟξδββΟδοΟΞ»ΟΟΞΌΞ³ΟΟΞ΄Ο΢ΡιΡΟ
Here's an interesting way to generate a uniform random integer between 0 to 999 inclusive:
0 + Session::Token->new(alphabet => ['0'..'9'], length => 3)->get
If you wanted to natively support high code points, there is no point in hard-coding a limitation on the size of Unicode or even the (higher) limitation of perl characters. Instead, arbitrary precision "characters" should be supported with bigint. Here's an example of something similar in lisp: isaac.lisp <http://hcsw.org/downloads/isaac.lisp> β
This module is not however designed to be the ultimate random number generator and at this time I think changing the design as described above would interfere with its goal of being secure, efficient, and simple. no notes, iconic π
π TOKEN TEMPLATES π
String::Random has a method called randpattern where you provide a pattern that serves as a template when creating the token. You define the meaning of 1 or more template characters and each one that occurs in the pattern is replaced by a random character from a corresponding alphabet β¨
Andrew Beverley requested this feature for Session::Token and I suggested approximately the following:
use Session::Token;
sub token_template {
my (%m) = @_;
%m = map { $_ => Session::Token->new(alphabet => $m{$_}, length => 1) } keys %m;
return sub {
my $v = shift;
$v =~ s/(.)/exists $m{$1} ? $m{$1}->get : $1/eg;
return $v;
};
}
In order to use token_template you should pass it key-vaue pairs of the different token characters and the alphabets they represent. It will return a sub that should be passed the template pattern and it will return the resulting random tokens π«Ά
For example, here is how to create UUID version 4 https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_.28random.29 tokens:
sub uuid_v4_generator {
my $t = token_template(
x => [ 0..9, 'a'..'f' ],
y => [ 8, 9, 'a', 'b' ],
);
return sub {
return $t->('xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx');
}
}
uuid_v4_generator returns a generator function that will return tokens of the following form:
1b782499-9913-4726-a80a-25e7b2221a7c
90f85a64-d826-43bf-98e7-94ba87406bfb
b8b73175-3cce-4861-b43b-3dec5ed5d641
3afb64ab-6de3-4647-bbff-eb94dfa7d4b0
447d2001-2aec-4d32-9910-8c289ae34c48
Note that characters in the pattern which don't have template characters defined (- and 4 in the above example) are passed through to the output token π
πΈ SEEDING πΈ
This module is designed to always seed itself from your kernel's secure random number source. You should never need to seed it yourself π
However if you know what you're doing you can pass in a custom seed as a 1024 byte long string. For example, here is how to create a "null seeded" generator:
my $gen = Session::Token(seed => "\x00" x 1024);
This is done in the test-suite to compare against Jenkins' reference ISAAC output, but obviously don't do this in regular applications because the generated tokens will be the same every time your program is run π
One valid reason for manually seeding is if you have some reason to believe that there isn't enough entropy in your kernel's randomness pool and therefore you don't trust /dev/urandom. In this case you should acquire your own seed data from somewhere trustworthy (maybe /dev/random or a previously stored trusted seed) π§
π¦ VARIABLE LENGTH TOKENS π¦
As mentioned above, all tokens produced by a Session::Token generator are the same length. If you prefer tokens of variable length, it is possible to post-process the tokens in order to achieve this so long as you keep some things in mind β
If you randomly truncate tokens created by Session::Token, be careful not to introduce bias. For example, if you choose the length of the token as a uniformly distributed random length between 8 and 10, then the output will be biased towards shorter token sizes. Length 8 tokens should appear less frequently than length 9 or 10 tokens because there are fewer of them π
Another approach is to eliminate leading characters of a given value in the same way as leading 0s are commonly eliminated from numeric representations. Although this approach doesn't introduce bias, the tokens 1 and 01 are not distinct so it does not increase token entropy given a fixed maximum token length which is the main reason for preferring variable length tokens. The ideal variable length algorithm would generate both 1 and 01 tokens (with identical frequency of course) π«
Implementing unbiased, variable-length tokens would complicate the Session::Token implementation especially since you should still be able to specify minimum entropy variable-length tokens. Minimum entropy is the primary input to Session::Token, not token length. This is the reason that the default token length of 22 isn't hard-coded anywhere in the Session::Token source code (but 128 is) π₯
The final reason that Session::Token discourages variable length tokens is that they can leak token information through a side-channel. This could occur when a message is encrypted but the length of the original message can be inferred from the encrypted ciphertext π
π BUGS π
- Should check for biased alphabets and print warnings π
- Would be cool if it could detect forks and warn or re-seed in the child process (without incurring
getpidoverhead). - There is currently no way to extract the seed from a Session::Token object. Note when implementing this: The saved seed must either store the current state of the ISAAC round as well as the 1024 byte
randslarray or else do some kind of minimum fast forwarding in order to protect against a partially duplicated output-stream bug π - Doesn't work on perl 5.6 and below due to the use of
:raw(thanks CPAN testers). It could probably usebinmodeinstead, but meh π₯± - On windows we use Crypt::Random::Source::Strong::Win32 which has a big dependency tree. We should instead use a slimmer module like Crypt::Random::Seed.
π COMMAND-LINE APP π
There is a command-line application called App::Session::Token which is a convenience wrapper around Session::Token. You can generate session tokens by running the session-token binary β¨
$ echo "Your password is `session-token`"
Your password is 8Yom6z4AeB1RXxCGzklJFt
It supports all the options of this module via command line parameters, and multiple session tokens can be generated with the --num (aka -n) switch. For example:
$ session-token --alphabet ABC --entropy 32 --num 5
BACAACABCCCCAACBBBCAB
BCBACACBBCACCBABABCBA
ABBBCBABBACBBBCBBBCCA
AACCBBBCCAAACBABACABC
CCABCABBCCCAACAAACCAA
π SEE ALSO β aka the group chat comparing everyone π
Okay bestie, here's the whole cast of characters, and let's be real, we're comparing π
- The
Session::Tokengithub repo βhttps://github.com/hoytech/Session-Token App::Session::Token- Presentation for Toronto Perl Mongers β
https://www.youtube.com/watch?v=c2KZBTtrmZE?start=3705
There are lots of different modules for generating random data out there. And fr fr, if any of the characterisations below are inaccurate or out-of-date, pls file a github issue and the author will fix them. No drama, just accuracy β¨
perl's own rand() β the lowkey sketchy one π
Like this module, perl's rand() function implements a user-space PRNG seeded from /dev/urandom. BUT. And this is the big but. perl's rand() is not secure. Perl doesn't specify a PRNG algorithm at all. On linux, whatever it is is seeded with a mere 4 bytes from /dev/urandom. Four. That's nothing. It's giving crumbs π
Data::Token β the one that did NOT pass the vibe check π©
Data::Token is the first thing the author saw when looking around on CPAN. It has an inflexible and unspecified alphabet. It tries to get its source of unpredictability from UUIDs and then hashes these UUIDs with SHA-1. That's bad design, and here's why: some standard UUID formats aren't designed to be unpredictable at all. This is literally acknowledged in RFC 4122 section 6:
"Do not assume that UUIDs are hard to guess; they should not be used as security capabilities (identifiers whose mere possession grants access)."
With certain UUIDs, knowing a target's MAC address or the rough time the token was issued may help you predict a reduced area of token-space to concentrate guessing attacks upon. Big yikes π«£
The author doesn't know if Data::Token uses these types of UUIDs or the potentially secure "version 4" UUIDs, but because this wasn't addressed in the documentation and because of an apparent misapplication of hash functions (like, if you really had an unpredictable UUID, there would be no need to hash), the author does not feel good about using this module. And honestly? Same π
Math::Random::Secure and Crypt::URandom β solid, but with caveats πΈ
There are several decent random number generators like Math::Random::Secure and Crypt::URandom, but they usually don't implement alphabets, and some of them require you to open or read from /dev/urandom for every chunk of random bytes. Also, a lil fun fact for you: Math::Random::Secure does prevent mod bias in its random integers and could be used to implement unbiased alphabets (slowly). Keyword: slowly π
String::Random β cute language, questionable foundations π
String::Random has a neat regexp-like language for specifying random tokens which is more flexible than alphabets. Iconic feature. However, it uses perl's rand() and its documentation fails to discuss performance, bias, or security. See the "TOKEN TEMPLATES" section for a similar feature π
String::Urandom β half ate, half left on read π
String::Urandom has alphabets, but it uses the flawed mod algorithm described above and opens /dev/urandom for every token. So close bestie π«Ά
The rand()/mersenne-twister girlies π
There are other modules like Data::Random, App::Genpass, String::MkPasswd, Crypt::RandPasswd, Crypt::GeneratePassword, and Data::SimplePassword but they use insecure PRNGs such as rand() or mersenne twister, don't adequately deal with bias, and/or don't let you specify generic alphabets. Not the slay we were hoping for.
Bytes::Random::Secure β the respectable one π
Bytes::Random::Secure has alphabets (aka "bags"), uses ISAAC, and avoids mod bias using the re-roll algorithm. It is much slower than Session::Token (even when using Math::Random::ISAAC::XS) but does support alphabets larger than 256 and might work in environments without XS. Respect where respect is due π
Reviews & remixes β¨
- Neil Bowers has conducted a 3rd party review of various token/password generation modules including
Session::Tokenβhttp://neilb.org/reviews/passwords.html - Leo Zovic has created a Common Lisp implementation of session-token β
https://github.com/Inaimathi/session-token
π AUTHOR π
Doug Hoyte, "<doug@hcsw.org>"
π COPYRIGHT & LICENSE π
Copyright 2012-2016 Doug Hoyte.
This module is licensed under the same terms as perl itself.
ISAAC code:
By Bob Jenkins. My random number generator, ISAAC. Public Domain
And that's the whole manual, bestie, you made it to the end π₯³ This lil page lives in Session::Token(3pm) and comes to you courtesy of perl v5.42.3, dated 2026-08-19. Go generate some iconic tokens, stay unbiased, and never trust a UUID ππ
β¨
π¦ where this page comes from
- ubuntu stonking: libsession-token-perl 1.503-3build1 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms