Radiator Server Documentation — v10.34.2

Security requirements change over time. A hash that was acceptable a few years ago may no longer meet your current policy.

Use login-time rehashing to upgrade stored password hashes gradually, without forcing a global password reset.

Why Rehash During Login

Use this approach when you need to:

  • Migrate from plaintext or weak legacy hashes to stronger formats
  • Increase PBKDF2 cost (rounds) or Argon2 cost (memory_kib, iterations, parallelism)
  • Keep backend data aligned with current security policy

With login-time rehashing, Radiator only writes a new hash after a successful authentication. Users who never log in are not modified.

Rehash Pattern

A safe rehash flow has four steps:

  1. Authenticate user against the currently stored password hash.
  2. In post-authentication, check if the stored value already matches your target format.
  3. If not, hash the verified plaintext credential (auth.password) with the target settings.
  4. Persist the new hash to your backend.

Use auth.password as the input for rehashing. This value represents the verified credential from the current PAP login attempt.

Example: Rehash to PBKDF2-SHA512

This example upgrades accounts to {pbkdf2-sha512} after successful PAP authentication.

backends {
    radiatordb "CUSTOMER_DB" {
        server "primary" {
            host "db.example.com";
            port 5432;
            database "radiator";
            username "radiator";
            password env.DB_PASSWORD;
        }

        collection "customers" {
            roles {
                read "support";
                write "ops";
            }

            key {
                field {
                    name "Username";
                    type text;
                }
            }
        }

        query "FIND_USER" {
            collection "customers";
            key aaa.identity;
        }

        put "UPDATE_PASSWORD" {
            collection "customers";
            key aaa.identity;
            bindings {
                password = auth.password | password_hash("pbkdf2_sha512", rounds=300000);
            }
        }
    }
}

aaa {
    policy "BASIC_AUTH" {
        conditions all {
            aaa.protocol == protocol.RADIUS;
        }

        handler "PAP" {
            @authentication {
                backend {
                    name "CUSTOMER_DB";
                    query "FIND_USER";
                    @once {
                        user.password = doc | jsonpath("$.password");
                    }
                }
                must pap;
            }

            @post_authentication {
                if all {
                    user.password !starts "{pbkdf2-sha512}";
                } then {
                    backend {
                        name "CUSTOMER_DB";
                        query "UPDATE_PASSWORD";
                    }
                }
            }
        }
    }
}

The @once pipeline copies the stored hash to user.password. In post-authentication, user.password still holds the stored hash, so the condition checks its format before UPDATE_PASSWORD writes the new hash.

The put operation replaces the stored document with the document that its bindings block builds. If the customer documents contain other fields, add a binding for each field that the document must keep. See radiatordb put.

For algorithm formats and option details, see Password Hashing.