1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
|
"""passlib.pbkdf2 - PBKDF2 support
"""
#=================================================================================
#imports
#=================================================================================
#core
from binascii import unhexlify
from cStringIO import StringIO
import hashlib
import hmac
import logging; log = logging.getLogger(__name__)
import re
from struct import pack
from warnings import warn
#site
try:
from M2Crypto import EVP as _EVP
except ImportError:
_EVP = None
#pkg
from passlib.utils import xor_bytes
#local
__all__ = [
"hmac_sha1",
"pbkdf2",
]
#=================================================================================
#hmac sha1 support
#=================================================================================
def hmac_sha1(key, msg):
"perform raw hmac-sha1 of a message"
return hmac.new(key, msg, hashlib.sha1).digest()
if _EVP:
#default *should* be sha1, which saves us a wrapper function, but might as well check.
try:
result = _EVP.hmac('x','y')
except ValueError: #pragma: no cover
#this is probably not a good sign if it happens.
warn("PassLib: M2Crypt.EVP.hmac() unexpected threw value error during passlib startup test")
else:
if result == ',\x1cb\xe0H\xa5\x82M\xfb>\xd6\x98\xef\x8e\xf9oQ\x85\xa3i':
hmac_sha1 = _EVP.hmac
#=================================================================================
#backend
#=================================================================================
MAX_BLOCKS = 0xffffffffL #2**32-1
def _resolve_prf(prf):
"resolve prf string or callable -> func & digest_size"
if isinstance(prf, str):
if prf.startswith("hmac-"):
digest = prf[5:]
#check if m2crypto is present and supports requested digest
if _EVP:
try:
result = _EVP.hmac('x', 'y', digest)
except ValueError:
pass
else:
#it does. so use M2Crypto's hmac & digest code
hmac_const = _EVP.hmac
def encode_block(key, msg):
return hmac_const(key, msg, digest)
digest_size = len(result)
return encode_block, digest_size
#fall back to stdlib implementation
digest_const = getattr(hashlib, digest, None)
if not digest_const:
raise ValueError, "unknown hash algorithm: %r" % (digest,)
digest_size = digest_const().digest_size
hmac_const = hmac.new
def encode_block(key, msg):
return hmac_const(key, msg, digest_const).digest()
return encode_block, digest_size
else:
raise ValueError, "unknown prf algorithm: %r" % (prf,)
elif callable(prf):
#assume it's a callable, use it directly
digest_size = len(prf('',''))
return prf, digest_size
else:
raise TypeError, "prf must be string or callable"
def pbkdf2(secret, salt, rounds, keylen, prf="hmac-sha1"):
"""pkcs#5 password-based key derivation v2.0
:arg secret: passphrase to use to generate key
:arg salt: salt string to use when generating key
:param rounds: number of rounds to use to generate key
:arg keylen: number of bytes to generate
:param prf:
psuedo-random function to use for key strengthening.
should be a callable of the form ``prf(secret, plaintext) -> ciphertext``,
or a string ``hmac-xxx`` where ``xxx`` is the name
of a hash function recognized by :func:`M2Crypto.EVP.hmac` (if present),
or :func:`hashlib.new`.
Defaults to ``hmac-sha1``, the only prf defined
by the PBKDF2 specification.
This function attempts to use M2Crypto as a backend
if available and if the digest is a string supported
by M2Crypto. Otherwise it falls back to a software implementation.
:returns:
raw bytes of generated key
"""
#prepare secret
if isinstance(secret, unicode):
secret = secret.encode("utf-8")
elif not isinstance(secret, str):
raise TypeError("secret must be str or unicode")
#prepare salt
if isinstance(salt, unicode):
salt = salt.encode("utf-8")
elif not isinstance(salt, str):
raise TypeError("salt must be str or unicode")
#preprare rounds
if not isinstance(rounds, (int, long)):
raise TypeError("rounds must be an integer")
if rounds < 1:
raise ValueError("rounds must be at least 1")
#special case for m2crypto + hmac-sha1
if prf == "hmac-sha1" and _EVP:
try:
return _EVP.pbkdf2(secret, salt, rounds, keylen)
except OverflowError:
raise ValueError, "key length too long"
#resolve prf
encode_block, digest_size = _resolve_prf(prf)
#figure out how many blocks we'll need
bcount = (keylen+digest_size-1)//digest_size
if bcount >= MAX_BLOCKS:
raise ValueError, "key length to long"
#build up key from blocks
out = StringIO()
write = out.write
for i in xrange(1,bcount+1):
block = tmp = encode_block(secret, salt + pack(">L", i))
#NOTE: could potentially unroll this loop somewhat for speed,
# or find some faster way to accumulate & xor tmp values together
for j in xrange(rounds-1):
tmp = encode_block(secret, tmp)
block = xor_bytes(block, tmp)
write(block)
#and done
return out.getvalue()[:keylen]
#=================================================================================
#eof
#=================================================================================
|