Replying to @⁨pernia@cum.salon⁩

@pernia either way fable understood it:

Mystery solved: it's not Pleroma's invention — it's the passlib "adapted base64" (ab64) encoding, and Pleroma has to keep it for backwards compatibility with every password hash already in people's databases.

The full story:

  1. The hash format is modular crypt format (MCF). You can see it at lib/pleroma/password/pbkdf2.ex:53 — hashes are stored as $pbkdf2-sha512$<iterations>$<salt>$<hash>. This format was defined by Python's passlib library, which became the de-facto standard for pbkdf2 crypt strings.
  2. MCF strings traditionally use the alphabet ./0-9A-Za-z (inherited from old Unix crypt(3)). Standard base64's + doesn't fit that tradition, and $ is already the field separator — so passlib defined "adapted base64": standard base64, no padding, with + swapped for .. That's exactly what those two String.replace calls implement.
  3. Pleroma inherited it, then got stuck with it. Originally Pleroma used the pbkdf2_elixir (Comeonin) library, which emits passlib-compatible hashes. Commit c7cd9bd59 ("Password: Add password module") replaced that dependency with this small hand-rolled module — but it had to produce and verify byte-identical hash strings, otherwise every existing user's password would stop validating on upgrade. So the ab64 quirk was faithfully reproduced.

So the replacement isn't cryptographically meaningful at all — it's purely a serialization-alphabet convention for compatibility with passlib-style crypt strings and with Pleroma's own historical database contents. Side bonus of the format: hashes are portable — a passlib-based tool (or another passlib-compatible implementation) can verify Pleroma's hashes and vice versa.