Russell Hay avatar Russell Hay committed 1a5a0d6

Stopping point in working towards better hashing choices

Comments (0)

Files changed (2)

 import string
 import random
 import hashlib
+import logging ; logger = logging.getLogger(__name__)
 
 SALT_CHARACTERS = string.letters + string.digits
+SHA512_LEGACY = -1
+PBKDF2 = 1
+BCRYPT = 2
+SHA512 = 3
+_HASH_MAPPING = {
+    'PBKDF2': PBKDF2,
+    'BCRYPT': BCRYPT,
+    'SHA512': SHA512
+}
 
 class InvalidPasswordError(Exception):
     """ Invalid Password was provided to the validate method """
     """
 
     __PASSWORD_FIELD__ = "hash"
-    __SALT_COUNT__ = 2
+    __SALT_COUNT__ = 10
 
     @property
     def password(self):
         raise AttributeError("Password is not readable")
 
+    @property
+    def hash_type(self):
+        hash_value = getattr(self, self.__PASSWORD_FIELD__)
+        if hash_value[:2] != "$$":
+            return SHA512_LEGACY
+        (v, _) = hash_value.split(";")[2:]
+        return _HASH_MAPPING[v]
+
     @password.setter
     def password(self, value):
         """ Generate the password has by setting this property
 
         self._salt = str(value)
 
+    def _validate_legacy(self, hash_value, throw, value):
+        salt = hash_value[:self.__SALT_COUNT__]
+        expected = hash_value[(self.__SALT_COUNT__ + 1):]
+        actual = self._generate_hash(salt, value)
+        comparison = [ord(a) ^ ord(b) for a, b in zip(actual, expected)]
+        valid_password = sum(comparison) == 0
+        if throw and not valid_password:
+            raise InvalidPasswordError()
+        return valid_password
+
     def validate(self, value, throw=False):
         """ Validate that a password matches the password hash
 
 
         """
         hash_value = getattr(self, self.__PASSWORD_FIELD__)
-        salt = hash_value[:self.__SALT_COUNT__]
-        expected = hash_value[(self.__SALT_COUNT__+1):]
+        hash_type = self.hash_type
+        if hash_type == SHA512_LEGACY:
+            valid_password = self._validate_legacy(hash_value, throw, value)
+            if valid_password:
+                logging.debug("Upgrading password")
+                self._upgrade_password(value)
 
-        actual = self._generate_hash(salt, value)
-
-        comparison = [ord(a) ^ ord(b) for a,b in zip(actual,expected)]
-        valid_password = sum(comparison) == 0
+            return valid_password
 
-        if throw and not valid_password:
-            raise InvalidPasswordError()
-
-        return valid_password
+        return False
 
     def _generate_hash(self, salt, value):
         hash_string = "{salt};{value}".format(salt=salt, value=value)
     __PASSWORD_FIELD__ = "password_hash"
     __SALT_COUNT__ = 4
 
-    def __init__(self):
-        self.password_hash = None
+    def __init__(self, password_hash=None):
+        self.password_hash = password_hash
 
     @property
     def password_field(self):
 
     obj.password = "1234"
     obj.validate("12345", throw=True)
+
+def test_hash_upgrade():
+    obj = _TestObject("B6Jf;45ca446b620151bcd17b30ef101545daac3d0ede5a2d3ecf57b16dd788d0fc950a4269c0993c40eae30907b01bfb3e54ca91d23820959b7e60dbd4d8e41c1c3a")
+    obj.validate("1234", throw=True)
+    t.eq_(obj.hash_type, hashish.PBKDF2)
+    t.fail()
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.