Search code examples
encryptioncryptographysftppbkdf2

Custom password hashing function for Cerberus SFTP server


I'm trying to migrate users from our legacy Cerberus SFTP server to a new AWS Transfer Family SFTP server. The problem is most of our Cerberus users have password based authentication and all I have access to is their one-way hashed password. Thus I'm trying to reverse engineer how Cerberus hashes it's password so I don't have to ask our 100+ customers to submit a new password to use or switch to a public key based authentication.

I came across this blog post that I think details how to do it but I can't seem to get it to work for some reason - https://support.cerberusftp.com/hc/en-us/articles/360000040039-Securely-Storing-User-Passwords.

Here are the steps I've taken so far -

  1. created a user in Cerberus with a password of "asdf"
  2. exported my collection of users to a CSV file
  3. identified the hashed password from the export as follows - {{PBKDF2 HMAC SHA256}}:5000:42ED67592D7D80F03BF3E2413EB80718C5DAFEB5237FC4E5E309C2940DF1DBB2A4ABD9BB63B8AD285858B532A573D9DE
  4. attempted to write a Python script that could hash "asdf" to the same hash as shown above

Here is what my script looks like so far - https://replit.com/@ryangrush/sample.

import hashlib
import base64

password = b'asdf'
salt = b'sample_salt'

combined = salt + password
first = hashlib.pbkdf2_hmac('sha256', combined, b'', 5000)
combined = salt + first
second = hashlib.pbkdf2_hmac('sha256', combined, b'', 5000)
base_16 = base64.b16encode(second)

print(second.hex())
print(base_16)

Solution

  • The documentation must've been written before the v7.0 PBKDF2 HMAC functions were adopted. The salt and the password are now used just as described in the documentation for PBKDF2.

    import hashlib
    import base64
    
    hashed_password_entry = '{{PBKDF2 HMAC SHA256}}:5000:42ED67592D7D80F03BF3E2413EB80718C5DAFEB5237FC4E5E309C2940DF1DBB2A4ABD9BB63B8AD285858B532A573D9DE'
    entry_components_strings = hashed_password_entry.split(':')
    password = b'asdf'
    
    iterations = int(entry_components_strings[1])
    salt_plus_hashvalue = base64.b16decode(entry_components_strings[2])
    hash_len = 256 // 8
    salt, hashvalue = salt_plus_hashvalue[:-hash_len], salt_plus_hashvalue[-hash_len:]
    
    hashvalue_test = hashlib.pbkdf2_hmac('SHA256', password, salt, iterations)
    print(hashvalue_test.hex())
    

    the output is c5dafeb5237fc4e5e309c2940df1dbb2a4abd9bb63b8ad285858b532a573d9de which you can see matches the hashed value that is at the end of entry.