A Practical Procedure for Identifying the Scheme Behind a Hash String
· Updated: · Go Komura · 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
- The Conclusion First
- At-a-Glance Identification Tables
- The Practical Identification Procedure
- Common Misidentifications
- The Verification Order When You Need 100% Certainty
- Summary
- Services Related to This Theme
- 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 thecrypt(3)family - distinctions like these really work. -
If you want 100% certainty, you need context. Whether it lives in
/etc/shadow,.htpasswd, Django’sauth_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 theExamplecolumn of section 2.1. -
$2a$/$2b$/$2y$Strongly suspect bcrypt. -
$1$/$5$/$6$/$7$/$y$Suspect a Unixcrypt(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:hashoralgo$iterations$salt$hashSuspect a framework- or app-specific format. Django’spbkdf2_sha256$iterations$salt$hashis 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/shadowSuspect Linux password hash formats such as$y$,$6$,$5$,$1$ -
Found in
.htpasswdSuspect Apache-family formats such as$apr1$,{SHA}, bcrypt -
Found in Django settings or
auth_user.passwordSuspect Django formats such aspbkdf2_sha256$...orargon2$... -
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 asNTOWFv1(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 formatSpring Security password storage formatcrypt(5) sha512crypt formatApache 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.
- Extract the salt and parameters from the string
- Recompute with the same salt and parameters against the known plaintext
- 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.
- Is there a prefix?
- What are the separators?
- What is the character set?
- How many bytes does the length correspond to?
- 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.
7. Services Related to This Theme
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
- RFC 1321 - The MD5 Message-Digest Algorithm
- NIST FIPS 180-4 - Secure Hash Standard (SHA-1, SHA-2, SHA-512/224, SHA-512/256)
- NIST FIPS 202 - SHA-3 Standard: Permutation-Based Hash and Extendable-Output Functions
- PHC string format specification
- Argon2 reference implementation
- RFC 7693 - The BLAKE2 Cryptographic Hash and Message Authentication Code (MAC)
- BLAKE3 C README - default output length and extendable output
- crypt(5) - prefixes and hashed passphrase formats
- Apache HTTP Server 2.4 - Password Formats
- slappasswd(8) - RFC 2307 schemes such as {SHA} and {SSHA}
- Django documentation - example of
pbkdf2_sha256$... - Spring Security -
DelegatingPasswordEncoderstorage format{id}encodedPassword - MS-NLMP: NTLM v1 Authentication -
NTOWFv1(Passwd, User, UserDom) = MD4(UNICODE(Passwd)) - hashcat wiki - example hashes (the list of mode numbers)
- John the Ripper - command line options (the names usable with
--format=NAME) - passlib - the
PasswordHashAPI (identifyandverify) - openssl-passwd(1) -
-1/-apr1/-5/-6and-salt
Related Articles
Recent articles sharing the same tags. Deepen your understanding with closely related topics.
What to Do Before Disposing of a Windows PC — A Practical Checklist for Data Erasure, Account Unlinking, and Backups
What to do before disposing of, transferring, selling, or returning a leased Windows PC — covering backups, data erasure, BitLocker, Micr...
Handling Windows Impersonation Tokens Correctly — Borrowing Privileges per Thread and Reverting Safely
A practical guide to Windows impersonation tokens — access tokens, primary tokens, thread tokens, impersonation levels, RevertToSelf, and...
Does Turning Off Memory Integrity (HVCI) Make Windows Faster? — What It Means, How to Do It, and How to Decide
Does turning off Memory integrity (HVCI) really speed up a Windows PC? When it can help, when it cannot, how to switch it off and back, a...
What Is an OLE Object? — How Embedding and Linking Work and the Pitfalls in Business Documents
An OLE object is what embeds an Excel table in Word. Learn embedding vs. linking, compound files, In-Place Activation, broken links, bloa...
The Depths of Windows Virtualization (Part 2) — Memory Even the Kernel Cannot See: How VBS, HVCI, and Credential Guard Work
Enabled by default on a clean install to compatible hardware, VBS uses the hypervisor and SLAT to isolate beyond the kernel. Covers VTLs,...
Related Topics
These topic pages place the article in a broader service and decision context.
Windows Technical Topics
Topic hub for KomuraSoft LLC's Windows development, investigation, and legacy-asset articles.
Where This Topic Connects
This article connects naturally to the following service pages.
Technical Consulting & Design Review
This suits engagements where we separate the logs, DB, authentication method, and storage format of an existing system, identify the hash scheme, and organize the decisions for migration or investigation.
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.