Skip to content

Password hashing Argon2id: cost per guess decides

5 min read API guides

password hashing Argon2id
password hashing Argon2id

Password hashing Argon2id is the answer to one question: how much does a single guess cost? Length and alphabet decide how many guesses exist; the function decides how much each one costs. A fast hash turns a stolen table into a few hours of GPU time, and no amount of salt changes that.

The old GLOBUS hash had this problem in a visible way. It ran three cheap hashes in sequence, used a salt built from the current year, and added a 16-bit checksum at the end. Every part of it was fast by design, and fast is the wrong property for a password.

Why salt alone does not help

Password hashing Argon2id without a slow function is still a fast function. A salt stops precomputation. It does not slow a guess: an attacker who has the salt, and in the old format the salt was public and shared, can still try billions of candidates per second against one target.

The same applies to the checksum. A truncated CRC32 at the end of the string looks like an integrity guard and provides 16 bits of it. It cannot detect a deliberate change, and it does not affect the cost of a guess at all.

Why password hashing Argon2id changed the format

Argon2id is the memory-hard function standardised in RFC 9106. It won the Password Hashing Competition and is what PHP's password_hash() implements natively.

GS3$argon2id$v=19$m=65536,t=3,p=1$c2FsdA$hash

Three parameters sit in the string. m=65536 means 64 MiB of memory per hash, t=3 means three passes, p=1 means one lane. A GPU has thousands of cores and very little memory per core, so memory cost hurts an attacker's hardware far more than it hurts a server that hashes on login.

The salt is random per call. The prefix GS3 versions the construction, so a future change does not orphan existing values.

Hashing and verifying

curl -s "https://api.globus.studio/v2/hash?input=correct-horse&type=globus"
curl -s "https://api.globus.studio/v2/hash?verify=1&input=correct-horse&digest=GS3%24argon2id%24..."
{"valid": true, "algorithm": "argon2id", "format_ok": true}

The verify call returns a boolean and the algorithm name. It does not echo the input, which keeps the request safe to log.

A host can be built without Argon2id. In that case the endpoint uses bcrypt with cost 12 over a SHA3-256 pre-hash, and the response says so in kdf and a warning field. The pre-hash keeps inputs longer than 72 bytes from being truncated, which is the one detail bcrypt gets wrong on its own. Both paths produce a GS3$ string that verifies the same way.

Choosing the cost

The default is 64 MiB and three passes, about 100 ms on the current server. For password hashing Argon2id that is a reasonable place to start: a login takes a tenth of a second, and an attacker with a stolen table pays the same tenth of a second for every candidate.

For password hashing Argon2id, raise the memory before raising the passes. A hundred passes of a cheap function still fit in cache; one pass over 256 MiB does not. Measure on the machine that will run the check, then leave a note with the date. Hardware changes, and a cost that was expensive in 2026 is ordinary in 2030.

Per-request parameters exist for that reason: memory, time and threads. Clamp them in your own code if the value comes from a user, or the parameter becomes a denial-of-service switch. The API already bounds them, from 8 MiB to 256 MiB.

What the fingerprint is for

type=fingerprint returns a keyed digest with no work factor: same input, same output, always. Use it to compare two records, deduplicate an import, or build a cache key without keeping the original value.

Do not use it for passwords. A value that costs microseconds is a value an attacker can enumerate. The field names differ on purpose, GS3F against GS3, so a mix-up is visible in the string.

Migrating old values

type=globus_legacy reproduces the old GS$ output, and the answer is marked deprecated. Keep it while old values are still checked, then hash each password again on the next successful login and drop the branch. The old format cannot be strengthened in place: its cost is fixed by the algorithm.

Password hashing Argon2id has one migration advantage over a custom scheme: the stored string carries its own parameters, so a mixed table works during the transition.

Practical rules

Hash at the highest cost your login path can afford. Never store the plaintext, and never log the candidate. Rotate any pepper or key used for domain separation separately from the database, so a database leak alone does not give an attacker the full input to the hash.

Rehash when you raise the cost, not on a schedule. The stored string carries its own parameters, so you can rehash lazily, on the next successful login, and let the table fill with the new cost over a few weeks.

The hash toolkit API page lists the parameters, the error messages and the cost limits. For generating the password itself, the strong password generator avoids the human pattern that no hash function can repair. Further reading: OWASP Password Storage Cheat Sheet.

Custom work

Need an API that does not exist yet?

Describe the task and the response format. We will estimate the endpoint, the integration and the maintenance.

Start a conversation