summaryrefslogtreecommitdiff
path: root/passlib/handlers/phpass.py
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2011-03-22 19:27:06 -0400
committerEli Collins <elic@assurancetechnologies.com>2011-03-22 19:27:06 -0400
commitde2622bd4f6477983d09a001000f0e30856065d3 (patch)
tree3673ea80500b748e222fd15ccaed1804a736641e /passlib/handlers/phpass.py
parentcce3a27b515c15410512bc7fbf7ddd6b2ff2af44 (diff)
downloadpasslib-de2622bd4f6477983d09a001000f0e30856065d3.tar.gz
big rename: driver->handler
=========================== Renamed all references from password hash "driver" -> password hash "handler", to be more consistent with existing phrasing. This also required rearranging quite a lot of modules.
Diffstat (limited to 'passlib/handlers/phpass.py')
-rw-r--r--passlib/handlers/phpass.py151
1 files changed, 151 insertions, 0 deletions
diff --git a/passlib/handlers/phpass.py b/passlib/handlers/phpass.py
new file mode 100644
index 0000000..cbf6c70
--- /dev/null
+++ b/passlib/handlers/phpass.py
@@ -0,0 +1,151 @@
+"""passlib.handlers.phpass - PHPass Portable Crypt
+
+phppass located - http://www.openwall.com/phpass/
+algorithm described - http://www.openwall.com/articles/PHP-Users-Passwords
+
+phpass context - blowfish, ext_des_crypt, phpass
+"""
+#=========================================================
+#imports
+#=========================================================
+#core
+from hashlib import md5
+import re
+import logging; log = logging.getLogger(__name__)
+from warnings import warn
+#site
+#libs
+from passlib.utils import h64
+from passlib.utils.handlers import ExtHash
+#pkg
+#local
+__all__ = [
+ "phpass",
+]
+
+#=========================================================
+#phpass
+#=========================================================
+class phpass(ExtHash):
+ """This class implements the PHPass Portable Hash, and follows the :ref:`password-hash-api`.
+
+ It supports a fixed-length salt, and a variable number of rounds.
+
+ The :meth:`encrypt()` and :meth:`genconfig` methods accept the following optional keywords:
+
+ :param salt:
+ Optional salt string.
+ If not specified, one will be autogenerated (this is recommended).
+ If specified, it must be 8 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
+
+ :param rounds:
+ Optional number of rounds to use.
+ Defaults to 9, must be between 7 and 30, inclusive.
+ This value is logarithmic, the actual number of iterations used will be :samp:`2**{rounds}`.
+
+ :param ident:
+ phpBB3 uses ``H`` instead of ``P`` for it's identifier,
+ this may be set to ``H`` in order to generate phpBB3 compatible hashes.
+ it defaults to ``P``.
+
+ """
+
+ #=========================================================
+ #class attrs
+ #=========================================================
+ name = "phpass"
+ setting_kwds = ("salt", "rounds", "ident")
+
+ min_salt_chars = max_salt_chars = 8
+
+ default_rounds = 9
+ min_rounds = 7
+ max_rounds = 30
+ rounds_cost = "log2"
+
+ _strict_rounds_bounds = True
+ _extra_init_settings = ("ident",)
+
+ #=========================================================
+ #instance attrs
+ #=========================================================
+ ident = None
+
+ #=========================================================
+ #init
+ #=========================================================
+ @classmethod
+ def norm_ident(cls, ident, strict=False):
+ if not ident:
+ if strict:
+ raise ValueError, "no ident specified"
+ ident = "P"
+ if ident not in ("P", "H"):
+ raise ValueError, "invalid ident: %r" % (ident,)
+ return ident
+
+ #=========================================================
+ #formatting
+ #=========================================================
+
+ @classmethod
+ def identify(cls, hash):
+ return bool(hash) and (hash.startswith("$P$") or hash.startswith("$H$"))
+
+ #$P$9IQRaTwmfeRo7ud9Fh4E2PdI0S3r.L0
+ # $P$
+ # 9
+ # IQRaTwmf
+ # eRo7ud9Fh4E2PdI0S3r.L0
+ _pat = re.compile(r"""
+ ^
+ \$
+ (?P<ident>[PH])
+ \$
+ (?P<rounds>[A-Za-z0-9./])
+ (?P<salt>[A-Za-z0-9./]{8})
+ (?P<chk>[A-Za-z0-9./]{22})?
+ $
+ """, re.X)
+
+ @classmethod
+ def from_string(cls, hash):
+ if not hash:
+ raise ValueError, "no hash specified"
+ m = cls._pat.match(hash)
+ if not m:
+ raise ValueError, "invalid phpass portable hash"
+ ident, rounds, salt, chk = m.group("ident", "rounds", "salt", "chk")
+ return cls(
+ ident=ident,
+ rounds=h64.decode_6bit(rounds),
+ salt=salt,
+ checksum=chk,
+ strict=bool(chk),
+ )
+
+ def to_string(self):
+ return "$%s$%s%s%s" % (self.ident, h64.encode_6bit(self.rounds), self.salt, self.checksum or '')
+
+ #=========================================================
+ #backend
+ #=========================================================
+ def calc_checksum(self, secret):
+ #FIXME: can't find definitive policy on how phpass handles non-ascii.
+ if isinstance(secret, unicode):
+ secret = secret.encode("utf-8")
+ real_rounds = 1<<self.rounds
+ result = md5(self.salt + secret).digest()
+ r = 0
+ while r < real_rounds:
+ result = md5(result + secret).digest()
+ r += 1
+ return h64.encode_bytes(result)
+
+ #=========================================================
+ #eoc
+ #=========================================================
+
+#=========================================================
+#eof
+#=========================================================