mitsuhiko/python-pbkdf2

python3 port

Tcll opened this issue · 2 comments

Tcll commented

if I could submit a PR, I would

  • the machine I wrote the updated code on is offline behind a non-functional subnet.
  • I'm not willing to give Micro$**t the undeserved pleasure of me putting my sources on their platform for them to control by forking a repo, I pulled my sources when they partnered, and I intend to keep things that way.
    (call me rude all you like, I'm not the one restricting freedoms and making a profit off my sheep)

but anyways, with that out of the way, here's the updated code:

import hmac
import hashlib
from struct import Struct
from operator import xor
from itertools import starmap

try:
    from itertools import izip as zip
except ImportError: pass # py3

try: range = xrange
except NameError: pass # py3

_pack_int = Struct('>I').pack


def pbkdf2_hex(data, salt, iterations=1000, keylen=24, hashfunc=None):
    """Like :func:`pbkdf2_bin` but returns a hex encoded string."""
    return ''.join('%02x'%(v if type(v) is int else ord(v)) for v in
        pbkdf2_bin(data, salt, iterations, keylen, hashfunc))


def pbkdf2_bin(data, salt, iterations=1000, keylen=24, hashfunc=None):
    """Returns a binary digest for the PBKDF2 hash algorithm of `data`
    with the given `salt`.  It iterates `iterations` time and produces a
    key of `keylen` bytes.  By default SHA-1 is used as hash function,
    a different hashlib `hashfunc` can be provided.
    """
    hashfunc = hashfunc or hashlib.sha1
    mac = hmac.new(data, None, hashfunc)

    buf = []
    for block in range(1, -(-keylen // mac.digest_size) + 1):
        h = mac.copy()
        h.update(salt + _pack_int(block))
        u = h.digest()
        rv = list(bytearray(u)) # needs further testing on py2 and could possibly be more performant
        for i in range(iterations - 1):
            h = mac.copy()
            h.update(bytes(u))
            u = h.digest()
            rv = starmap(xor, zip(rv, list(bytearray(u))))

        buf.extend(rv)
    return ''.join(map(chr, buf))[:keylen]

I've even ported the tests over and manually verified every expected key with your keys
(it was easier working 2 keyboards for 12 keys than copying the tests over on a flash drive)
all tests have passed.

a few things you may notice:

  • removed _pseudorandom() to improve overhead performance while maintaining namespace security
  • yes I'm locally overriding zip and range for py2, since this reflects a py3 namespace
    (@triggeredpythonists: I wouldn't do this if the original functionality was actually needed, please remain calm)

respect:

  • +1 for not following the crowd by using ''%() over ''.format() ;)

tips/advice:

  • you might want to from __future__ import print_function for porting print to print() in test()->check()

keep the credit, I care more about security than I care about being the one who ported your code ;)

mrdc commented

@Tcll Thanks for porting it to Python3. There is a small issue which should be also fixed for Python 3:

 h.update(salt + _pack_int(block))
TypeError: can only concatenate str (not "bytes") to str
* you might want to `from __future__ import print_function` for porting `print` to `print()` in `test()`->`check()`

Python 3 complains - should be changed manually to print()

Tcll commented

ah that can just be fixed by ensuring salt is a bytes object

if you don't want to sacrifice much performance for encodings:

assert type(salt) is bytes

yes it still throws an error, but at least now it's educational
thank you Struct and python 3 for giving us some extra tediosity.
(because 'a' can be any number of bytes in size thanks to utf-## or others)

Python 3 complains - should be changed manually to print()

I meant using that for Python 2
yes you still manually have to change it from the statement to the function
but it makes your code compatible across both