2
votes

I am migrating a platform which used Passlib 1.6.2 to generate password hashes. The code to encrypt the password is (hash is called with default value for rounds):

from passlib.hash import pbkdf2_sha512 as pb

def hash(cleartext, rounds=10001):
    return pb.encrypt(cleartext, rounds=rounds)

The output format looks like (for the password "Patient3" (no quotes)):

$pbkdf2-sha512$10001$0dr7v7eWUmptrfW.9z6HkA$w9j9AMVmKAP17OosCqDxDv2hjsvzlLpF8Rra8I7p/b5746rghZ8WrgEjDpvXG5hLz1UeNLzgFa81Drbx2b7.hg

And "Testing123"

$pbkdf2-sha512$10001$2ZuTslYKAYDQGiPkfA.B8A$ChsEXEjanEToQcPJiuVaKk0Ls3n0YK7gnxsu59rxWOawl/iKgo0XSWyaAfhFV0.Yu3QqfehB4dc7yGGsIW.ARQ

I can see that represents:

  • Algorithm SHA512
  • Iterations 10001
  • Salt 0dr7v7eWUmptrfW.9z6HkA (possibly)

The Passlib algorithm is defined on their site and reads:

All of the pbkdf2 hashes defined by passlib follow the same format, $pbkdf2-digest$rounds$salt$checksum.

$pbkdf2-digest$ is used as the Modular Crypt Format identifier ($pbkdf2-sha256$ in the example). digest - this specifies the particular cryptographic hash used in conjunction with HMAC to form PBKDF2’s pseudorandom function for that particular hash (sha256 in the example). rounds - the number of iterations that should be performed. this is encoded as a positive decimal number with no zero-padding (6400 in the example). salt - this is the adapted base64 encoding of the raw salt bytes passed into the PBKDF2 function. checksum - this is the adapted base64 encoding of the raw derived key bytes returned from the PBKDF2 function. Each scheme uses the digest size of its specific hash algorithm (digest) as the size of the raw derived key. This is enlarged by approximately 4/3 by the base64 encoding, resulting in a checksum size of 27, 43, and 86 for each of the respective algorithms listed above.

I found passlib.net which looks a bit like an abandoned beta and it uses '$6$' for the algorithm. I could not get it to verify the password. I tried changing the algorithm to $6$ but I suspect that in effect changes the salt as well.

I also tried using PWDTK with various values for salt and hash, but it may have been I was splitting the shadow password incorrectly, or supplying $ in some places where I should not have been.

Is there any way to verify a password against this hash value in .NET? Or another solution which does not involve either a Python proxy or getting users to resupply a password?

2
Add your code? Also how are you handling the Base64 variant in your code and the embedded space characters?zaph
@zaph I do not know how the different libraries handle encoding and spaces, it is exactly direction on those things that I am after.JohnMark13
@zaph The spaces in this case were an artifact of copying and pasting.JohnMark13
Supply the password input to the example.zaph
In addition to the input: What's pb? Please provide an MCVEArtjom B.

2 Answers

1
votes

The hash is verified by passing the password into the PBKDF HMAC-SHA-256 hash method and then comparing the resulting hash to the saved hash portion, converted back from the Base64 version.

Saved hash to binary, then separate the hash Convert the password to binary using UTF-8 encoding PBKDF2,HMAC,SHA-256(toBinary(password, salt, 10001) == hash Password: "Patient3"

$pbkdf2 - sha512$10001$0dr7v7eWUmptrfW.9z6HkA$w9j9AMVmKAP17OosCqDxDv2hjsvzlLpF8Rra8I7p/b5746rghZ8WrgEjDpvXG5hLz1UeNLzgFa81Drbx2b7.hg

Breaks down to (with the strings converted to standard Base64 (change '.' to '+' and add trailing '=' padding:

pbkdf2 - sha512
10001
0dr7v7eWUmptrfW+9z6HkA==
w9j9AMVmKAP17OosCqDxDv2hjsvzlLpF8Rra8I7p/b5746rghZ8WrgEjDpvXG5hLz1UeNLzgFa81Drbx2b7+hg==

Decoded to hex:

D1DAFBBFB796526A6DADF5BEF73E8790
C3D8FD00C5662803F5ECEA2C0AA0F10EFDA18ECBF394BA45F11ADAF08EE9FDBE7BE3AAE0859F16AE01230E9BD71B984BCF551E34BCE015AF350EB6F1D9BEFE86

Which makes sense: 16-byte (128-bit) salt and 64-byte (512-bit) SHA-512 hash.

Converting "Patient3" using UTF-8 to a binary array Converting the salt from a modified BASE64 encoding to a 16 byte binary array Using an iteration count od 10001 Feeding this to PBKDF2 using HMAC with SHA-512

I get

C3D8FD00C5662803F5ECEA2C0AA0F10EFDA18ECBF394BA45F11ADAF08EE9FDBE7BE3AAE0859F16AE01230E9BD71B984BCF551E34BCE015AF350EB6F1D9BEFE86

Which when Base64 encoded, replacing '+' characters with '.' and stripping the trailing '=' characters returns: w9j9AMVmKAP17OosCqDxDv2hjsvzlLpF8Rra8I7p/b5746rghZ8WrgEjDpvXG5hLz1UeNLzgFa81Drbx2b7.hg

0
votes

I quickly knocked together a .NET implementation using zaph's logic and using the code from JimmiTh on SO answer. I have put the code on GitHub (this is not supposed to be production ready). It appears to work with more than a handful of examples from our user base.

As zaph said the logic was:

  1. Split the hash to find the iteration count, salt and hashed password. (I have assumed the algorithm, but you'd verify it). You'll have an array of 5 values containing [0] - Nothing, [1] - Algorithm, [2] - Iterations, [3] - Salt and [4] - Hash
  2. Turn the salt into standard Base64 encoding by replacing any '.' characters with '+' characters and appending "==".
  3. Pass the password, salt and iteration count to the PBKDF2-HMAC-SHA512 generator.
  4. Convert back to the original base64 format by replacing any '+' characters with '.' characters and stripping the trailing "==".
  5. Compare to the original hash (element 4 in the split string) to this converted value and if they're equal you've got a match.