A Practical Procedure for Identifying the Scheme Behind a Hash String

· Updated: · · Hash, Security, Passwords, Legacy Asset Reuse, Technical Investigation

Revision history (1 updates, last updated Sep 1, 2026)

A log of the changes made to this article. Where a pre-update version was archived, it stays readable at a permanent DOI link.

Retranslated as a full translation of the Japanese original. The previous English version was an abridgement that carried only part of the source, so sections, tables, Mermaid diagrams, figure captions and FAQ entries were missing. All of them have been restored to match the Japanese original, and the technical claims are the same as in the Japanese version. Read the version before this update (DOI: 10.5281/zenodo.21614607)
First published
Cite this article(DOI: 10.5281/zenodo.21614606)

This article is archived on Zenodo. Below are both the DOI that always resolves to the latest version and the DOI pinned to the version you are reading.

Go Komura (2026). A Practical Procedure for Identifying the Scheme Behind a Hash String. KomuraSoft LLC. https://doi.org/10.5281/zenodo.21614606 https://comcomponent.com/en/blog/2026/04/10/000-hash-format-identification/

DOI (latest version)
10.5281/zenodo.21614606
DOI (this version)
10.5281/zenodo.22220427

There are plenty of situations where you look at a string like 5f4dcc3b5aa765d61d8327deb882cf99 or $2b$12$... left in logs or a database and want to determine “what kind of hash is this?” In migrations of existing systems, investigations of authentication methods, log analysis, and integrations with third-party systems, it is not unusual to get stuck right here.

The dangerous move, though, is jumping to a conclusion based on length alone. Looking at a 64-character hex string and declaring “that’s SHA-256” is premature. SHA3-256, SHA-512/256, BLAKE2s-256, and BLAKE3’s default 32-byte output can all be the same length. Conversely, storage formats that include a prefix and parameters, like $2b$ or $argon2id$, can be identified with quite high accuracy from the string alone.

In this article we use the word hash broadly, covering not only message digests like MD5 / SHA-2 / SHA-3 but also string representations used for password storage such as bcrypt / scrypt / Argon2 / PBKDF2. The content is organized based on the RFCs, NIST publications, Linux crypt(5), Apache, Django, Spring Security, and other official materials publicly available as of April 2026.

The intended readers are people working on migrating an existing system, investigating an authentication method, or analyzing logs, who need to work out what the hash-looking string in front of them actually is. Knowing roughly what hexadecimal notation and Base64 are is enough background.

One caveat: use the procedures in this article only on strings from environments you administer yourself, or environments where you have explicit permission to investigate. Extracting the password hash of someone else’s account, or analyzing hashes on a system you have no authority over, is not justified even when the goal is investigation. The verification commands later in the article also assume you are trying them on test accounts or samples you created yourself.

Table of Contents

  1. The Conclusion First
  2. At-a-Glance Identification Tables
  3. The Practical Identification Procedure
  4. Common Misidentifications
  5. The Verification Order When You Need 100% Certainty
  6. Summary
  7. Services Related to This Theme
  8. References

In the diagram a solid line marks a relation that always holds and a dashed line marks a conditional one (the conditions are given per relation on the detail page). The full list of relations (27 in total, with evidence and certainty) and the definitions of the main concepts are collected on the knowledge map detail page (in Japanese). Data: JSON-LD / Turtle

1. The Conclusion First

Here is the short version up front.

  • Storage formats with prefixes or separators are fairly easy to identify from the string alone. Examples: $argon2id$..., $2b$..., $5$..., $6$..., {SHA}..., pbkdf2_sha256$...

  • Plain hex strings or bare Base64 usually only get you as far as “narrowing the candidates.” Example: 32 hex = could be MD5, but could also come from MD4 or an NT hash

  • The character set is as important as the length. If you see + / =, it looks like RFC 4648 Base64; if it contains . and is $-delimited, it looks like the crypt(3) family - distinctions like these really work.

  • If you want 100% certainty, you need context. Whether it lives in /etc/shadow, .htpasswd, Django’s auth_user, or Spring Security changes the story.

In short, “schemes you can identify from the string alone” and “schemes where the string only gives you a candidate set” are two different things. Just keeping these separate changes how an investigation proceeds.

2. At-a-Glance Identification Tables

Before reading the tables, it is worth sorting out four terms that appear in this article as names of formats. They all get used in similar contexts, but they refer to different things.

Term Full name What it refers to
crypt(3) - The Unix password hashing function itself. It is a C library function, documented in section 3 of the manual (library functions), which is why it is written this way
crypt(5) - The man page describing the string format that this function reads and writes. It sits in section 5 of the manual (file formats), and layouts such as $6$salt$hash are documented there
MCF Modular Crypt Format The informal name for the convention of putting $id$ at the front to indicate the scheme. There is no single specification document; the name settled into use as more crypt(3)-family implementations appeared
PHC string format Password Hashing Competition string format A specification that reorganizes MCF. It pins down how versions and parameters are written, as in $argon2id$v=19$m=65536,t=3,p=4$salt$hash

Roughly speaking, crypt(3) is the function, crypt(5) is the specification of its output format, MCF is the informal name for that format, and PHC string format is that format written down properly. When the tables below say “PHC string format” or “crypt family,” read them with this distinction in mind.

2.1 Formats nearly pinned down by a prefix or format marker

“Confidence” in the table is used in this sense.

  • Strong: nearly identifiable from the string alone
  • Medium: candidates narrow considerably, but watch for implementation differences
  • Weak: cannot be determined from length or appearance alone
Visual feature First suspect Confidence Notes Example
$argon2id$... Argon2id Strong PHC string format. Often followed by v=, m=, t=, p= $argon2id$v=19$m=65536,t=3,p=4$MDEyMzQ1Njc4OWFiY2RlZg$uKZLaN6muIyoyIYr5waqw3y+zaDbe9aLSPj6Ln/rbz4
$argon2i$... Argon2i Strong Same as above $argon2i$v=19$m=65536,t=3,p=4$MDEyMzQ1Njc4OWFiY2RlZg$Kx1koF/7n8EytGJYTS5krh+ag+FlG5ksM4xOsjOSDvo
$argon2d$... Argon2d Strong Same as above $argon2d$v=19$m=65536,t=3,p=4$MDEyMzQ1Njc4OWFiY2RlZg$HLIGA+T1bwK8akx3LGOco+Df+PvxX6cIXhycO7O7t6c
$2a$... / $2b$... / $2y$... bcrypt Strong 2-digit cost + crypt-family alphabet $2b$12$9YQ2u/e5Y/ArOnG.gJKxK.0makLATcYLP1q.Nsabzrw7XErYCfoYO
$1$... md5crypt Strong The Unix-family MD5 password storage format $1$vA7mQ9xZ$Erz32JUFnZ9991KdU5.N3.
$5$... sha256crypt Strong Not plain SHA-256 $5$rounds=5000$N3v8Kx2Lq9Rt$uOTla5GAHaRH2aHlUSjkrZUBCuFiahQZ36O/seB39r3
$6$... sha512crypt Strong Not plain SHA-512 $6$rounds=5000$N3v8Kx2Lq9Rt$6LUcSUAELX3aC/.60pTB.TFLTQi1mOGRCwKqNCqtRSaXjorxj01HJ9oNni97Kci1uDt7a/Kn4t3OS20Dw/.vi1
$7$... scrypt (crypt family) Strong Seen in Linux crypt(5)-family implementations $7$CU..../....k2XAnEHBqQ1Ct2aMXFKNa/$y3Q0e/UlCHacIGWQshgvvz6UIbP.BCja.5BfVWP2Ml8
$y$... yescrypt Strong Seen on newer Linux systems $y$j9T$k2XAnEHBqQ1Ct2aMXFKNa/$OVYXzjlkiQpWT/F1CUE0JrvV4phLY8FB.ofDttnrSQ7
$apr1$... Apache APR1-MD5 Strong Often seen in .htpasswd $apr1$vA7mQ9xZ$ZE64.ohiyK11sPZmtnJZQ.
{SHA}... Base64 representation of a SHA-1 digest Strong Often seen in Apache / LDAP contexts {SHA}VBPuJHI7uixaa6LQGWx4s+5GKNE=
{SSHA}... salted SHA-1 Strong LDAP family {SSHA}/OczD0GNNkOAUPbYhA3L9fjmcyBCbHVlTWVzYTQyIQ==
{MD5}... / {SMD5}... MD5 / salted MD5 Strong LDAP family {MD5}X03MO1qnZdYdgyfeuILPmQ==
{SMD5}fOn1rOv4ZH0OrO/KT9H0fEJsdWVNZXNhNDIh
pbkdf2_sha256$... PBKDF2-HMAC-SHA256 Medium to Strong Django and others prepend the format name pbkdf2_sha256$600000$N3v8Kx2Lq9Rt$CLxGB+zTiV1IdOt2y4m9JpaAONzHuRTOd96xKQwRQAs
{bcrypt}$2b$... bcrypt Strong Wrapped in Spring Security’s {id} prefix {bcrypt}$2b$12$9YQ2u/e5Y/ArOnG.gJKxK.0makLATcYLP1q.Nsabzrw7XErYCfoYO
{pbkdf2}... / {scrypt}... Implementation-labeled schemes Medium to Strong Spring Security and similar; identify the wrapper format rather than the underlying algorithm {pbkdf2}sha256$600000$Qmx1ZU1lc2E0MiE$4eNuai1qNkgs1kXz3+tBUMzAexVsSUz9SrQKEhbk0Cw
{scrypt}ln=14,r=8,p=1$Qmx1ZU1lc2E0MiE$xAgBRhXbMtHB1UHUR0br5bI+1XdXWKbwauiFv5VRQBY

The point of this table is that formats where the first few characters carry meaning are strong. Strings delimited by $...$ in particular are very likely Unix crypt(3) / MCF / PHC-family formats, and it is faster to look at the prefix before the length.

2.2 Narrowing candidates by length for plain hex / Base64

This table is for bare digest strings without a prefix. For representations containing :, -, or whitespace, first strip the separators and then count the length.

Raw byte length Hex chars Base64 chars (with / without =) Main candidates Example
4 8 8 / 6 Checksums such as CRC32 cbf43926
16 32 24 / 22 MD5, MD4, NT hash (MD4-based) 5f4dcc3b5aa765d61d8327deb882cf99
20 40 28 / 27 SHA-1, RIPEMD-160 da39a3ee5e6b4b0d3255bfef95601890afd80709
28 56 40 / 38 SHA-224, SHA-512/224, SHA3-224 d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f
32 64 44 / 43 SHA-256, SHA-512/256, SHA3-256, BLAKE2s-256, BLAKE3’s default 32-byte output e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
48 96 64 / 64 SHA-384, SHA3-384, BLAKE2b-384 38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b
64 128 88 / 86 SHA-512, SHA3-512, BLAKE2b-512, Whirlpool cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e

How to read the “Base64 chars” column: this column lists two numbers, one with = padding and one without it. RFC 4648 Base64 aligns output to multiples of four characters, so one or two = characters are appended only when the raw byte length is not a multiple of three. JWTs and URL-embedded values often drop that =, which means the same digest can appear as either 43 or 44 characters. Conversely, when the length is a multiple of three, as with 48 bytes, no padding is generated, so both numbers are the same and the cell reads 64 / 64. When you narrow candidates by length, always check against both numbers.

The key takeaway here is that a matching length does not uniquely determine the scheme. Hex strings of 32 / 64 / 128 characters in particular have many candidates, and declaring a winner from this alone misses often.

2.3 Classic examples that trip people up

What the string looks like Common snap judgment The right way to read it Example
5f4dcc3b5aa765d61d8327deb882cf99 Definitely MD5 Looks like MD5, but could also be MD4, an NT hash, or an app-specific use of MD5 8846f7eaee8fb117ad06bdd830b7586c
64 hex like 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 Definitely SHA-256 SHA-256 is a candidate, but SHA3-256 / SHA-512/256 / BLAKE2s-256 / BLAKE3 are also possible e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
$6$rounds=5000$salt$hash A hex representation of SHA-512 No - it is a password hash string called sha512crypt $6$rounds=5000$N3v8Kx2Lq9Rt$6LUcSUAELX3aC/.60pTB.TFLTQi1mOGRCwKqNCqtRSaXjorxj01HJ9oNni97Kci1uDt7a/Kn4t3OS20Dw/.vi1
{SHA}VBPuJHI7uixaa6LQGWx4s+5GKNE= Some kind of “SHA” In Apache / LDAP contexts this usually means a Base64-encoded SHA-1 digest {SHA}VBPuJHI7uixaa6LQGWx4s+5GKNE=
{bcrypt}$2b$12$... A proprietary scheme called {bcrypt} bcrypt wrapped by Spring Security {bcrypt}$2b$12$9YQ2u/e5Y/ArOnG.gJKxK.0makLATcYLP1q.Nsabzrw7XErYCfoYO

3. The Practical Identification Procedure

From here, let’s organize how to actually look at a string, step by step. The recommended order is prefix, separators, character set, length, context.

3.1 Look at the leading characters first

The first one to ten characters narrow things down considerably.

  • $argon2id$ / $argon2i$ / $argon2d$ Strongly suspect Argon2’s PHC string format. The components are easy to follow in the Example column of section 2.1.

  • $2a$ / $2b$ / $2y$ Strongly suspect bcrypt.

  • $1$ / $5$ / $6$ / $7$ / $y$ Suspect a Unix crypt(3)-family password hash.

  • {SHA} / {SSHA} / {MD5} / {SMD5} Suspect LDAP / Apache-family representations.

  • {bcrypt} / {pbkdf2} / {scrypt} Suspect an implementation-labeled storage format like Spring Security’s.

The trick here is to look not just at the underlying algorithm but at the storage format. For example, $6$ is not “a SHA-512 digest” - it is “a password hash string that uses SHA-512.” Mixing these up will skew the rest of the investigation.

3.2 Look at the number of separators

Next, look at separators such as $, :, {}, ,, and =.

  • Multiple $ characters Suspect a format that carries parameters, salt, and hash together. Argon2, bcrypt, sha256crypt, and sha512crypt are typical.

  • Starts with {name} Suspect a wrapper that names the scheme explicitly, as in LDAP / Spring Security.

  • Shapes like algo:salt:hash or algo$iterations$salt$hash Suspect a framework- or app-specific format. Django’s pbkdf2_sha256$iterations$salt$hash is the classic example.

The more separators a string has, the easier the scheme is to identify. Conversely, a single lump of bare hex or Base64 stays quite ambiguous.

3.3 Look at the character set

The character set is as important as the length.

Hex representation

If the string consists only of [0-9a-fA-F], suspect a hex representation first. In this case, character count / 2 = raw byte length.

  • 32 hex to 16 bytes
  • 40 hex to 20 bytes
  • 64 hex to 32 bytes
  • 128 hex to 64 bytes

RFC 4648 Base64 / Base64url

If you see + / =, suspect ordinary Base64 first. If you see - _, suspect Base64url. Padding = may be omitted, so lengths come in “either is possible” pairs like 43 / 44 and 86 / 88.

crypt-family radix64

If . and / appear and the string is delimited with $...$, it is more natural to suspect a crypt-family alphabet than ordinary Base64. bcrypt, sha256crypt, sha512crypt, md5crypt, yescrypt, scrypt, and friends use this family of character sets.

This is unglamorous but very effective. If you read “there’s a . in it, so it’s broken Base64”, you will easily overlook bcrypt and the crypt(3) family.

3.4 Count the length

After the character set, look at the length. The reasoning is simple.

  • For hex: raw byte length = character count / 2
  • For Base64: character count is roughly 4 x ceil(raw byte length / 3) Note that omitting the = padding makes it 0 to 2 characters shorter

At this stage you narrow the candidates. But it is safer not to make the leap of “64 hex, therefore confirmed SHA-256.”

3.5 Confirm with context

What clinches it in the end is context. This is where you approach 100%.

  • Found in /etc/shadow Suspect Linux password hash formats such as $y$, $6$, $5$, $1$

  • Found in .htpasswd Suspect Apache-family formats such as $apr1$, {SHA}, bcrypt

  • Found in Django settings or auth_user.password Suspect Django formats such as pbkdf2_sha256$... or argon2$...

  • Found in a Spring Security authentication table Suspect {id}-prefixed formats such as {bcrypt}... or {pbkdf2}...

  • 32 hex found around SMB / AD integration Seriously consider an NT hash (MD4-based)

In practice, looking at the product, framework, or configuration file the string came from is often faster than staring at the string itself.

4. Common Misidentifications

4.1 Hard-coding 64 hex = SHA-256

This one is very common. SHA-256 is of course a strong candidate, but multiple schemes produce the same 32-byte output. SHA3-256, SHA-512/256, BLAKE2s-256, and BLAKE3’s default output are all the same length.

Length is material for building a candidate set, not material for a verdict.

4.2 Mistaking $6$ for plain SHA-512

$6$... is the sha512crypt prefix. It is not “a hex SHA-512 digest” - it is a password hash string that includes a salt and rounds.

Likewise:

  • $5$ is sha256crypt
  • $1$ is md5crypt

The moment a prefix is present, it is no longer “just a digest.”

4.3 Reading {SHA} as “either SHA-256 or SHA-512”

In Apache or LDAP contexts, {SHA} does not vaguely mean “the SHA family.” In most cases it means a Base64-encoded SHA-1 digest. {SSHA} is salted SHA-1.

If you handle {SHA} loosely as “some kind of SHA” based on appearance, you will get verification code and migration logic wrong.

4.4 Treating password hashes and content hashes as the same thing

They are both “hash strings,” but their purposes differ.

  • Digests for file integrity checks
  • Digests for API signing
  • Hash / KDF strings for password storage

These three look similar but are handled differently. Password hashes in particular often embed salt, rounds, memory cost, parallelism, and so on into the string, so the “compare raw digests” mindset will not see through them.

4.5 Forgetting XOFs and variable-length digests

SHAKE128 / SHAKE256 are XOFs, so the output length can be chosen freely. BLAKE2 also allows a configurable digest length, and BLAKE3 has extendable output as well.

In other words, the inference “this length, therefore this scheme” misses whenever it leans too hard on the assumption of classic fixed-length digests.

4.6 Treating “NTLM” and “NT hash” as the same thing

This is a matter of terminology, but it pays off considerably in Windows / AD investigations.

  • NT hash: the 16-byte value produced by encoding the password as UTF-16LE and running MD4 over it. It shows up as 32 hex digits, as in 8846f7eaee8fb117ad06bdd830b7586c. The specification defines it as NTOWFv1(Passwd, User, UserDom) = MD4(UNICODE(Passwd)). That definition appears verbatim in the NTLM v1 Authentication section of MS-NLMP, listed as reference 13.
  • NTLM: the name of the authentication protocol that uses that value as a key to run a challenge / response exchange. It is not the name of a string format.

People do say “NTLM hash” in the field and are understood, but when you write it into an identification table, NT hash (MD4-based) is the accurate label. Keeping the two apart stops the format question - “is this 32 hex MD5 or an NT hash?” - from getting mixed up with the protocol question - “is this traffic NTLM or Kerberos?”

One more thing: NT hashes have no salt. The same password always yields the same 32 hex, so the very fact that unsalted 32 hex values are lined up in a table sourced from AD is itself a clue.

5. The Verification Order When You Need 100% Certainty

Migrations and authentication integrations ultimately require certainty. When that time comes, working through the following order is what keeps things from going wrong.

5.1 Identify the storage source

First, establish where the string came from.

  • Linux shadow?
  • Apache / Nginx basic auth?
  • LDAP?
  • Django / Spring Security?
  • A custom application’s DB?

The specification of the storage source is often stronger evidence than the string alone.

5.2 Look up the “storage format” in official documentation

Next, look up the storage format, not the algorithm name.

  • Django password format
  • Spring Security password storage format
  • crypt(5) sha512crypt format
  • Apache htpasswd password formats

Searching with format / storage / encoding as keywords makes these easy to find.

5.3 If you have a known plaintext, actually verify against the candidate schemes

If you have a test account or a known plaintext, the fastest route is to compute with the candidate schemes and compare. For password hashes, this means extracting the salt and rounds from the string and recomputing.

The shape of the procedure is the same three steps for every scheme.

  1. Extract the salt and parameters from the string
  2. Recompute with the same salt and parameters against the known plaintext
  3. Check whether the resulting string is an exact match for the original

Checking a digest with no prefix

Start with the “bare digest” case. The Python 3 standard library is all you need.

# Python 3.8+ / standard library only
import base64
import hashlib

target = "5f4dcc3b5aa765d61d8327deb882cf99"   # the string to identify
plain = b"password"                            # the known plaintext

for name in ("md5", "sha1", "sha256", "sha512", "sha3_256", "blake2s"):
    digest = hashlib.new(name, plain).digest()
    if digest.hex() == target.lower():
        print("hex match:", name)
    if base64.b64encode(digest).decode() == target:
        print("base64 match:", name)

For this example the output is hex match: md5. The basic pattern is to list the candidate schemes from the table in section 2.2 straight into the for loop.

When you want to try an NT hash, you end up writing something like hashlib.new("md4", "password".encode("utf-16-le")) - but on a Python linked against OpenSSL 3.x the legacy provider is disabled by default, so this can fail with unsupported hash type md4. Confirm that in your own environment before you rely on it.

Checking crypt-family password hashes

For $1$ / $5$ / $6$ / $apr1$, you can recompute by handing the salt to openssl passwd. The examples in the table in section 2.1 are reproducible this way too.

# OpenSSL 3.x
openssl passwd -6 -salt N3v8Kx2Lq9Rt password
# $6$N3v8Kx2Lq9Rt$6LUcSUAELX3aC/.60pTB.TFLTQi1mOGRCwKqNCqtRSaXjorxj01HJ9oNni97Kci1uDt7a/Kn4t3OS20Dw/.vi1

openssl passwd -5 -salt N3v8Kx2Lq9Rt password    # sha256crypt
openssl passwd -1 -salt vA7mQ9xZ password        # md5crypt
openssl passwd -apr1 -salt vA7mQ9xZ password     # Apache APR1-MD5

If the output matches the string under investigation, that settles both the scheme and the plaintext on the spot. For strings that carry a rounds= value, you have to pass that value as well. $6$rounds=5000$... uses the default, so stating it explicitly or not produces the same digest, but for a non-default value such as rounds=100000 you must compute with that value.

On Debian / Ubuntu systems, mkpasswd from the whois package does the same job (the form is mkpasswd -m sha512crypt -S N3v8Kx2Lq9Rt password).

Checking bcrypt and Argon2

bcrypt and Argon2 encode their salt in a scheme-specific alphabet, so instead of slicing it out by hand, it is more reliable to hand the whole string to the library’s verify.

# pip install "passlib[bcrypt]" argon2-cffi
from passlib.hash import argon2, bcrypt

samples = [
    (bcrypt, "$2b$12$9YQ2u/e5Y/ArOnG.gJKxK.0makLATcYLP1q.Nsabzrw7XErYCfoYO"),
    (argon2, "$argon2id$v=19$m=65536,t=3,p=4$MDEyMzQ1Njc4OWFiY2RlZg$uKZLaN6muIyoyIYr5waqw3y+zaDbe9aLSPj6Ln/rbz4"),
]

for handler, stored in samples:
    # identify reports whether the string is in that format; verify reports whether the plaintext matches
    print(handler.name, handler.identify(stored), handler.verify("password", stored))

verify reads the cost, rounds, and salt out of the string and recomputes for you, which means you do not have to write the parameter extraction yourself. The plaintext in the bcrypt example above is password, so it returns True.

Note that Python’s standard crypt module was deprecated and removed in Python 3.13. If you need crypt-family handling on 3.13 or later, move to openssl passwd or passlib.

5.4 Translate the candidate scheme into a hashcat mode number or a John format name

When the plaintext is unknown and you want to hand the problem to a tool, you have to translate the scheme name into the identifier the tool uses. This is a common sticking point, so here are the representative ones in a table.

Appearance Scheme hashcat mode (-m)
32 hex MD5 0
32 hex (from AD) NT hash 1000
40 hex SHA-1 100
64 hex SHA-256 1400
128 hex SHA-512 1700
$1$... md5crypt 500
$apr1$... Apache APR1-MD5 1600
$2a$ / $2b$ / $2y$ bcrypt 3200
$5$... sha256crypt 7400
$6$... sha512crypt 1800
pbkdf2_sha256$... Django PBKDF2-HMAC-SHA256 10000
scrypt-family storage formats scrypt 8900

Mode numbers keep getting added with each release, so for newer schemes such as Argon2 and yescrypt, check the list in the help output of the hashcat you have installed.

John the Ripper takes a name rather than a number. The ones valid in core John (1.8.0) are descrypt, bsdicrypt, md5crypt, bcrypt, LM, AFS, tripcode, dummy, and crypt; most others are added by the jumbo build. If the name of the scheme you want is not in core, check the jumbo documentation.

To repeat the earlier point: use the tools listed here only against your own environment, or an environment where you have permission to investigate.

5.5 Check the implementation code or configuration

If the system under investigation is your own, looking at the code and configuration is ultimately the most reliable.

  • The library in use
  • The framework configuration
  • The options used at generation time
  • The output encoding (hex / Base64 / Base64url / crypt alphabet)

Looking here usually settles the matter.

5.6 For the future, store with a scheme label

If you are the one designing going forward, choosing a format that embeds the scheme into the string makes future migrations much easier.

  • Argon2’s PHC string format
  • Spring Security’s {id}encodedPassword
  • Django’s algo$iterations$salt$hash
  • Unix crypt(3)-family prefixed formats

Done this way, whoever looks at it later will rarely be confused. Conversely, a design that puts “just 64 hex” in the DB is unkind to your future self.

6. Summary

When identifying the scheme behind the string representation of a hash, it helps to look in this order.

  1. Is there a prefix?
  2. What are the separators?
  3. What is the character set?
  4. How many bytes does the length correspond to?
  5. What is the context of the storage source?

The two most important points are:

  • Prefixed storage formats are fairly easy to pin down
  • Plain hex / Base64 often only gets you to a candidate set

So the practical judgment goes like this.

  • With $argon2id$..., $2b$..., $6$..., {SHA}..., pbkdf2_sha256$..., the string alone takes you quite far
  • With only 32 / 40 / 64 / 128 hex digits, think “narrow the candidates,” not “declare a verdict”
  • If you truly need certainty, go look at the source product, configuration, and implementation

Following this order speeds up an investigation considerably. Snap judgments based on length alone, on the other hand, quietly send you the long way around.

Technical Consulting & Design Review

Identifying the scheme of password hashes left in an existing DB, migrating an authentication platform, and investigating logs in mixed Windows / Web systems all require organizing not just the look of the strings but the storage source’s implementation and the migration policy. Looking at everything together, from scheme identification through migration design, makes it easier to keep things from breaking.

Bug Investigation & Root Cause Analysis

Investigations stuck on “we can’t make progress on verification because we don’t know what this string is” are not unusual. Pinning down where the scheme is decided - logs, configuration files, DB schema, or application implementation - speeds up root cause identification considerably.

8. References

  1. RFC 1321 - The MD5 Message-Digest Algorithm
  2. NIST FIPS 180-4 - Secure Hash Standard (SHA-1, SHA-2, SHA-512/224, SHA-512/256)
  3. NIST FIPS 202 - SHA-3 Standard: Permutation-Based Hash and Extendable-Output Functions
  4. PHC string format specification
  5. Argon2 reference implementation
  6. RFC 7693 - The BLAKE2 Cryptographic Hash and Message Authentication Code (MAC)
  7. BLAKE3 C README - default output length and extendable output
  8. crypt(5) - prefixes and hashed passphrase formats
  9. Apache HTTP Server 2.4 - Password Formats
  10. slappasswd(8) - RFC 2307 schemes such as {SHA} and {SSHA}
  11. Django documentation - example of pbkdf2_sha256$...
  12. Spring Security - DelegatingPasswordEncoder storage format {id}encodedPassword
  13. MS-NLMP: NTLM v1 Authentication - NTOWFv1(Passwd, User, UserDom) = MD4(UNICODE(Passwd))
  14. hashcat wiki - example hashes (the list of mode numbers)
  15. John the Ripper - command line options (the names usable with --format=NAME)
  16. passlib - the PasswordHash API (identify and verify)
  17. openssl-passwd(1) - -1 / -apr1 / -5 / -6 and -salt

Recent articles sharing the same tags. Deepen your understanding with closely related topics.

These topic pages place the article in a broader service and decision context.

This article connects naturally to the following service pages.

Frequently Asked Questions

Common questions about the topic of this article.

In what order should I look at a hash string to identify its scheme?
Looking in the order prefix, separator, character set, length, and then context keeps things organized. Formats where the first few characters carry meaning are easy to pin down: $argon2id$ strongly suggests Argon2, $2b$ suggests bcrypt, $5$ is sha256crypt, and $6$ is sha512crypt. Plain hex or Base64 without a prefix, on the other hand, only gets you as far as narrowing the candidates, so you settle the question by checking the product, framework, and configuration the string was stored by.
Can I declare that a 64-character hex string is SHA-256?
Declaring it is risky. SHA-256 is a strong candidate, but other schemes share the same 32-byte output, including SHA3-256, SHA-512/256, BLAKE2s-256, and BLAKE3's default output, and length alone cannot tell them apart. Length is material for building a candidate set, not material for a verdict. If you need certainty, you have to go further: the specification of the storage source, the storage format in the official documentation, a comparison against a known plaintext, and a look at the implementation code and configuration.
Is a string starting with $6$ a SHA-512 hash?
No. $6$ is the prefix of sha512crypt, a Unix-family password storage format. It is not a SHA-512 hex digest but a password hash string that includes a salt and rounds. Likewise, $5$ is sha256crypt and $1$ is md5crypt. The moment a prefix is present, the string is no longer just a digest, and getting this wrong will skew the implementation of your migration or verification code.
What can the character set tell me?
The character set is as important a clue as the length. If the string uses only characters in the range 0-9a-fA-F, it is a hex representation and half the character count is the raw byte length. If + / = appear, suspect RFC 4648 Base64; if - or _ appear, suspect Base64url. If periods and slashes show up and the string is $-delimited, it is more natural to suspect a crypt-family alphabet such as bcrypt or sha512crypt than ordinary Base64. Reading a string with periods in it as broken Base64 is how people overlook the crypt-family formats.

Author Profile

Profile page for the article author.

Go Komura

Representative of KomuraSoft LLC

Focused on Windows software development, technical consulting, and investigations into failures that are difficult to reproduce.

Back to the Blog