Permalink
Browse files

Add even faster pbkdf2 provider based on nettle

  • Loading branch information...
1 parent 3200a12 commit 7c751ee3dff8774f3f9f074da3ce67eb78cde612 @Roguelazer committed Mar 6, 2013
Showing with 67 additions and 8 deletions.
  1. +12 −3 README.markdown
  2. +33 −0 onepassword/_pbkdf2_nettle.py
  3. +11 −5 onepassword/pbkdf2.py
  4. +11 −0 tests/unit/pbkdf2_tests.py
View
@@ -18,9 +18,18 @@ This project depends on the following upstream libraries:
This is a human-readable denormalized list; for the actual list, look at `setup.py`.
-If you have [M2Crypto](http://chandlerproject.org/Projects/MeTooCrypto)
-installed key derivation (and thus keychain unlocking) will be approximately
-five times faster. The PyCrypto fallback is totally functional, though.
+There are three different providers for the most expensive crypto operation
+(key derivation via [PBKDF2](http://en.wikipedia.org/wiki/PBKDF2)):
+* [nettle](http://www.lysator.liu.se/~nisse/nettle/) (via `ctypes`):
+ finishes test suite in 0.35s
+* openssl (via [M2Crypto](http://chandlerproject.org/Projects/MeTooCrypto)):
+ finishes test suite in 1.85s
+* [PyCrypto](https://www.dlitz.net/software/pycrypto/): finishes test suite
+ in 8.08s
+
+These will be imported in that order. If you don't have one of the faster
+options (nettle, M2Crypto), everything will fall back gracefully to PyCrypto
+(which is also used for the speedy symmetric crypto).
Unit tests are written using Yelp's
[testify](https://github.com/Yelp/testify) framework; you should install it
@@ -0,0 +1,33 @@
+import ctypes
+import ctypes.util
+
+"""Simple ctypes wrapper around nettle. Idea came from https://github.com/fredrikt/python-ndnkdf"""
+
+
+_nettle = ctypes.cdll.LoadLibrary(ctypes.util.find_library('nettle'))
+for function in ('nettle_hmac_sha1_update', 'nettle_hmac_sha512_update', 'nettle_hmac_sha1_digest', 'nettle_hmac_sha512_digest', 'nettle_pbkdf2'):
+ if not hasattr(_nettle, function):
+ raise ImportError(function)
+
+
+def _pbkdf2(password, salt, length, iterations, hash_size, set_fn, update_fn, digest_fn):
+ buf = ctypes.create_string_buffer('', size=max(length, hash_size))
+ # TODO: 1024 bytes is almost definitely not the size of this structure
+ shactx = ctypes.create_string_buffer('', size = 1024)
+ set_fn(ctypes.byref(shactx), len(password), password)
+ _nettle.nettle_pbkdf2(
+ ctypes.byref(shactx),
+ update_fn,
+ digest_fn,
+ hash_size, int(iterations),
+ len(salt), salt,
+ max(length, hash_size), ctypes.byref(buf))
+ return buf.raw[:length]
+
+
+def pbkdf2_sha1(password, salt, length, iterations):
+ return _pbkdf2(password, salt, length, iterations, 20, _nettle.nettle_hmac_sha1_set_key, _nettle.nettle_hmac_sha1_update, _nettle.nettle_hmac_sha1_digest)
+
+
+def pbkdf2_sha512(password, salt, length, iterations):
+ return _pbkdf2(password, salt, length, iterations, 64, _nettle.nettle_hmac_sha512_set_key, _nettle.nettle_hmac_sha512_update, _nettle.nettle_hmac_sha512_digest)
View
@@ -1,10 +1,16 @@
try:
- from _pbkdf2_m2crypto import pbkdf2_sha1, pbkdf2_sha512
+ from _pbkdf2_nettle import pbkdf2_sha1, pbkdf2_sha512
# make pyflakes happy
pbkdf2_sha1 = pbkdf2_sha1
pbkdf2_sha512 = pbkdf2_sha512
except ImportError:
- from _pbkdf2_pycrypto import pbkdf2_sha1, pbkdf2_sha512
- # make pyflakes happy
- pbkdf2_sha1 = pbkdf2_sha1
- pbkdf2_sha512 = pbkdf2_sha512
+ try:
+ from _pbkdf2_m2crypto import pbkdf2_sha1, pbkdf2_sha512
+ # make pyflakes happy
+ pbkdf2_sha1 = pbkdf2_sha1
+ pbkdf2_sha512 = pbkdf2_sha512
+ except ImportError:
+ from _pbkdf2_pycrypto import pbkdf2_sha1, pbkdf2_sha512
+ # make pyflakes happy
+ pbkdf2_sha1 = pbkdf2_sha1
+ pbkdf2_sha512 = pbkdf2_sha512
View
@@ -3,6 +3,7 @@
from onepassword import _pbkdf2_pycrypto
from onepassword import _pbkdf2_m2crypto
+from onepassword import _pbkdf2_nettle
class PBKDF2SHA1TestCase(T.TestCase):
@@ -24,6 +25,11 @@ def test_vectors_m2crypto(self):
generated = _pbkdf2_m2crypto.pbkdf2_sha1(password, salt, length=16, iterations=iterations)
T.assert_equal(generated, expected_key)
+ def test_vectors_nettle(self):
+ for password, salt, iterations, expected_key in self.VECTORS:
+ generated = _pbkdf2_nettle.pbkdf2_sha1(password, salt, length=16, iterations=iterations)
+ T.assert_equal(generated, expected_key)
+
class PBKDF2SHA512TestCase(T.TestCase):
VECTORS = (
@@ -43,3 +49,8 @@ def test_vectors_m2crypto(self):
for password, salt, iterations, expected_key in self.VECTORS:
generated = _pbkdf2_m2crypto.pbkdf2_sha512(password, salt, length=16, iterations=iterations)
T.assert_equal(generated, expected_key)
+
+ def test_vectors_nettle(self):
+ for password, salt, iterations, expected_key in self.VECTORS:
+ generated = _pbkdf2_nettle.pbkdf2_sha512(password, salt, length=16, iterations=iterations)
+ T.assert_equal(generated, expected_key)

0 comments on commit 7c751ee

Please sign in to comment.