0

Can somebody explain simply why crypt of a password with a salt (the hash result) is equal to crypt of the password with the hash result itself ?

Surely there is a simple mathematical explanation. I asked Bing AI Chat and she said :

When you use the crypt() function to hash a password, the resulting hash includes the salt value. When you pass the hash value as the salt argument to the crypt() function, the function extracts the salt value from the hash and uses it to hash the password again. Since the salt value is the same in both cases, the resulting hash values are also the same. This is why crypt(password, hash) = hash.

This process is useful when you need to verify a user’s password in a database. Instead of comparing the entered password to the stored password in plain text, you can use the crypt() function to hash the entered password and compare it to the stored hashed password. This ensures that the passwords are not stored in plain text, which is important for maintaining security.

I think it is not correct because it is not easy to extract the salt value from the hash.

I am not familiar with cryptography. I just wanted to ask this question. I would also appreciate reliable references.

8
  • 24
    You made a fatal mistake. You asked an AI (Artificial Idiocy) and believed its output. Anything difficult AIs will totally fail.
    – gnasher729
    Feb 2 at 17:49
  • 4
    Except that in this case the AI was essentially correct Feb 2 at 18:19
  • 2
    The AI is pretty much spot on here, really. And yes, the salt needs to be included in the hash in a way that allows for it to be extracted, since otherwise, how could you compare an entered password with the hash at all? Turning the hash back to the password doesn't work, and you need the salt to make an identical hash.
    – ilkkachu
    Feb 2 at 21:19
  • 6
    This confused me too. Turns out the wording in the manual is misleading. This wording is the problem: "... the resulting hash includes the salt value" This wording would be more accurate: "...the return value includes the hash (calculated from the password and the salt) concatenated with the salt that was used." Feb 3 at 5:20
  • 3
    Regardless of the merits or deficits of AI tools, may I suggest we avoid unnecessarily anthropomorphising them through the use of pronouns like in "she said"? Feb 3 at 8:37

4 Answers 4

22

The output of crypt looks like this:

$1$L2l4$oqf.HIrymNg/YIIF.r.rP0

This is not just the cryptographic hash. The dollar signs in here split the structure into three strings:

  • 1, the type of hash function used, in this case MD5.
  • L2l4, the salt
  • oqf.HIrymNg/YIIF.r.rP0, the cryptographic hash.

The salt is contained in the output of crypt. When calling crypt again and passing this whole string, it only takes the salt to recompute the hash. This is a convenience feature, so you don't have to keep track of the salt separately.

So it's not true in general that MD5(password, hash) = hash. It's just true for crypt, because it takes the salt value from the given hash.

2
  • I have the impression that the Blowfish hash formats don't have the $ there to tell apart the salt from the hash proper. But it's still there as part of the output.
    – ilkkachu
    Feb 2 at 21:21
  • 4
    All true, but I'm wincing at the choice of MD5 as the example hash (since it's no longer considered secure enough for password hashing).
    – Neilski
    Feb 3 at 14:42
17

In this case man 3 crypt is probably more useful than asking AI. AI has a tendency to be... not entirely correct.

The man page has the following about return value:

Upon successful completion, crypt, crypt_r, crypt_rn, and crypt_ra return a pointer to a string which encodes both the hashed passphrase, and the settings that were used to encode it.

It doesn't say that the hash contains the salt - it says that crypt returns a string which encodes both hash of passphrase and settings used for crypt. These settings include the salt.

5

... it is not easy to extract the salt value from the hash

The problem is that the term "hash" is overloaded with several different meanings and that you use the wrong meaning to interpret the description.

You assume that "hash" is a one-way computation, which cannot be reversed - and thus the salt cannot be extracted. While this is true for some meaning of hash this is not true to how the term is used for password hashing.

With crypt and other password hashing methods "hash" means just the result of the password hashing function. The one-way computation is only part of this result. While the salt is part of this one-way computation it is also explicitly added to the resulting "hash" string. From the man page:

The returned value points to the hashed password, a series of 13 printable ASCII characters (the first two characters represent the salt itself).

So, extracting the salt is simply using the first two characters of the password hash.

1
  • 2
    note that the "first two characters" only applies for the obsolete DES-based crypt format. The ones in actual use nowadays have start with the type indicator, as described in another answer
    – ilkkachu
    Feb 3 at 10:22
2

To expand on Sjoerd's answer:

  • The original crypt(3) used DES, and used a very short 2-byte salt (it actually only used 12 bits of that), which was included as the first two bytes of the result, before the actual hash value:

    salt: salt string: value -> sagZ785I5/e5E
    

    So in the output of crypt, sa is the part of the salt actually used, and the rest gZ785I5/e5E is the actual hash. This allows one to manipulate and store a single value, but it actually contains both the salt and the hash.

    salt: saZZ string: value -> sagZ785I5/e5E
    

    You see here that anything after the first 2 bytes of the salt is ignored.

    So if I give the full "hashed" password (i.e. the output of crypt which actually includes the salt), I get the same value:

    salt: sagZ785I5/e5E string: value -> sagZ785I5/e5E
    
  • In some implementations, this was latter expanded to an "extended" crypt (salt beginning with _) with takes a longer "salt" which is actually an iteration count and the salt itself. But the principle was retained:

    salt: _AAAAAAAA string: value -> _AAAAAAAAa05by8Rlm06
    

    Again, you see that the output of crypt(3) starts with the value of the "salt", followed by the actual hash. If we re-hash the input with the output, we get the same result:

    salt: _AAAAAAAAa05by8Rlm06 string: value -> _AAAAAAAAa05by8Rlm06
    
  • In most modern implementations, this was later expanded again in a more flexible format of the form $<id>$<settings>$, where id gives the type of hash (support varies). Some types:

    1       MD5
    2       Bcrypt (Blowfish)
    2a      Bcrypt (Blowfish)
    2b      Bcrypt (Blowfish)
    2x      Bcrypt (Blowfish)
    2y      Bcrypt (Blowfish)
    5       SHA-256
    6       SHA-512
    7       Scrypt
    argon2  Argon2
    argon2i Argon2
    argon2d Argon2
    

    The settings contain a salt but depending on the type may also include other parameters. In all cases, when the result is to be stored as a single value, the output always starts with the $<id>$<settings>$ provided, so one can:

    • find what type of hash one needs to use
    • provide the right settings and salt

    In many implementations, this is completely transparent: just provide the stored "hash" and the entered password, and it will be able to do all the magic (as long as that implementation supports the hash type that was used originally).

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .