cryptacular
Advanced tools
+5
-0
@@ -0,1 +1,6 @@ | ||
| 1.3 | ||
| === | ||
| - Python 3 support contributed by Frank Smit (some tests do not run) | ||
| - Fix staticmethod issue with CRYPTPasswordManager | ||
| 1.2.1 | ||
@@ -2,0 +7,0 @@ ===== |
@@ -1,4 +0,4 @@ | ||
| Metadata-Version: 1.0 | ||
| Metadata-Version: 1.1 | ||
| Name: cryptacular | ||
| Version: 1.2.1 | ||
| Version: 1.3 | ||
| Summary: A password hashing framework with bcrypt and pbkdf2. | ||
@@ -131,2 +131,7 @@ Home-page: http://bitbucket.org/dholth/cryptacular/ | ||
| 1.3 | ||
| === | ||
| - Python 3 support contributed by Frank Smit (some tests do not run) | ||
| - Fix staticmethod issue with CRYPTPasswordManager | ||
| 1.2.1 | ||
@@ -191,3 +196,10 @@ ===== | ||
| Classifier: Programming Language :: Python | ||
| Classifier: Programming Language :: Python :: 2 | ||
| Classifier: Programming Language :: Python :: 2.6 | ||
| Classifier: Programming Language :: Python :: 2.7 | ||
| Classifier: Programming Language :: Python :: 3 | ||
| Classifier: Programming Language :: Python :: 3.2 | ||
| Classifier: Programming Language :: Python :: Implementation :: CPython | ||
| Classifier: Programming Language :: Python :: Implementation :: PyPy | ||
| Classifier: Programming Language :: C | ||
| Classifier: Topic :: Security :: Cryptography |
@@ -14,3 +14,2 @@ CHANGES.txt | ||
| crypt_blowfish-1.2/crypt_blowfish.c | ||
| crypt_blowfish-1.2/crypt_blowfish.c~ | ||
| crypt_blowfish-1.2/crypt_blowfish.h | ||
@@ -17,0 +16,0 @@ crypt_blowfish-1.2/crypt_gensalt.c |
| # Copyright (c) 2009 Daniel Holth <dholth@fastmail.fm> | ||
| # | ||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| # of this software and associated documentation files (the "Software"), to deal | ||
| # of this software and associated documentation files (the 'Software'), to deal | ||
| # in the Software without restriction, including without limitation the rights | ||
@@ -13,3 +13,3 @@ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| # | ||
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
@@ -24,2 +24,3 @@ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| import os | ||
@@ -29,50 +30,48 @@ import re | ||
| from cryptacular.bcrypt._bcrypt import crypt_rn, crypt_gensalt_rn | ||
| import cryptacular.core | ||
| from cryptacular.core import _cmp, check_unicode | ||
| class BCRYPTPasswordManager(object): | ||
| # for testing | ||
| crypt_rn = crypt_rn | ||
| crypt_gensalt_rn = crypt_gensalt_rn | ||
| SCHEME = "BCRYPT" | ||
| PREFIX = "$2a$" | ||
| ROUNDS = 10 | ||
| _scheme = 'BCRYPT' | ||
| _prefix = '$2a$' | ||
| _rounds = 10 | ||
| _bcrypt_syntax = re.compile('\$2a\$[0-9]{2}\$[./A-Za-z0-9]{53}') | ||
| def encode(self, password, rounds=None): | ||
| """Hash a password using bcrypt. | ||
| def encode(self, text, rounds=None): | ||
| '''Hash a password using bcrypt. | ||
| Note: only the first 72 characters of password are significant. | ||
| """ | ||
| work_factor = rounds or self.ROUNDS | ||
| settings = crypt_gensalt_rn('$2a$', work_factor, os.urandom(16)) | ||
| ''' | ||
| rounds = rounds or self._rounds | ||
| settings = self.crypt_gensalt_rn(self._prefix, rounds, os.urandom(16)) | ||
| if settings is None: | ||
| raise ValueError("_bcrypt.crypt_gensalt_rn returned None") # pragma NO COVERAGE | ||
| if isinstance(password, unicode): | ||
| password = password.encode('utf-8') | ||
| if not isinstance(password, str): | ||
| raise TypeError("password must be a str") | ||
| rc = crypt_rn(password, settings) | ||
| if rc is None: | ||
| raise ValueError("_bcrypt.crypt_rn returned None") # pragma NO COVERAGE | ||
| return rc | ||
| raise ValueError('_bcrypt.crypt_gensalt_rn returned None') | ||
| def check(self, encoded, password): | ||
| """Check a bcrypt password hash against a password.""" | ||
| if isinstance(password, unicode): | ||
| password = password.encode('utf-8') | ||
| if isinstance(encoded, unicode): | ||
| encoded = encoded.encode('utf-8') | ||
| if not isinstance(password, str): | ||
| raise TypeError("password must be a str") | ||
| if not isinstance(encoded, str): | ||
| raise TypeError("encoded must be a str") | ||
| encoded = self.crypt_rn(check_unicode(text), settings) | ||
| if encoded is None: | ||
| raise ValueError('_bcrypt.crypt_rn returned None') | ||
| return encoded | ||
| def check(self, encoded, text): | ||
| '''Check a bcrypt password hash against a password. | ||
| ''' | ||
| if not self.match(encoded): | ||
| return False | ||
| rc = crypt_rn(password, encoded) | ||
| if rc is None: | ||
| raise ValueError("_bcrypt.crypt_rn returned None") | ||
| return cryptacular.core._cmp(rc, encoded) | ||
| encoded_text = self.crypt_rn(check_unicode(text), encoded) | ||
| if encoded_text is None: | ||
| raise ValueError('_bcrypt.crypt_rn returned None') | ||
| return _cmp(encoded_text, check_unicode(encoded)) | ||
| def match(self, hash): | ||
| """Return True if hash looks like a BCRYPT password hash.""" | ||
| '''Return True if hash looks like a BCRYPT password hash. | ||
| ''' | ||
| return self._bcrypt_syntax.match(hash) is not None | ||
| /* Python extension module for bcrypt2. | ||
| * | ||
| * Daniel Holth <dholth@fastmail.fm>, 2010 | ||
| * Frank Smit <frank@61924.nl>, 2011 (added Python 3 support) | ||
| * | ||
@@ -24,70 +25,112 @@ * Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| #define PY_SSIZE_T_CLEAN | ||
| #include <Python.h> | ||
| #include "ow-crypt.h" | ||
| static PyObject *_py_crypt_rn(PyObject *self, PyObject *args) { | ||
| char *rc; | ||
| const char *key; | ||
| const char *setting; | ||
| char output[61]; | ||
| memset(output, 0, sizeof(output)); | ||
| static PyObject * | ||
| _py_crypt_rn(PyObject *self, PyObject *args) | ||
| { | ||
| char *rc; | ||
| const char *key; | ||
| const char *setting; | ||
| char output[61]; | ||
| if (!PyArg_ParseTuple(args, "ss", &key, &setting)) { | ||
| return NULL; | ||
| } | ||
| memset(output, 0, sizeof(output)); | ||
| Py_BEGIN_ALLOW_THREADS; | ||
| /* key, setting, output, size */ | ||
| rc = crypt_rn(key, setting, output, sizeof(output)); | ||
| if (!PyArg_ParseTuple(args, "ss", &key, &setting)) { | ||
| return NULL; | ||
| } | ||
| Py_END_ALLOW_THREADS; | ||
| Py_BEGIN_ALLOW_THREADS; | ||
| if (rc == NULL) { | ||
| Py_RETURN_NONE; | ||
| } | ||
| /* key, setting, output, size */ | ||
| rc = crypt_rn(key, setting, output, sizeof(output)); | ||
| output[sizeof(output) - 1] = '\0'; | ||
| Py_END_ALLOW_THREADS; | ||
| return Py_BuildValue("s", output); | ||
| if (rc == NULL) { | ||
| Py_RETURN_NONE; | ||
| } | ||
| output[sizeof(output) - 1] = '\0'; | ||
| return Py_BuildValue("s", output); | ||
| } | ||
| static PyObject *_py_crypt_gensalt_rn(PyObject *self, PyObject *args) { | ||
| char *rc; | ||
| const char *prefix; | ||
| const int count; | ||
| const char *salt; | ||
| const Py_ssize_t salt_len; | ||
| char output[30]; | ||
| memset(output, 0, sizeof(output)); | ||
| if (!PyArg_ParseTuple(args, "sis#", &prefix, &count, &salt, &salt_len)) { | ||
| return NULL; | ||
| } | ||
| static PyObject * | ||
| _py_crypt_gensalt_rn(PyObject *self, PyObject *args) | ||
| { | ||
| char *rc; | ||
| const char *prefix; | ||
| const int count; | ||
| const char *salt; | ||
| const Py_ssize_t salt_len; | ||
| char output[30]; | ||
| /* prefix, count, input, size, output, output_size */ | ||
| rc = crypt_gensalt_rn(prefix, count, salt, salt_len, output, sizeof(output)); | ||
| memset(output, 0, sizeof(output)); | ||
| if (rc == NULL) { | ||
| Py_RETURN_NONE; | ||
| } | ||
| if (!PyArg_ParseTuple(args, "sis#", &prefix, &count, &salt, &salt_len)) { | ||
| return NULL; | ||
| } | ||
| output[sizeof(output) - 1] = '\0'; | ||
| Py_BEGIN_ALLOW_THREADS; | ||
| return Py_BuildValue("s", output); | ||
| /* prefix, count, input, size, output, output_size */ | ||
| rc = crypt_gensalt_rn(prefix, count, salt, salt_len, output, sizeof(output)); | ||
| Py_END_ALLOW_THREADS; | ||
| if (rc == NULL) { | ||
| Py_RETURN_NONE; | ||
| } | ||
| output[sizeof(output) - 1] = '\0'; | ||
| return Py_BuildValue("s", output); | ||
| } | ||
| static PyMethodDef BcryptMethods[] = { | ||
| {"crypt_rn", _py_crypt_rn, METH_VARARGS, "Encrypt password"}, | ||
| {"crypt_gensalt_rn", _py_crypt_gensalt_rn, METH_VARARGS, "Generate salt"}, | ||
| {NULL, NULL, 0, NULL} | ||
| static PyMethodDef _bcrypt_methods[] = { | ||
| {"crypt_rn", _py_crypt_rn, METH_VARARGS, "Encrypt password"}, | ||
| {"crypt_gensalt_rn", _py_crypt_gensalt_rn, METH_VARARGS, "Generate salt"}, | ||
| {NULL, NULL, 0, NULL} /* Sentinel */ | ||
| }; | ||
| PyMODINIT_FUNC | ||
| init_bcrypt(void) | ||
| #if PY_MAJOR_VERSION >= 3 | ||
| static struct PyModuleDef moduledef = { | ||
| PyModuleDef_HEAD_INIT, | ||
| "_bcrypt", | ||
| NULL, | ||
| -1, | ||
| _bcrypt_methods, | ||
| NULL, | ||
| NULL, | ||
| NULL, | ||
| NULL | ||
| }; | ||
| #define INITERROR return NULL | ||
| PyObject * | ||
| PyInit__bcrypt(void) | ||
| #else | ||
| #define INITERROR return | ||
| PyMODINIT_FUNC | ||
| init_bcrypt(void) | ||
| #endif | ||
| { | ||
| (void) Py_InitModule("_bcrypt", BcryptMethods); | ||
| #if PY_MAJOR_VERSION >= 3 | ||
| PyObject *module = PyModule_Create(&moduledef); | ||
| #else | ||
| PyObject *module = Py_InitModule("_bcrypt", _bcrypt_methods); | ||
| #endif | ||
| #if PY_MAJOR_VERSION >= 3 | ||
| return module; | ||
| #endif | ||
| } | ||
@@ -62,1 +62,23 @@ from nose.tools import eq_, raises, assert_false, assert_true, assert_not_equal | ||
| assert_true(manager.check(hash, password)) | ||
| @raises(ValueError) | ||
| def test_fail_1(self): | ||
| def return_none(*args): return None | ||
| bcrypt = BCRYPTPasswordManager() | ||
| bcrypt.crypt_gensalt_rn = return_none | ||
| bcrypt.encode('foo') | ||
| @raises(ValueError) | ||
| def test_fail_2(self): | ||
| def return_none(*args): return None | ||
| bcrypt = BCRYPTPasswordManager() | ||
| bcrypt.crypt_rn = return_none | ||
| bcrypt.encode('foo') | ||
| @raises(ValueError) | ||
| def test_fail_3(self): | ||
| def return_none(*args): return None | ||
| bcrypt = BCRYPTPasswordManager() | ||
| pw = bcrypt.encode('foobar') | ||
| bcrypt.crypt_rn = return_none | ||
| bcrypt.check(pw, 'foo') |
@@ -23,4 +23,20 @@ # -*- coding: utf-8 -*- | ||
| __all__ = ['DelegatingPasswordManager', 'PasswordChecker', 'PasswordManager'] | ||
| __all__ = [ | ||
| 'DelegatingPasswordManager', | ||
| 'PasswordChecker', | ||
| 'PasswordManager', | ||
| 'check_unicode' | ||
| ] | ||
| if 'unicode' in __builtins__: | ||
| def check_unicode(text): | ||
| if isinstance(text, unicode): | ||
| text = text.encode('utf-8') | ||
| return text | ||
| else: # pragma NO COVERAGE | ||
| def check_unicode(text): | ||
| return text | ||
| class PasswordChecker(object): | ||
@@ -36,3 +52,4 @@ | ||
| strings. The schemes included with this package convert unicode | ||
| 'encoded' and 'password' to utf-8 as necessary.""" | ||
| 'encoded' and 'password' to utf-8 as necessary. | ||
| """ | ||
| raise NotImplementedError() | ||
@@ -46,8 +63,11 @@ | ||
| class PasswordManager(PasswordChecker): | ||
| def encode(self, password): | ||
| """Return hash of 'password' using this scheme.""" | ||
| """Return hash of 'password' using this scheme. | ||
| """ | ||
| raise NotImplementedError() | ||
| class DelegatingPasswordManager(object): | ||
@@ -88,3 +108,4 @@ | ||
| def _cmp(a, b): | ||
| """Constant-time comparison""" | ||
| """Constant-time comparison. | ||
| """ | ||
| if len(a) != len(b): | ||
@@ -91,0 +112,0 @@ return False |
@@ -36,7 +36,7 @@ # -*- coding: utf-8 -*- | ||
| __all__ = ['CRYPTPasswordManager', 'OLDCRYPT', 'MD5CRYPT', 'SHA256CRYPT', | ||
| 'SHA512CRYPT', 'BCRYPT', 'available'] | ||
| 'SHA512CRYPT', 'BCRYPT'] | ||
| import os | ||
| import re | ||
| import crypt | ||
| import crypt as system_crypt | ||
| import base64 | ||
@@ -52,18 +52,20 @@ | ||
| def available(prefix, _crypt=crypt.crypt): | ||
| # Lame 'is implemented' check. | ||
| l = len(_crypt('implemented?', prefix + 'xyzzy')) | ||
| if prefix == OLDCRYPT: | ||
| if l != 13: | ||
| return False | ||
| elif l < 26: | ||
| return False | ||
| return True | ||
| class CRYPTPasswordManager(object): | ||
| _crypt = crypt.crypt | ||
| _crypt = staticmethod(system_crypt.crypt) | ||
| def available(self, prefix): | ||
| # Lame 'is implemented' check. | ||
| l = len(self._crypt('implemented?', prefix + 'xyzzy')) | ||
| if prefix == OLDCRYPT: | ||
| if l != 13: | ||
| return False | ||
| elif l < 26: | ||
| return False | ||
| return True | ||
| def __init__(self, prefix): | ||
| """prefix: $1$ etc. indicating hashing scheme.""" | ||
| self.PREFIX = prefix | ||
| if not available(prefix, self._crypt): | ||
| if not self.available(prefix): | ||
| raise NotImplementedError | ||
@@ -70,0 +72,0 @@ |
@@ -53,2 +53,3 @@ from nose.tools import eq_, raises, assert_false, assert_true, assert_not_equal | ||
| available = CRYPTPasswordManager('').available | ||
@@ -55,0 +56,0 @@ if available(BCRYPT): |
@@ -30,3 +30,3 @@ # Copyright (c) 2009 Daniel Holth <dholth@fastmail.fm> | ||
| except (ImportError, AttributeError): # pragma NO COVERAGE | ||
| import pbkdf2 | ||
| from . import pbkdf2 | ||
| _pbkdf2 = pbkdf2.pbkdf2 | ||
@@ -33,0 +33,0 @@ |
+14
-2
@@ -1,4 +0,4 @@ | ||
| Metadata-Version: 1.0 | ||
| Metadata-Version: 1.1 | ||
| Name: cryptacular | ||
| Version: 1.2.1 | ||
| Version: 1.3 | ||
| Summary: A password hashing framework with bcrypt and pbkdf2. | ||
@@ -131,2 +131,7 @@ Home-page: http://bitbucket.org/dholth/cryptacular/ | ||
| 1.3 | ||
| === | ||
| - Python 3 support contributed by Frank Smit (some tests do not run) | ||
| - Fix staticmethod issue with CRYPTPasswordManager | ||
| 1.2.1 | ||
@@ -191,3 +196,10 @@ ===== | ||
| Classifier: Programming Language :: Python | ||
| Classifier: Programming Language :: Python :: 2 | ||
| Classifier: Programming Language :: Python :: 2.6 | ||
| Classifier: Programming Language :: Python :: 2.7 | ||
| Classifier: Programming Language :: Python :: 3 | ||
| Classifier: Programming Language :: Python :: 3.2 | ||
| Classifier: Programming Language :: Python :: Implementation :: CPython | ||
| Classifier: Programming Language :: Python :: Implementation :: PyPy | ||
| Classifier: Programming Language :: C | ||
| Classifier: Topic :: Security :: Cryptography |
+8
-1
@@ -14,3 +14,3 @@ import os | ||
| setup(name='cryptacular', | ||
| version='1.2.1', | ||
| version='1.3', | ||
| description='A password hashing framework with bcrypt and pbkdf2.', | ||
@@ -22,2 +22,9 @@ long_description=README + '\n\n' + CHANGES, | ||
| "Programming Language :: Python", | ||
| "Programming Language :: Python :: 2", | ||
| "Programming Language :: Python :: 2.6", | ||
| "Programming Language :: Python :: 2.7", | ||
| "Programming Language :: Python :: 3", | ||
| "Programming Language :: Python :: 3.2", | ||
| "Programming Language :: Python :: Implementation :: CPython", | ||
| "Programming Language :: Python :: Implementation :: PyPy", | ||
| "Programming Language :: C", | ||
@@ -24,0 +31,0 @@ "Topic :: Security :: Cryptography", |
Sorry, the diff of this file is not supported yet
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
652
5.84%143764
-16.77%41
-2.38%