{"id":50384886,"url":"https://github.com/hoytech/session-token-cpp","last_synced_at":"2026-05-30T14:30:25.917Z","repository":{"id":353176580,"uuid":"1140884010","full_name":"hoytech/session-token-cpp","owner":"hoytech","description":"SessionToken: SessionToken C++: Secure, efficient, simple random session token generation","archived":false,"fork":false,"pushed_at":"2026-04-22T18:51:37.000Z","size":27,"stargazers_count":0,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-04-22T20:24:21.540Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hoytech.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-01-23T22:01:57.000Z","updated_at":"2026-04-22T18:51:41.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/hoytech/session-token-cpp","commit_stats":null,"previous_names":["hoytech/session-token-cpp"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/hoytech/session-token-cpp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hoytech%2Fsession-token-cpp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hoytech%2Fsession-token-cpp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hoytech%2Fsession-token-cpp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hoytech%2Fsession-token-cpp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hoytech","download_url":"https://codeload.github.com/hoytech/session-token-cpp/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hoytech%2Fsession-token-cpp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33696681,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-30T02:00:06.278Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-30T14:30:25.277Z","updated_at":"2026-05-30T14:30:25.909Z","avatar_url":"https://github.com/hoytech.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SessionToken C++: Secure, efficient, simple random session token generation\n\nA modern C++20 header-only library for creating session tokens, password reset codes, temporary passwords, random identifiers, and anything else you can think of.\n\nThis is an LLM-assisted port of the [Session::Token Perl module](https://github.com/hoytech/Session-Token).\n\n## Synopsis\n\n### Simple 128-bit session token\n\n```cpp\n#include \"include/SessionToken.h\"\n\nauto token = SessionToken::Generator().get();\n// 74da9DABOqgoipxqQDdygw\n```\n\n### Keep generator around\n\n```cpp\nSessionToken::Generator generator;\n\nauto token = generator.get();\n// bu4EXqWt5nEeDjTAZcbTKY\n\nauto token2 = generator.get();\n// 4Vez56Zc7el5Ggx4PoXCNL\n```\n\n### Custom minimum entropy in bits\n\n```cpp\nauto token = SessionToken::Generator::with_entropy(256).get();\n// WdLiluxxZVkPUHsoqnfcQ1YpARuj9Z7or3COA4HNNAv\n```\n\n### Custom alphabet and length\n\n```cpp\nauto token = SessionToken::Generator::with_length(100000000, \"ACGT\").get();\n// AGTACTTAGCAATCAGCTGGTTCATGGTTGCCCCCATAG...\n```\n\n## Description\n\nWhen a `SessionToken::Generator` object is created, 32 bytes are read from the kernel's secure random number generator. These bytes are used to seed the ChaCha20 stream cipher which serves as a cryptographically secure pseudo-random number generator.\n\nOnce a generator is created, you can repeatedly call the `get()` method on the generator object and it will return a new token each time.\n\n**IMPORTANT**: If your application calls `fork()`, make sure that any generators are re-created in one of the processes after the fork since forking will duplicate the generator state and both parent and child processes will go on to produce identical tokens.\n\nAfter the generator is created, no system calls are used to generate tokens. This is one way that SessionToken helps with efficiency. However, this is only important for certain use cases (generally not web sessions).\n\nChaCha20 is a widely-used, cryptographically secure stream cipher. It is the basis of the ChaCha20-Poly1305 AEAD used in TLS and other protocols.\n\n## Building\n\n```bash\nmake test\n```\n\nRequires a C++20 compatible compiler (tested with g++).\n\n## API Reference\n\n### Constructors and Factory Methods\n\n```cpp\n// Default: 128-bit entropy, base-62 alphabet\nSessionToken::Generator gen;\n\n// Custom alphabet\nSessionToken::Generator gen(\"0123456789abcdef\");\n\n// With explicit seed (32 bytes) for deterministic generation\nstd::array\u003cuint8_t, 32\u003e seed = { /* ... */ };\nauto gen = SessionToken::Generator::with_seed(seed);\n\n// With custom entropy (bits)\nauto gen = SessionToken::Generator::with_entropy(256);\nauto gen = SessionToken::Generator::with_entropy(64, \"0123456789abcdef\");\n\n// With custom length (characters)\nauto gen = SessionToken::Generator::with_length(50);\nauto gen = SessionToken::Generator::with_length(20, \"ACGT\");\n\n// With seed and custom settings\nauto gen = SessionToken::Generator::with_seed_and_entropy(seed, 256);\nauto gen = SessionToken::Generator::with_seed_and_length(seed, 50, \"ACGT\");\n```\n\n### Methods\n\n```cpp\nstd::string get();                      // Generate a token\nconst std::string\u0026 alphabet() const;    // Get the alphabet\nsize_t token_length() const;            // Get the token length\n```\n\n### Constants\n\n```cpp\nSessionToken::Generator::default_alphabet  // \"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\"\nSessionToken::Generator::default_entropy   // 128\nSessionToken::Generator::seed_size         // 32\n```\n\n## Generators and randomness\n\nDevelopers must choose whether a single token generator will be kept around and used to generate all tokens, or if a new `SessionToken::Generator` object will be created every time a token is needed. This library accesses the kernel's random number source in its constructor for seeding purposes, but not subsequently while generating tokens.\n\nOn Linux, it uses `getrandom()`. On Windows it uses `BCryptGenRandom(..., BCRYPT_USE_SYSTEM_PREFERRED_RNG)`. On Mac OS X it uses `arc4random_buf()`. On other systems, it attempts to read from `/dev/urandom`.\n\nGenerally speaking the generator should be kept around and re-used. Probably the most important reason for this is that generating a new token from an existing generator cannot fail due to resource exhaustion. Depending on the platform, creating a new `SessionToken::Generator` object could fail (ie because it needs to open `/dev/urandom` but is out of file descriptors). If a generator object cannot be created, a C++ exception will be thrown.\n\nPrograms that re-use a generator are more likely to be portable to `chroot`ed environments where `/dev/urandom` may not be accessible (if relevant for your platform).\n\nFinally, accessing the kernel's random source frequently is inefficient because it requires making system calls.\n\nOn the other hand, re-using a generator may 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. Similarly, when starting a virtual machine a previously used entropy pool state may be restored. If an attacker is able to peek at the memory of get a glimpse into the private memory of a running process, it can predict all future tokens. For these reasons, 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.\n\nThis library will always throw an exception if it can't securely seed itself.\n\n## Custom Alphabets\n\nBeing able to choose exactly which characters appear in your token is sometimes useful. This set of characters is called the *alphabet*. **The default alphabet size is 62 characters: uppercase letters, lowercase letters, and digits** (`a-zA-Z0-9`).\n\nFor some purposes, base-62 is a sweet spot. It is 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).\n\nAlso, base-62 tokens don't use \"wacky\" characters like base-64 encodings do. These characters sometimes cause encoding/escaping problems (ie when embedded in URLs) and are annoying because often you can't select tokens by double-clicking on them.\n\nAlthough the default is base-62, there are all kinds of reasons for using another alphabet. One example is if your users are reading tokens from a print-out or SMS or whatever, you may choose to omit characters like `o`, `O`, and `0` that can easily be confused.\n\nTo set a custom alphabet, pass a string to the constructor or factory methods:\n\n```cpp\nSessionToken::Generator::with_entropy(128, \"01\").get();           // Binary\nSessionToken::Generator::with_length(32, \"0123456789abcdef\").get(); // Hex\nSessionToken::Generator::with_length(20, \"ACGT\").get();           // DNA\n```\n\n## Entropy\n\nThere are two ways to specify the length of tokens. The most primitive is in terms of characters:\n\n```cpp\nauto token = SessionToken::Generator::with_length(5).get();\n// -\u003e wpLH4\n```\n\nBut the primary way is to specify their minimum entropy in terms of bits:\n\n```cpp\nauto token = SessionToken::Generator::with_entropy(24).get();\n// -\u003e Fo5SX\n```\n\nIn the above example, 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 as follows:\n\n```\n5 * log2(62) ≈ 29.77 bits\n```\n\nSo these tokens have about 29.8 bits of entropy. Note that if we removed one character from this token, it would bring it below our desired 24 bits of entropy:\n\n```\n4 * log2(62) ≈ 23.82 bits\n```\n\n**The default minimum entropy is 128 bits.** Default tokens are 22 characters long and therefore have about 131 bits of entropy:\n\n```\n22 * log2(62) ≈ 130.99 bits\n```\n\nAn interesting observation is that in base-64 representation, 128-bit minimum tokens also require 22 characters and that these tokens contain only 1 more bit of entropy.\n\nAnother 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). A fixed token length is nice because it makes writing matching regular expressions easier, simplifies storage (you never have to store length), causes various log files and things to line up neatly on your screen, and ensures that encrypted tokens won't leak token entropy due to length.\n\nIn summary, the default token length of exactly 22 characters is a consequence of these decisions: base-62 representation, 128 bit minimum token entropy, and fixed token length.\n\n## Mod Bias\n\nSome 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.\n\nAssume 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 wish to use the alphabet `\"abc\"`.\n\nIf we use the 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`. This results in the following biased distribution for each character in the token:\n\n```\nP(a) = 2/4 = 1/2\nP(b) = 1/4\nP(c) = 1/4\n```\n\nOf course in an unbiased distribution, each character would have the same chance:\n\n```\nP(a) = 1/3\nP(b) = 1/3\nP(c) = 1/3\n```\n\nBias is undesirable because certain tokens are obvious starting points when token guessing and certain other tokens are very unlikely. Tokens that are unbiased are equally likely and therefore there is no obvious starting point with them.\n\nSessionToken provides unbiased tokens regardless of the size of your alphabet (though see the *Introducing Bias* section for a mis-use warning). It does this in the same way that you might simulate producing unbiased random numbers from 1 to 5 given an unbiased 6-sided die: Re-roll every time a 6 comes up.\n\nIn the above example, SessionToken eliminates bias by only using values of `0`, `1`, and `2`.\n\n## Efficiency of Re-Rolling\n\nThrowing away a portion of random data in order to avoid mod bias is slightly inefficient. How many bytes from ChaCha20 do we expect to consume for every character in the token? It depends on the size of the alphabet.\n\nSessionToken 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:\n\n```\nP = alphabet_size / next_power_of_two(alphabet_size)\n```\n\nFor example, with the default base-62 alphabet `P` is `62/64`.\n\nIn 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:\n\n```\nE = P*1 + (1 - P)*(1 + E)\n\nE = P + 1 + E - P - P*E\n\n0 = 1 - P*E\n\nP*E = 1\n\nE = 1/P\n```\n\nThe expected number of bytes consumed for each character is `E = 1/P`. For the default base-62 alphabet:\n\n```\nE = 1/(62/64) = 64/62 ≈ 1.0323\n```\n\nBecause of the next power of two masking optimisation, `E` will always be less than `2`. In the worst case scenario of an alphabet with 129 characters, `E` is roughly `1.9845`.\n\nThis minor inefficiency isn't an issue because the ChaCha20 implementation is quite fast and this library is very thrifty in how it uses ChaCha20's output.\n\n## Introducing Bias\n\nIf your alphabet contains the same character two or more times, this character will be more biased than a character that only occurs once. You should be careful that your alphabets don't repeat in this way if you are trying to create random session tokens.\n\n```cpp\n// Don't do this!\nSessionToken::Generator::with_length(5000, \"0000001\").get();\n// -\u003e 0000000000010000000110000000000000000000000100...\n```\n\n## Alphabet Size Limitation\n\nDue to the byte-oriented design, alphabets can't be larger than 256 characters. If you need to map tokens onto larger character sets, you can post-process the output.\n\n## Seeding\n\nThis library is designed to always seed itself from your kernel's secure random number source. You should never need to seed it yourself.\n\nHowever if you know what you're doing you can pass in a custom seed as a 32-byte array. For example, here is how to create a \"null seeded\" generator:\n\n```cpp\nstd::array\u003cuint8_t, 32\u003e seed{};  // All zeros\nauto gen = SessionToken::Generator::with_seed(seed);\n```\n\nThis is useful for testing to get reproducible output, but obviously don't do this in production because the generated tokens will be the same every time your program is run.\n\nOne 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. In this case you should acquire your own seed data from somewhere trustworthy.\n\n## See Also\n\n- [Original Perl Session::Token](https://github.com/hoytech/Session-Token)\n- [ChaCha20 cipher](https://en.wikipedia.org/wiki/Salsa20#ChaCha_variant)\n\n## License\n\n(C) 2012-2026 Doug Hoyte\n\nThis library is licensed under the MIT license.\n\nChaCha20 implementation from [github.com/983/ChaCha20](https://github.com/983/ChaCha20).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhoytech%2Fsession-token-cpp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhoytech%2Fsession-token-cpp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhoytech%2Fsession-token-cpp/lists"}