remove Django's PBKDF2 in favour of werkzeug.security.pbkdf2_hex

This commit is contained in:
Martin Zimmermann 2014-05-29 12:31:38 +02:00
parent 932274921c
commit 211f637569
3 changed files with 14 additions and 121 deletions

View File

@ -375,6 +375,9 @@ class TestComments(unittest.TestCase):
# just for the record
self.assertEqual(self.post('/id/1/dislike', content_type=js).status_code, 200)
def testPBKDF2(self):
self.assertEqual(comments.API.pbkdf2(u"", Isso.salt, 1000, 6), u"42476aafe2e4")
class TestModeratedComments(unittest.TestCase):

View File

@ -1,115 +0,0 @@
# -*- encoding: utf-8 -*-
#
# Copyright (c) Django Software Foundation and individual contributors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# 3. Neither the name of Django nor the names of its contributors may be used
# to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import hmac
import struct
import base64
import hashlib
import binascii
import operator
from functools import reduce
def _bin_to_long(x):
"""
Convert a binary string into a long integer
This is a clever optimization for fast xor vector math
"""
return int(binascii.hexlify(x), 16)
def _long_to_bin(x, hex_format_string):
"""
Convert a long integer into a binary string.
hex_format_string is like "%020x" for padding 10 characters.
"""
return binascii.unhexlify((hex_format_string % x).encode('ascii'))
def _fast_hmac(key, msg, digest):
"""
A trimmed down version of Python's HMAC implementation.
This function operates on bytes.
"""
dig1, dig2 = digest(), digest()
if len(key) > dig1.block_size:
key = digest(key).digest()
key += b'\x00' * (dig1.block_size - len(key))
dig1.update(key.translate(hmac.trans_36))
dig1.update(msg)
dig2.update(key.translate(hmac.trans_5C))
dig2.update(dig1.digest())
return dig2
def _pbkdf2(password, salt, iterations, dklen=0, digest=None):
"""
Implements PBKDF2 as defined in RFC 2898, section 5.2
HMAC+SHA256 is used as the default pseudo random function.
Right now 10,000 iterations is the recommended default which takes
100ms on a 2.2Ghz Core 2 Duo. This is probably the bare minimum
for security given 1000 iterations was recommended in 2001. This
code is very well optimized for CPython and is only four times
slower than openssl's implementation.
"""
assert iterations > 0
if not digest:
digest = hashlib.sha1
password = b'' + password
salt = b'' + salt
hlen = digest().digest_size
if not dklen:
dklen = hlen
if dklen > (2 ** 32 - 1) * hlen:
raise OverflowError('dklen too big')
l = -(-dklen // hlen)
r = dklen - (l - 1) * hlen
hex_format_string = "%%0%ix" % (hlen * 2)
def F(i):
def U():
u = salt + struct.pack(b'>I', i)
for j in range(int(iterations)):
u = _fast_hmac(password, u, digest).digest()
yield _bin_to_long(u)
return _long_to_bin(reduce(operator.xor, U()), hex_format_string)
T = [F(x) for x in range(1, l + 1)]
return b''.join(T[:-1]) + T[-1][:r]
pbkdf2 = lambda text, salt, iterations, dklen: base64.b16encode(
_pbkdf2(text.encode('utf-8'), salt, iterations, dklen)).lower()

View File

@ -9,17 +9,17 @@ import functools
from itsdangerous import SignatureExpired, BadSignature
from werkzeug.http import dump_cookie
from werkzeug.routing import Rule
from werkzeug.wrappers import Response
from werkzeug.exceptions import BadRequest, Forbidden, NotFound
from werkzeug.wsgi import get_current_url
from werkzeug.utils import redirect
from werkzeug.routing import Rule
from werkzeug.security import pbkdf2_hex
from werkzeug.wrappers import Response
from werkzeug.exceptions import BadRequest, Forbidden, NotFound
from isso.compat import text_type as str
from isso import utils, local
from isso.utils import http, parse, JSONResponse as JSON
from isso.utils.crypto import pbkdf2
from isso.views import requires
# from Django appearently, looks good to me *duck*
@ -139,6 +139,11 @@ class API(object):
return True, ""
@classmethod
def pbkdf2(cls, text, salt, iterations, dklen):
# werkzeug.security.pbkdf2_hex returns always the native string type
return pbkdf2_hex(text.encode("utf-8"), salt, iterations, dklen)
@xhr
@requires(str, 'uri')
def new(self, environ, request, uri):
@ -197,7 +202,7 @@ class API(object):
max_age=self.conf.getint('max-age'))
rv["text"] = self.isso.render(rv["text"])
rv["hash"] = pbkdf2(rv['email'] or rv['remote_addr'], self.isso.salt, 1000, 6).decode("utf-8")
rv["hash"] = API.pbkdf2(rv['email'] or rv['remote_addr'], self.isso.salt, 1000, 6)
self.cache.set('hash', (rv['email'] or rv['remote_addr']).encode('utf-8'), rv['hash'])
@ -429,7 +434,7 @@ class API(object):
val = self.cache.get('hash', key.encode('utf-8'))
if val is None:
val = pbkdf2(key, self.isso.salt, 1000, 6).decode("utf-8")
val = API.pbkdf2(key, self.isso.salt, 1000, 6)
self.cache.set('hash', key.encode('utf-8'), val)
item['hash'] = val