#serialization #argon2 #format #password-hash #ergonomics #salt #argon2id #serde-default #byte-array #deserialize

compact_argon2

Thin wrapper around the argon2 crate to improve ergonomics and provide a smaller serialization format

3 unstable releases

Uses new Rust 2024

new 0.2.0 Feb 27, 2026
0.1.1 Feb 23, 2026
0.1.0 Feb 23, 2026

#989 in Cryptography

Apache-2.0

24KB
425 lines

compact_argon2

The ergonomics of the argon2 crate sucks. This crate provides a thin wrapper around argon2's methods and an alternate, much smaller binary serialization format. It uses whatever the default parameters of argon2 current are, but uses deserialized parameters during verification, just like the argon2 crate. It's hardcoded to use the argon2id algorithm with the default salt and output lengths because get real.

Usage

let password = b"hunter2";

// hash a password
let hash = compact_argon2::hash(password).unwrap();

// verify a password against a hash
assert!(hash.verify(password).unwrap());
// can also be written as
assert!(compact_argon2::verify(password, &hash).unwrap());

// serialize a hash into a byte array
let bytes: [u8; compact_argon2::OUTPUT_LEN] = hash.to_bytes();

// deserialize a hash from a byte array
let reconstructed = compact_argon2.from_bytes(&bytes).unwrap();
assert_eq!(hash, reconstructed);

Stability

The format may change with only a minor version bump until the 1.0.0 release, by which it should hopefully be stable.

serde compatibility

Enable the serde feature for Serialize and Deserialize impls on Hash.

#[derive(Serialize, Deserialize)]
struct User {
    pub name: String,
    pub password: compact_argon2::Hash,
}

Binary format

The Hash struct is fully stack allocated and serializes to a compact binary representation. This is the recommended format, and the default for serde. The format looks like this (made up types, all integers are big-endian):

#[repr(C, packed)]
struct Hash {
    iterations: u24be, // non-zero
    parallelism: u24be, // non-zero
    memory: u32be, // non-zero
    version: u8,
    salt: [u8; 16],
    out: [u8; 32]
}

base64 format

This crate also offers helpers to serialize/deserialize hashes in base64, if you need to store hashes as strings for whatever reason. The output is still constant size and always smaller than the PHC equivalent. This is just a wrapper around the base64 crate with the URL-safe alphabet and no padding (because it's constant size).

let hash = compact_argon2::hash(b"hunter2").unwrap();
let hash_string = hash.to_string();
let parsed_hash = hash_string.parse().unwrap();

assert_eq!(hash, parsed_hash);

// Can also be used with serde

#[derive(Serialize, Deserialize)]
struct User {
    pub name: String,
    #[serde(with = "compact_argon2::serde::base64")]
    pub password: compact_argon2::Hash,
}

sqlx::Postgres compatibility

Enable the postgres feature to use Hash with sqlx's PostgeSQL driver.

#[derive(FromRow)]
struct User {
    pub name: String,
    pub password: compact_argon2::Hash,
}

let mut user: User = sqlx::query_as("SELECT * FROM users WHERE name=?")
    .bind("AzureDiamond")
    .fetch_one(&pool)
    .await?;

let new_password = compact_argon2::hash(b"hunter2").unwrap();

sqlx::query("UPDATE users SET password=? WHERE name=?")
    .bind(&new_password)
    .bind(&user.name)
    .execute(&pool)
    .await?;

Dependencies

~1–14MB
~115K SLoC