6

I am rewriting a Django app in Ruby on Rails and want to preserve the old passwords for users.

Django uses PBKDF2SHA1 as the encryption mechanism. So one encrypted password I have is this

pbkdf2_sha256$10000$YsnGfP4rZ1IZ$Tpf4922MoNEjuJQA9EG2Elptyt3dMAyzBPUgmunFOW4=

the original password is 2bulls

In Ruby, I use PBKDF256 gem and base64 for the checking.

Base64.encode64 PBKDF256.dk("2bulls", "YsnGfP4rZ1IZ", 10000, 32)

I am expecting

Tpf4922MoNEjuJQA9EG2Elptyt3dMAyzBPUgmunFOW4=

However, I got

YEfK6oUGFHdaKZMDXC0Dz8TpwsJlKfqC5vjCxjo+ldU=

Any ideas?

UPDATE

Found code in django source if it makes more sense to you.

class PBKDF2PasswordHasher(BasePasswordHasher):
    """
    Secure password hashing using the PBKDF2 algorithm (recommended)

    Configured to use PBKDF2 + HMAC + SHA256 with 10000 iterations.
    The result is a 64 byte binary string.  Iterations may be changed
    safely but you must rename the algorithm if you change SHA256.
    """
    algorithm = "pbkdf2_sha256"
    iterations = 10000
    digest = hashlib.sha256

    def encode(self, password, salt, iterations=None):
        assert password
        assert salt and '$' not in salt
        if not iterations:
            iterations = self.iterations
        hash = pbkdf2(password, salt, iterations, digest=self.digest)
        hash = base64.b64encode(hash).decode('ascii').strip()
        return "%s$%d$%s$%s" % (self.algorithm, iterations, salt, hash)

    def verify(self, password, encoded):
        algorithm, iterations, salt, hash = encoded.split('$', 3)
        assert algorithm == self.algorithm
        encoded_2 = self.encode(password, salt, int(iterations))
        return constant_time_compare(encoded, encoded_2)

    def safe_summary(self, encoded):
        algorithm, iterations, salt, hash = encoded.split('$', 3)
        assert algorithm == self.algorithm
        return SortedDict([
            (_('algorithm'), algorithm),
            (_('iterations'), iterations),
            (_('salt'), mask_hash(salt)),
            (_('hash'), mask_hash(hash)),
        ])

CONCLUSION:

It turns out that only the account with 2bulls being the password has this problem. The other accounts are fine. When I know the exact reason for inconsistent password for 2bulls, I will post here.

benzhang
  • 471
  • 4
  • 11
  • I assume the salt you're using came from the Django installation, and the iteration number is from there as well (`$10000$` in that first password?). The length matches. Any chance the issue is with the Base64 encoding? – pjmorse Mar 29 '13 at 00:15
  • yes. second argument is the salt. third argument is the number of iterations, last argument is key length. I am digging into django source code to investigate now. – benzhang Mar 29 '13 at 00:20
  • Ah, interesting. I see now where you derived all the arguments. So first it gets a sha256 digest, then runs it through pbkdf2, then base64 encodes. Is that what the Ruby gem is doing? (I'll look too.) Also, can we verify that Python's sha256 hash generates the same output as Ruby's on the same string? That seems like a least common denominator. – pjmorse Mar 29 '13 at 00:36
  • aaaah, never mind, the gem isn't using Ruby's sha256, it's using its own C-based version. I am way over my head. – pjmorse Mar 29 '13 at 00:42

2 Answers2

7

If you hash that password 2bulls using the following script (same logic that Django uses to hash passwords) this is what you get (ran this on Django's shell):

>>> import base64, hashlib
>>> hash = pbkdf2("2bulls","YsnGfP4rZ1IZ", 10000, 32, hashlib.sha256)
>>> hash.encode('base64').strip()
'YEfK6oUGFHdaKZMDXC0Dz8TpwsJlKfqC5vjCxjo+ldU='

Notice how I'm using the same parameters that you are using in Ruby:

password = "2bulls"
salt = "YsnGfP4rZ1IZ"
iterations = 10000
dklen = 32
digest = hashlib.sha256

Are you sure your expected hash corresponds to password 2bulls?

Dan Aronne
  • 616
  • 3
  • 11
0

I've just released a gem for this purpose. I too, get the same result as @Daniel Aronne :

require 'pbkdf2_password_hasher'

puts Pbkdf2PasswordHasher::hash_password('2bulls','YsnGfP4rZ1IZ',10000,32)
#=> "YEfK6oUGFHdaKZMDXC0Dz8TpwsJlKfqC5vjCxjo+ldU="
aherve
  • 3,795
  • 6
  • 28
  • 41