This library implements split tokens.
A standard way to implement password reset tokens and similar is to generate a random token, send it to your user, usually in a URL, and store it in your database. When the user tries to use the token, you check that the token is in the database.
Split tokens improve upon this design. You still generate a random token and send it to your user, but you store it in the database in two parts called selector and verifier. Selector is stored as-is and verifier is hashed before saving it. The full token is not stored in your database since the verifier is hashed.
When the user wants to use a token, you split it and use the selector to look up the verifier hash from the database. Then you hash the user-supplied verifier and check that it matches the stored hash. This achieves two things:
- It prevents timing attacks. Typically database lookup is suspectible to timing attacks, but a proper hash comparison is not.
- An attacker with read access to your token database cannot use the tokens themselves.
This basically the same as why you hash users' passwords with a password hashing function before storing them in the database, except that we have a single randomly-generated token instead of a username and a password.
Generating a token:
(require '[split-token.core :as split-token])
(split-token/generate)
;; {:selector "gEHOHXOFanTHp43CbFWdCw",
;; :verifier-hash "m0UYbYs2dhbeGHnsjCLY4w",
;; :token "gEHOHXOFanTHp43CbFWdC0yKajTVYk58FpXoCt9FyQY"}
Validating a token:
(let [token "gEHOHXOFanTHp43CbFWdC0yKajTVYk58FpXoCt9FyQY"]
(split-token/get-selector token))
;; "gEHOHXOFanTHp43CbFWdCw"
;; At this point you'd look up the verifier hash from the database based on the selector.
;; Then you can verify it:
(let [token "gEHOHXOFanTHp43CbFWdC0yKajTVYk58FpXoCt9FyQY"
verifier-hash "m0UYbYs2dhbeGHnsjCLY4w"]
(split-token/valid? token verifier-hash))
;; true
- The library uses 32-byte tokens with 16-byte selectors and 16-byte verifiers.
- The hash is 128-bit BLAKE2b.
- To make things easy, all the functions return URL-safe Base64-encoded strings.
- There's no configuration. The implementation is one short file, though, so you could vendor it.
- This library builds on buddy-core, which builds on Bouncy Castle.
Run tests:
bin/kaocha
# Automatically run tests when files change
bin/kaocha --watch
Deployment:
export CLOJARS_USERNAME=...
export CLOJARS_PASSWORD=...
clj -T:build jar
clj -T:build deploy
Copyright 2021 Miikka Koskinen. Distributed under the terms of ISC license, see LICENSE
.