Generating PBKDF2 Password Hashes In Python, Not Ruby

Chef offers a great many useful features, including the ability to manage and create user accounts. The password for a local user account can be specified either in clear text or as a password hash.

According to the documentation linked above, generating an appropriate password hash for 10.8+ requires the use of a specific Ruby function:

OpenSSL::PKCS5::pbkdf2_hmac(
password,
salt,
iterations,
128,
OpenSSL::Digest::SHA512.new
)

However, when trying to generate such a hash using this tool on 10.10.5, I discovered a problem:

irb(main):026:0> OpenSSL::PKCS5::pbkdf2_hmac(
irb(main):027:1* password,
irb(main):028:1* salt,
irb(main):029:1* iterations,
irb(main):030:1* 128,
irb(main):031:1* OpenSSL::Digest::SHA512.new
irb(main):032:1> )
NotImplementedError: pbkdf2_hmac() function is unimplemented on this machine
from (irb):26:in `pbkdf2_hmac'
from (irb):26
from /usr/bin/irb:12:in `<main>'

Well, that's not very nice.

The issue is that the version of OpenSSL on OS X for the last several years is still 0.9.8zg. That version simply doesn't have an implementation of the pbkdf2_hmac() that Ruby wants to use. However, Python does, thanks to hashlib.

To recreate the same process in Python that the Chef documentation recommends for generating a 10.8+ password hash, use the following steps:

import hashlib
import binascii
import os

password = b'password'
salt = os.urandom(32)
chef_salt = binascii.hexlify(salt)
iterations = 25000

hex = hashlib.pbkdf2_hmac('sha512', password, salt, 25000, 128)
chef_password_hash = binascii.hexlify(hex)

Let's break down what happened there. First, we set the password to our password string. In Python 2, the b before a string doesn't really do anything.

The salt is a random 32-bit string. In Python, this comes out in binary form:

>>> salt = os.urandom(32)
>>> salt
'M\xde\xf6\x9fp\xd7$\x128\x9a\xc2!\xad\x1a\xe6\x9bE\xf8N\n\xd0\x18\xf6Ez\xf5@\xe0\xd1\r\xe6a'

Chef, however, requires this in a hexadecimal form:

>>> binascii.hexlify(salt)
'4ddef69f70d72412389ac221ad1ae69b45f84e0ad018f6457af540e0d10de661'

We use 25,000 iterations as a nice arbitrary number, but you should use anything above 10,000 to be at least minimally safe. Of course this is a local user account on a service machine in my context, so I’m not entirely worried about its security as much.

Once we have all the variables, we can use the actual pbkdf2_hmac() function. In the example above, we’re using the SHA-512 digest, with a derived key length of 128 as the Chef documentation suggests. Once again, the result of that command is binary data, and Chef requires a hexadecimal representation, so we turn to our trusty friend binascii.hexlify again.

This allows us to create the Chef user block we need:

user 'myuser' do
gid 'staff'
home '/Users/myuser'
shell '/bin/bash'
password 'e6a8a452c0a9edb7f80703657b91fae74191d3b83982687ca00b83741ad775410178542ffc176abe6db9dc46053bc7ed36c91c1f43f82ba1dedc12de929f81cca868e223a25f3f16728e9f92c02e4421e9f73d73edb5e23e5d0cf1784243e8c79307ee5e61b411c9f116c450af8112e519fa15cfb50f5e7a8c1e6a78fb7cbc0e'
salt 'eb30e9c1946f086b4cd84679c1ee81235edea080b28b1ce4d39341794fad1ccd'
iterations 25_000
supports manage_home: true
action :create
end

I’m told this same technique can also generate password hashes to be used with Puppet as well, although I haven’t tested it personally.

Advertisements