passlib-1.5.3/0000755000175000017500000000000011643754212014310 5ustar biscuitbiscuit00000000000000passlib-1.5.3/passlib.egg-info/0000755000175000017500000000000011643754212017437 5ustar biscuitbiscuit00000000000000passlib-1.5.3/passlib.egg-info/SOURCES.txt0000644000175000017500000000742111643754211021326 0ustar biscuitbiscuit00000000000000CHANGES LICENSE MANIFEST.in README setup.cfg setup.py docs/conf.py docs/conf.py.orig docs/contents.rst docs/copyright.rst docs/history.rst docs/index.rst docs/install.rst docs/make.py docs/modular_crypt_format.rst docs/new_app_quickstart.rst docs/notes.txt docs/overview.rst docs/password_hash_api.rst docs/_static/logo-128.png docs/_static/logo-64.png docs/_static/logo.ico docs/_static/logo.png docs/_static/logo.svg docs/_static/masthead.png docs/_static/masthead.svg docs/lib/passlib.apache.rst docs/lib/passlib.apps.rst docs/lib/passlib.context-interface.rst docs/lib/passlib.context-options.rst docs/lib/passlib.context-usage.rst docs/lib/passlib.context.rst docs/lib/passlib.ext.django.rst docs/lib/passlib.hash.apr_md5_crypt.rst docs/lib/passlib.hash.atlassian_pbkdf2_sha1.rst docs/lib/passlib.hash.bcrypt.rst docs/lib/passlib.hash.bigcrypt.rst docs/lib/passlib.hash.bsdi_crypt.rst docs/lib/passlib.hash.crypt16.rst docs/lib/passlib.hash.cta_pbkdf2_sha1.rst docs/lib/passlib.hash.des_crypt.rst docs/lib/passlib.hash.django_std.rst docs/lib/passlib.hash.dlitz_pbkdf2_sha1.rst docs/lib/passlib.hash.fshp.rst docs/lib/passlib.hash.grub_pbkdf2_sha512.rst docs/lib/passlib.hash.hex_digests.rst docs/lib/passlib.hash.ldap_crypt.rst docs/lib/passlib.hash.ldap_other.rst docs/lib/passlib.hash.ldap_pbkdf2_digest.rst docs/lib/passlib.hash.ldap_std.rst docs/lib/passlib.hash.md5_crypt.rst docs/lib/passlib.hash.mysql323.rst docs/lib/passlib.hash.mysql41.rst docs/lib/passlib.hash.nthash.rst docs/lib/passlib.hash.oracle10.rst docs/lib/passlib.hash.oracle11.rst docs/lib/passlib.hash.pbkdf2_digest.rst docs/lib/passlib.hash.phpass.rst docs/lib/passlib.hash.plaintext.rst docs/lib/passlib.hash.postgres_md5.rst docs/lib/passlib.hash.rst docs/lib/passlib.hash.sha1_crypt.rst docs/lib/passlib.hash.sha256_crypt.rst docs/lib/passlib.hash.sha512_crypt.rst docs/lib/passlib.hash.sun_md5_crypt.rst docs/lib/passlib.hash.unix_fallback.rst docs/lib/passlib.hosts.rst docs/lib/passlib.registry.rst docs/lib/passlib.utils.des.rst docs/lib/passlib.utils.h64.rst docs/lib/passlib.utils.handlers.rst docs/lib/passlib.utils.md4.rst docs/lib/passlib.utils.pbkdf2.rst docs/lib/passlib.utils.rst passlib/__init__.py passlib/apache.py passlib/apps.py passlib/context.py passlib/default.cfg passlib/hash.py passlib/hosts.py passlib/registry.py passlib/win32.py passlib.egg-info/PKG-INFO passlib.egg-info/SOURCES.txt passlib.egg-info/dependency_links.txt passlib.egg-info/top_level.txt passlib.egg-info/zip-safe passlib/_setup/__init__.py passlib/_setup/cond2to3.py passlib/_setup/docdist.py passlib/_setup/stamp.py passlib/ext/__init__.py passlib/ext/django/__init__.py passlib/ext/django/models.py passlib/ext/django/utils.py passlib/handlers/__init__.py passlib/handlers/bcrypt.py passlib/handlers/des_crypt.py passlib/handlers/digests.py passlib/handlers/django.py passlib/handlers/fshp.py passlib/handlers/ldap_digests.py passlib/handlers/md5_crypt.py passlib/handlers/misc.py passlib/handlers/mysql.py passlib/handlers/nthash.py passlib/handlers/oracle.py passlib/handlers/pbkdf2.py passlib/handlers/phpass.py passlib/handlers/postgres.py passlib/handlers/roundup.py passlib/handlers/sha1_crypt.py passlib/handlers/sha2_crypt.py passlib/handlers/sun_md5_crypt.py passlib/tests/__init__.py passlib/tests/__main__.py passlib/tests/_test_bad_register.py passlib/tests/genconfig.py passlib/tests/sample_config_1s.cfg passlib/tests/test_apache.py passlib/tests/test_apps.py passlib/tests/test_context.py passlib/tests/test_drivers.py passlib/tests/test_ext_django.py passlib/tests/test_hosts.py passlib/tests/test_registry.py passlib/tests/test_utils.py passlib/tests/test_utils_handlers.py passlib/tests/test_win32.py passlib/tests/utils.py passlib/utils/__init__.py passlib/utils/des.py passlib/utils/h64.py passlib/utils/handlers.py passlib/utils/md4.py passlib/utils/pbkdf2.pypasslib-1.5.3/passlib.egg-info/zip-safe0000644000175000017500000000000111643713605021070 0ustar biscuitbiscuit00000000000000 passlib-1.5.3/passlib.egg-info/top_level.txt0000644000175000017500000000001011643754211022157 0ustar biscuitbiscuit00000000000000passlib passlib-1.5.3/passlib.egg-info/dependency_links.txt0000644000175000017500000000000111643754211023504 0ustar biscuitbiscuit00000000000000 passlib-1.5.3/passlib.egg-info/PKG-INFO0000644000175000017500000000375411643754211020544 0ustar biscuitbiscuit00000000000000Metadata-Version: 1.0 Name: passlib Version: 1.5.3 Summary: comprehensive password hashing framework supporting over 20 schemes Home-page: http://passlib.googlecode.com Author: Eli Collins Author-email: elic@assurancetechnologies.com License: BSD Download-URL: http://passlib.googlecode.com/files/passlib-1.5.3.tar.gz Description: Passlib is a password hashing library for Python 2 & 3, which provides cross-platform implementations of over 20 password hashing algorithms, as well as a framework for managing existing password hashes. It's designed to be useful for a wide range of tasks, from verifying a hash found in /etc/shadow, to providing full-strength password hashing for multi-user application. * See the `online documentation `_ for details, installation instructions, and examples. * See the `passlib homepage `_ for the latest news, more information, and additional downloads. * See the `changelog `_ for description of what's new in Passlib. All releases are signed with the gpg key `4CE1ED31 `_. Keywords: password secret hash security crypt md5-crypt sha256-crypt sha512-crypt bcrypt apache htpasswd htdigest pbkdf2 Platform: UNKNOWN Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2.5 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Security :: Cryptography Classifier: Topic :: Software Development :: Libraries Classifier: Development Status :: 5 - Production/Stable passlib-1.5.3/docs/0000755000175000017500000000000011643754212015240 5ustar biscuitbiscuit00000000000000passlib-1.5.3/docs/contents.rst0000644000175000017500000000076311643466373017645 0ustar biscuitbiscuit00000000000000================= Table Of Contents ================= .. toctree:: Front Page install new_app_quickstart overview password_hash_api lib/passlib.hash lib/passlib.context lib/passlib.apps lib/passlib.apache lib/passlib.hosts lib/passlib.registry lib/passlib.utils modular_crypt_format history copyright * :ref:`General Index ` * :ref:`Module List ` .. unlisted: lib/passlib.ext.django passlib-1.5.3/docs/conf.py0000644000175000017500000002054611643754032016546 0ustar biscuitbiscuit00000000000000# -*- coding: utf-8 -*- # # PassLib documentation build configuration file, created by # sphinx-quickstart on Mon Mar 2 14:12:06 2009. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os options = os.environ.get("PASSLIB_DOCS", "") #make sure passlib in sys.path doc_root = os.path.abspath(os.path.join(__file__,os.path.pardir)) source_root = os.path.abspath(os.path.join(doc_root,os.path.pardir)) sys.path.insert(0, source_root) # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) #building the docs requires the Cloud sphinx theme & extensions # https://bitbucket.org/ecollins/cloud_sptheme #which contains some sphinx extensions used by passlib import cloud_sptheme #hack to make autodoc generate documentation from the correct class... from passlib.utils import md4 md4.md4 = md4._builtin_md4 # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.todo', 'cloud_sptheme.ext.autodoc_sections', #add autdoc support for ReST sections in class/function docstrings 'cloud_sptheme.ext.index_styling', #adds extra ids & classes to genindex html, for additional styling 'cloud_sptheme.ext.relbar_toc', #inserts toc into right hand nav bar (ala old style python docs) ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. source_encoding = 'utf-8' # The master toctree document. master_doc = 'contents' index_doc = 'index' # General information about the project. project = u'PassLib' copyright = u'2008-2011, Assurance Technologies, LLC' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # version: The short X.Y version. # release: The full version, including alpha/beta/rc tags. from passlib import __version__ as release version = cloud_sptheme.get_version(release) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [ #disabling documentation of this until module is more mature. "lib/passlib.ext.django.rst" ] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. modindex_common_prefix = [ "passlib." ] # -- Options for all output --------------------------------------------------- todo_include_todos = "hide-todos" not in options keep_warnings = "hide-warnings" not in options # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'cloud' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. if html_theme == 'cloud': html_theme_options = { "roottarget": index_doc } else: html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. html_theme_path = [cloud_sptheme.get_theme_dir()] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". html_title = project + " v" + release + " Documentation" # A shorter title for the navigation bar. Default is the same as html_title. html_short_title = project + " Documentation" # The name of an image file (relative to this directory) to place at the top # of the sidebar. html_logo = os.path.join("_static", "masthead.png") # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. html_favicon = "logo.ico" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = project + 'Doc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ (index_doc, project + '.tex', project + u' Documentation', u'Assurance Technologies, LLC', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (index_doc, project, project + u' Documentation', [u'Assurance Technologies, LLC'], 1) ] passlib-1.5.3/docs/history.rst0000644000175000017500000000003011643466373017474 0ustar biscuitbiscuit00000000000000.. include:: ../CHANGES passlib-1.5.3/docs/overview.rst0000644000175000017500000000766311643466373017664 0ustar biscuitbiscuit00000000000000================ Library Overview ================ Passlib is a collection of routines for managing password hashes such as found in unix "shadow" files, as returned by stdlib's :func:`!crypt`, as stored in mysql and postgres, and various other places. Passlib's contents can be roughly grouped into three categories: password hashes, password contexts, and utility functions. .. note:: New applications which just need drop-in password hashing support should see the :doc:`new_app_quickstart`. Password Hashes =============== All of the hash schemes supported by Passlib are implemented as classes importable from the :mod:`passlib.hash` module. All of these classes support a single uniform interface of standard class methods. These methods are documented in detail by the :ref:`password hash api `. As a quick example of how a password hash can be used directly:: >>> #import the SHA512-Crypt class: >>> from passlib.hash import sha512_crypt as sc >>> #generate new salt, encrypt password: >>> h = sc.encrypt("password") >>> h '$6$rounds=40000$xCsOXRqPPk5AGDFu$o5eyqxEoOSq0dLRFbPxEHp5Jc1vFVj47BNT.h9gmjSHXDS15mjIM.GSUaT5r6Z.Xa1Akrv4FAgKJE3EfbkJxs1' >>> #same, but with explict number of rounds: >>> sc.encrypt("password", rounds=10000) '$6$rounds=10000$QWT8AlDMYRms7vSx$.1267Pg6Opn9CblFndtBJ2Q0AI0fcI2IX93zX3gi1Qse./j.VlKYX59NIUlbs0A66wCbfu/vra9wMv2uwTZAI.' >>> #check if string is recognized as belonging to this hash scheme: >>> sc.identify(h) True >>> #check if some other hash is recognized: >>> sc.identify('$1$3azHgidD$SrJPt7B.9rekpmwJwtON31') False >>> #verify correct password: >>> sc.verify("password", h) True >>> #verify incorrect password: >>> sc.verify("secret", h) False Password Contexts ================= Mature applications frequently have to deal with tables of existing password hashes. Over time, they have to migrate to newer and stronger schemes; as well as raise the requirements for existing algorithms as more processing power becomes available. In this case, directly importing and handling the various schemes generally becomes complicated and tedious. The :mod:`passlib.context` module provides the :class:`!CryptContext` class and other utilties to help with these use-cases. This class handles managing multiple password hash schemes, deprecation & migration of old hashes, and many other policy requirements. A quick example of how a password context can be used:: >>> #importing the 'linux_context', which understands >>> #all hashes found on standard linux systems: >>> from passlib.hosts import linux_context as lc >>> #try encrypting a password >>> lc.encrypt("password") '$6$rounds=30000$suoPoYtkbccdZa3v$DW2KUcV98H4IrvlBB0YZf4DM8zqz5vduygB3OROhPzwHE5PDNVkpSUjJfjswn/dXqidha5t5CSCCIhtm6mIDR1' >>> #try encrypting a password using a specified scheme >>> lc.encrypt("password", scheme="des_crypt") 'q1Oyx5r9mdGZ2' >>> #try verifying a password (scheme is autodetected) >>> lc.verify('password', 'q1Oyx5r9mdGZ2') True Predefined Password Contexts ============================ In addition to the :mod:`!passlib.context` module, PassLib provides a number of pre-configured :class:`!CryptContext` instances in order to get users started quickly: * The :mod:`passlib.apache` module contains classes for managing htpasswd and htdigest files. * The :mod:`passlib.apps` module contains pre-configured instances for managing hashes used by Postgres, Mysql, and LDAP, and others. * The :mod:`passlib.hosts` module contains pre-configured instances for managing hashes as found in the /etc/shadow files on Linux and BSD systems. Utility Functions ================= The :mod:`passlib.registry` and :mod:`passlib.utils` modules contain a large number of support functions, most of which are only needed when are implementing custom password hash schemes. Most users of passlib will not need to use these. passlib-1.5.3/docs/notes.txt0000644000175000017500000000514611643466373017147 0ustar biscuitbiscuit00000000000000==== Todo ==== Internal Changes ---------------- * C extensions to speed up some implementations * py3k support Other Hash Formats ------------------ * Mac OSX hash formats * SCrypt http://www.tarsnap.com/scrypt.html https://bitbucket.org/mhallin/py-scrypt/src Notes on various hash formats ============================= Cisco PIX --------- sample hashes found - http://www.freerainbowtables.com/phpBB3/viewtopic.php?f=2&t=1441 8Ry2YjIyt7RRXU24 '' 2KFQnbNIdI.2KYOU 'cisco' hN7LzeyYjw12FSIU 'john'/'cisco' 7DrfeZ7cyOj/PslD 'jack'/'cisco' alg secret+user truncate/pad-right-null to 16 bytes md5().digest() h64 encode todo: get some samples w/ passwords longer than 16 chars to verify Mac OSX ------- Summary of info from http://www.dribin.org/dave/blog/archives/2006/04/28/os_x_passwords_2/ osx < 10.2 used /etc/passwd w/ DES-CRYPT osx 10.3 hash file (passwd "macintosh") D47F3AF827A48F7DFA4F2C1F12D68CD6 <-- nthash 08460EB13C5CA0C4CA9516712F7FED95 <-- lmhash 01424f955c11f92efef0b79d7fa3fb6be56a9f99 <-- sha1 osx 10.4 hash file (passwd "macintosh") 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000\ 00000000000000000000000000000000000000000000000000000000000000000000000000000000\ 000000000E6A48F765D0FFFFF6247FA80D748E615F91DD0C7431E4D9000000000000000000000000\ 00000000000000000000000000000000000000000000000000000000000000000000000000000000\ 00000000000000000000000000000000000000000000000000000000000000000000000000000000\ 00000000000000000000000000000000000000000000000000000000000000000000000000000000\ 00000000000000000000000000000000000000000000000000000000000000000000000000000000\ 00000000000000000000000000000000000000000000000000000000000000000000000000000000\ 00000000000000000000000000000000000000000000000000000000000000000000000000000000\ 00000000000000000000000000000000000000000000000000000000000000000000000000000000\ 00000000000000000000000000000000000000000000000000000000000000000000000000000000\ 00000000000000000000000000000000000000000000000000000000000000000000000000000000\ 00000000000000000000000000000000000000000000000000000000000000000000000000000000\ 00000000000000000000000000000000000000000000000000000000000000000000000000000000\ 00000000000000000000000000000000000000000000000000000000000000000000000000000000\ 0000000000000000000000000000000000000000 offset 0-64 - nt hash + lm hash OR all zeros offset 64 - 40 chars - raw sha1 password OR all zeroes (if from upgraded from 10.3) offset 169-216 ( 48 chars) - salted sha1 hash - unhex first 8 chars + password | sha1 -> hexdigest passlib-1.5.3/docs/new_app_quickstart.rst0000644000175000017500000002136711643466373021716 0ustar biscuitbiscuit00000000000000================================ New Application Quickstart Guide ================================ Need to quickly get password hash support added into your new application, and don't have time to wade through pages of documentation, comparing and constrasting all the different schemes? Read on... Really Quick Start ================== The fastest route is to use the preconfigured :data:`~passlib.apps.custom_app_context` object. It supports the :class:`~passlib.hash.sha256_crypt` and :class:`~passlib.hash.sha512_crypt` schemes, and defaults to 40000 hash iterations for increased strength. For applications which want to quickly add password hashing, all they need to do is the following:: >>> #import the context under an app-specific name (so it can easily be replaced later) >>> from passlib.apps import custom_app_context as pwd_context >>> #encrypting a password... >>> hash = pwd_context.encrypt("somepass") >>> #verifying a password... >>> ok = pwd_context.verify("somepass", hash) >>> #[optional] encrypting a password for an admin account... >>> # the custom_app_context is preconfigured so that >>> # if the category is set to "admin" instead of None, >>> # it uses a stronger setting of 80000 rounds: >>> hash = pwd_context.encrypt("somepass", category="admin") For applications which started using this preset, but whose needs have grown beyond it, it is recommended to create your own :mod:`CryptContext ` instance; see below for more... .. _recommended-hashes: Choosing a Hash ================ *If you already know what hash algorithm(s) you want to use, skip to the next section,* `Creating a CryptContext`_. If you'd like to set up a configuration that's right for your application, the first thing to do is choose a password hashing scheme. Passlib contains a large number of schemes, but most of them should only be used when a specific format is explicitly required. For new applications, there are really only three choices [#choices]_: * :class:`~passlib.hash.bcrypt` * :class:`~passlib.hash.sha512_crypt` * :class:`~passlib.hash.pbkdf2_sha512` All three password hashes share the following properties: * no known vulnerabilties. * based on documented & widely reviewed algorithms. * basic algorithm has seen heavy scrutiny and use for at least 10 years. * public-domain or BSD-licensed reference implementations available. * in use across a number of OSes and/or a wide variety of applications. * variable rounds for configuring flexible cpu cost on a per-hash basis. * at least 96 bits of salt. The following comparison should help you choose which hash is most appropriate for your application; if in doubt, any of these is a good choice, though PBKDF2 is probably the best for portability. .. rst-class:: html-toggle Detailed Comparison of Choices ------------------------------ BCrypt ...... :class:`~passlib.hash.bcrypt` is `based `_ on the well-tested Blowfish cipher. In use since 1999, it's the default hash on all BSD variants. If you want your application's hashes to be readable by the native BSD crypt() function, this is the hash to use. There is also an alternative LDAP-formatted version (:class:`~passlib.hash.ldap_bcrypt`) available. Issues: Neither the original Blowfish, nor the modified version which BCrypt uses, have been NIST approved; this matter of concern is what motivated the development of SHA512-Crypt. As well, it's rounds parameter is logarithmically scaled, making it hard to fine-tune the amount of time taken to verify passwords; which can be an issue for applications that handle a large number of simultaneous logon attempts (eg web apps). .. note:: For BCrypt support on non-BSD systems, Passlib requires a C-extension module provided by the external :ref:`PyBcrypt or BCryptor ` packages. Neither of these currently supports Python 3. SHA512-Crypt ............ :class:`~passlib.hash.sha512_crypt` is based on well-tested :class:`~passlib.hash.md5_crypt` algorithm. In use since 2008, it's the default hash on most Linux systems; its direct ancestor :class:`!md5_crypt` has been in use since 1994 on most Unix systems. If you want your application's hashes to be readable by the native Linux crypt() function, this is the hash to use. There is also :class:`~passlib.hash.sha256_crypt`, which may be faster on 32 bit processors; as well as LDAP-formatted versions of these ( :class:`~passlib.hash.ldap_sha512_crypt` and :class:`~passlib.hash.ldap_sha256_crypt`). Issues: Like :class:`~passlib.hash.md5_crypt`, it's algorithm composes the underlying message digest hash in a baroque and somewhat arbitrary set combinations. So far this "kitchen sink" design has been successful in it's primary purpose: to prevent any attempts to create an optimized version for use in a pre-computed or brute-force search. However, this design also hampers analysis of the algorithm for future flaws. This algorithm is probably the best choice for Google App Engine, as Google's production servers appear to provide native support via :mod:`crypt`, which will be used by Passlib. .. note:: References to this algorithm are frequently confused with a raw SHA-512 hash; while it uses SHA-512 as a cryptographic primitive, this algorithm's resulting password hash is far more secure. PBKDF2 ...... :class:`~passlib.hash.pbkdf2_sha512` is a custom has format designed for Passlib. However, it directly uses the `PBKDF2 `_ key derivation function, which was standardized in 2000, and found across a `wide variety `_ of applications and platforms. Unlike the previous two hashes, PBKDF2 has a simple and portable design, which is resistant (but not immune) to collision and preimage attacks on the underlying message digest. There is also :class:`~passlib.hash.pbkdf2_sha256`, which may be faster on 32 bit processors; as well as LDAP-formatted versions of these ( :class:`~passlib.hash.ldap_pbkdf2_sha512` and :class:`~passlib.hash.ldap_pbkdf2_sha256`). Issues: PBKDF2 has no security or portability issues. However, it's only come into wide use as a password hash in recent years; mainly hampered by the fact that there is no standard format for encoding password hashes using this algorithm (which is why Passlib has it's own :ref:`custom format `). .. note:: Passlib strongly suggests installing the external M2Crypto package to speed up PBKDF2 calculations, though this is not required. Creating a CryptContext ======================= One you've chosen what password hash(es) you want to use, the next step is to define a :class:`~passlib.context.CryptContext` object to manage your hashes, and relating configuration information. Insert the following code into your application:: # #import the CryptContext class, used to handle all hashing... # from passlib.context import CryptContext # #create a single global instance for your app... # pwd_context = CryptContext( #replace this list with the hash(es) you wish to support. #this example sets pbkdf2_sha256 as the default, #with support for legacy des_crypt hashes. schemes=["pbkdf2_sha256", "des_crypt" ], default="pbkdf2_sha256", #vary rounds parameter randomly when creating new hashes... all__vary_rounds = "10%", #set the number of rounds that should be used... #(appropriate values may vary for different schemes, # and the amount of time you wish it to take) pbkdf2_sha256__default_rounds = 8000, ) Using a CryptContext ==================== To start using your CryptContext, import the context you created in the previous section wherever needed:: >>> #import context from where you defined it... >>> from myapp.model.security import pwd_context >>> #encrypting a password... >>> hash = pwd_context.encrypt("somepass") >>> hash '$pbkdf2-sha256$7252$qKFNyMYTmgQDCFDS.jRJDQ$sms3/EWbs4/3k3aOoid5azwq3HPZKVpUUrAsCfjrN6M' >>> #verifying a password... >>> pwd_context.verify("somepass", hash) True >>> pwd_context.verify("wrongpass", hash) False .. seealso:: * :mod:`passlib.hash` - list of all hashes supported by passlib. * :mod:`passlib.context` - for more details about the CryptContext class. .. rubric:: Footnotes .. [#choices] BCrypt, SHA-512 Crypt, and PBKDF2 are the most commonly used password hashes as of May 2011, when this document was written. You should make sure you are reading a current copy of the passlib documentation, in case the state of things has changed. passlib-1.5.3/docs/modular_crypt_format.rst0000644000175000017500000001736311643466373022250 0ustar biscuitbiscuit00000000000000.. index:: modular crypt format .. _modular-crypt-format: .. rst-class:: html-toggle ==================== Modular Crypt Format ==================== .. centered:: *or*, a side note about a standard that isn't In short, the modular crypt format (MCF) is a standard for encoding password hash strings, which requires hashes have the format :samp:`${identifier}${content}`; where :samp:`{identifier}` is an short alphanumeric string uniquely identifying a particular scheme, and :samp:`{content}` is the contents of the scheme, using only the characters in the regexp range ``[a-zA-Z0-9./]``. However, there appears to be no central registry of identifiers, no specification document, and no actual rules; so the modular crypt format is more of an ad-hoc idea rather than a true standard. History ======= Historically, most unix systems supported only :class:`~passlib.hash.des_crypt`. Around the same time, many incompatible variations were also developed, but their hashes were not easily distingiushable from each other (see :ref:`archaic-unix-schemes`); making it impossible to use multiple hashes on one system, or progressively migrate to a newer scheme. This was solved with the advent of the MCF, which was introduced around the time that :class:`~passlib.hash.md5_crypt` was developed. This format allows hashes from multiple schemes to exist within the same database, by requiring that all hash strings begin with a unique prefix using the format :samp:`${identifier}$`. Requirements ============ Unfortunately, there is no specification document for this format. Instead, it exists in *de facto* form only; the following is an attempt to roughly identify the guidelines followed by the modular crypt format hashes found in passlib: 1. Hash strings must use only 7-bit ascii characters. No known OS or application generates hashes which violate this rule. However, some systems (eg Linux's shadow routines) will happily and correctly accept hashes which contain 8-bit characters in their salt. This is probably a case of "permissive in what you accept, strict in what you generate". 2. Hash strings should always start with the prefix :samp:`${identifier}$`, where :samp:`{identifier}` is a short string uniquely identifying hashes generated by that algorithm, using only lower case ascii letters, numbers, and hyphens. Initially, most schemes adhereing to this format only used a single digit to identify the hash (eg ``$1$`` for :class:`!md5_crypt`). Because of this, many systems only look at the first character when attempting to distinguish hashes. Despite this, as Unix systems have branched off, new hashes have been developed which used larger identifying strings (eg ``$sha1$`` for :class:`~passlib.hash.sha1_crypt`); so in general identifier strings should not be assumed to use a single character. 3. Hashes should contain only ascii letters ``a``-``z`` and ``A``-``Z``, ascii numbers 0-9, and the characters ``./``; though additionally they should use the ``$`` character as an internal field separator. This is the least adhered-to of any modular crypt format rule. Other characters (such as ``=,-``) are sometimes used by various formats, though sparingly. The only hard and fast stricture is that ``:;!*`` and non-printable characters be avoided, since this would interfere with parsing of /etc/shadow where these hashes are typically stored. Pretty much all modular-crypt-format hashes use ascii letters, numbers, ``.``, and ``/`` to provide base64 encoding of their raw data, though the exact character value assignments vary between hashes (see :mod:`passlib.utils.h64`). 4. Hash schemes should put their "checksum" portion at the end of the hash, preferrably separated by a ``$``. This allows password hashes to be easily truncated to a "configuration string" containing just the identifying prefix, rounds, salt, etc. This configuration string then encodes all the information generated needed to generate a new hash in order to verify a password, without having to perform excessive parsing. Most modular crypt format hashes follow this, though some (like :class:`~passlib.hash.bcrypt`) omit the ``$`` separator. As well, there is no set standard about whether configuration strings should or should not include a trailing ``$`` at the end, though the general rule is that a hash behave the same regardless (:class:`~passlib.hash.sun_md5_crypt` behaves particularly poorly regarding this last point). .. note:: All of the above is guesswork based on examination of existing hashes and OS implementations; and was written merely to clarify the issue of what the "modular crypt format" is. It is drawn from no authoritative sources. .. index:: modular crypt format; known identifiers .. _mcf-identifiers: Identifiers & Platform Support ============================== The following table lists of all the major MCF hashes supported by Passlib, and indicates which operating systems [#gae]_ offer native support. .. todo:: include MacOS X in this list ==================================== ==================== =========== =========== =========== =========== ======= Scheme Prefix Linux FreeBSD NetBSD OpenBSD Solaris ==================================== ==================== =========== =========== =========== =========== ======= :class:`~passlib.hash.des_crypt` n/a y y y y y :class:`~passlib.hash.bsdi_crypt` ``_`` y y :class:`~passlib.hash.md5_crypt` ``$1$`` y y y y y :class:`~passlib.hash.sun_md5_crypt` ``$md5$``, ``$md5,`` y :class:`~passlib.hash.bcrypt` ``$2$``, ``$2a$`` y y y y :class:`~passlib.hash.nthash` ``$3$`` y :class:`~passlib.hash.sha256_crypt` ``$5$`` y y :class:`~passlib.hash.sha512_crypt` ``$6$`` y y :class:`~passlib.hash.sha1_crypt` ``$sha1$`` y ==================================== ==================== =========== =========== =========== =========== ======= The following table lists the other MCF hashes supported by Passlib, most of which are only used by applications: =========================================== =================== =========================== Scheme Prefix Primary Use (if known) =========================================== =================== =========================== :class:`~passlib.hash.apr_md5_crypt` ``$apr1$`` Apache htdigest files :class:`~passlib.hash.phpass` ``$P$``, ``$H$`` PHPass-based applications :class:`~passlib.hash.pbkdf2_sha1` ``$pbkdf2$`` :class:`~passlib.hash.pbkdf2_sha256` ``$pbkdf2-sha256$`` :class:`~passlib.hash.pbkdf2_sha512` ``$pbkdf2-sha512$`` :class:`~passlib.hash.cta_pbkdf2_sha1` ``$p5k2$`` [#cta]_ :class:`~passlib.hash.dlitz_pbkdf2_sha1` ``$p5k2$`` [#cta]_ =========================================== =================== =========================== .. rubric:: Footnotes .. [#gae] As of 2011-08-19, Google App Engine's :mod:`crypt` implementation appears to provide hash support matching that of a typical Linux system. .. [#cta] :class:`!cta_pbkdf2_sha1` and :class:`!dlitz_pbkdf2_sha1` both use the same identifier. They can be distinguished by the fact that cta hashes will always end in ``=``, while dlitz hashes contain no ``=`` at all. passlib-1.5.3/docs/password_hash_api.rst0000644000175000017500000005760411643466373021514 0ustar biscuitbiscuit00000000000000.. index:: single: password hash api single: custom hash handler; requirements .. currentmodule:: passlib.hash .. _password-hash-api: ================= Password Hash API ================= Overview ======== All of the hashes supported by PassLib are implemented using classes [#otypes]_ which support an identical interface; this document describes that interface in terms of a non-existent abstract class called :class:`!PasswordHash`. All of the supported password hashes [#supported]_ provide the following methods and attributes: :ref:`required-attributes` These consist of the attributes :attr:`~PasswordHash.name`, :attr:`~PasswordHash.setting_kwds`, and :attr:`~PasswordHash.context_kwds`. They permit users and applications to detect what features a specific :class:`!PasswordHash` allows and/or requires. :ref:`application-methods` This interface consists of the :meth:`~PasswordHash.encrypt`, :meth:`~PasswordHash.identify`, and :meth:`~PasswordHash.verify` classmethods. These are the methods most applications will need to make use of. :ref:`crypt-methods` This interface consists of the :meth:`~PasswordHash.genconfig` and :meth:`~PasswordHash.genhash` classmethods. These methods mimic the standard unix crypt interface, and are not usually needed by applications. :ref:`optional-attributes` These attributes provide additional information about the capabilities and limitations of certain password hash schemes. Usage ===== While most uses of PassLib are done through a :class:`~passlib.context.CryptContext` class, the various :class:`!PasswordHash` classes can be used directly to manipulate passwords:: >>> # for example, the SHA256-Crypt class: >>> from passlib.hash import sha256_crypt as sc >>> # using it to encrypt a password: >>> h = sc.encrypt("password") >>> h '$5$rounds=40000$HIo6SCnVL9zqF8TK$y2sUnu13gp4cv0YgLQMW56PfQjWaTyiHjVbXTgleYG9' >>> # subsequent calls to sc.encrypt() will generate a new salt: >>> sc.encrypt("password") '$5$rounds=40000$1JfxoiYM5Pxokyh8$ez8uV8jjXW7SjpaTg2vHJmx3Qn36uyZpjhyC9AfBi7B' >>> # the same, but with an explict number of rounds: >>> sc.encrypt("password", rounds=10000) '$5$rounds=10000$UkvoKJb8BPrLnR.D$OrUnOdr.IJx74hmyyzuRdr5k9lSXdkFxKmr7bLQTty5' >>> #the identify method can be used to determine the format of an unknown hash: >>> sc.identify(h) True >>> #check if some other hash is recognized (in this case, an MD5-Crypt hash) >>> sc.identify('$1$3azHgidD$SrJPt7B.9rekpmwJwtON31') False >>> #the verify method encapsulates all hash comparison logic for a class: >>> sc.verify("password", h) True >>> sc.verify("wrongpassword", h) False .. _required-attributes: Required Attributes ================================= .. attribute:: PasswordHash.name A unique name used to identify the particular scheme this class implements. These names should consist only of lowercase a-z, the digits 0-9, and underscores. .. note:: All handlers built into passlib are implemented as classes located under :samp:`passlib.hash.{name}`, where :samp:`{name}` is both the class name, and the value of the ``name`` attribute. This is not a requirement, and may not be true for externally-defined handlers. .. attribute:: PasswordHash.setting_kwds If the scheme supports per-hash configuration (such as salts, variable rounds, etc), this attribute should contain a tuple of keywords corresponding to each of those configuration options. This should list all the main configuration keywords accepted by :meth:`~PasswordHash.genconfig` and :meth:`~PasswordHash.encrypt`. If no configuration options are supported, this attribute should be an empty tuple. While each class may support a variety of options, each with their own meaning and semantics, the following keywords should have the same behavior across all schemes which use them: ``salt`` If present, this means the algorithm contains some number of bits of salt which should vary with every new hash created. Additionally, this means :meth:`~PasswordHash.genconfig` and :meth:`~PasswordHash.encrypt` should both accept an optional ``salt`` keyword allowing the user to specify a bare salt string. Note that this feature is rarely needed, and the constraints on the size & content of this string will vary for each algorithm. ``salt_size`` Most algorithms which support ``salt`` will auto-generate a salt string if none is provided. If this keyword is also present, it means it can be used to select the size of the auto-generated salt. If omitted, most algorithms will fall back to a default salt size. ``rounds`` If present, this means the algorithm allows for a variable number of rounds to be used, allowing the processor time required to be increased. Providing this as a keyword should allow the application to override the class' default number of rounds. While this must be a non-negative integer for all implementations, additional constraints may be present for each algorith (such as the cost varying on a linear or logarithmic scale). ``ident`` If present, the class supports multiple formats for encoding the same hash. The class's documentation will generally list the allowed values, allowing alternate output formats to be selected. .. attribute:: PasswordHash.context_kwds This attribute should contain a tuple of keywords which should be passed into :func:`encrypt`, :func:`verify`, and :func:`genhash` in order to encrypt a password. Some algorithms require external contextual information in order to generate a checksum for a password. An example of this is :doc:`Postgres' MD5 algorithm `, which requires the username be provided when generating a hash (see that class for an example of how this works in pratice). Since most password hashes require no external information, this tuple will usually be empty, and references to context keywords can be ignored for all but a few classes. While each class may support a variety of options, each with their own meaning and semantics, the following keywords should have the same behavior across all schemes which use them: ``user`` If present, the class requires a username be specified whenever performing a hash calculation (eg: postgres_md5 and oracle10). .. _application-methods: Application Methods =================== The :meth:`~PasswordHash.encrypt`, :meth:`~PasswordHash.identify`, and :meth:`~PasswordHash.verify` methods are designed to provide an easy interface for applications. They allow encrypt new passwords without having to deal with details such as salt generation, verifying passwords without having to deal with hash comparison rules, and determining which scheme a hash belongs to when multiple schemes are in use. .. classmethod:: PasswordHash.encrypt(secret, \*\*settings_and_context_kwds) encrypt secret, returning resulting hash string. :arg secret: A string containing the secret to encode. Unicode behavior is specified on a per-hash basis, but the common case is to encode into utf-8 before processing. :param \*\*settings_and_context_kwds: All other keywords are algorithm-specified, and should be listed in :attr:`~PasswordHash.setting_kwds` and :attr:`~PasswordHash.context_kwds`. Common settings keywords include ``salt`` and ``rounds``. :raises ValueError: * if settings are invalid and handler cannot correct them. (eg: if a ``salt`` string is to short, this will cause an error; but a ``rounds`` value that's too large should be silently clipped). * if a context keyword contains an invalid value, or was required but omitted. * if secret contains forbidden characters (e.g: des-crypt forbids null characters). this should rarely occur, since most modern algorithms have no limitations on the types of characters. :raises TypeError: if :samp:`{secret}` is not a bytes or unicode instance. :returns: Hash string, using an algorithm-specific format. .. classmethod:: PasswordHash.identify(hash) identify if a hash string belongs to this algorithm. :arg hash: the candidate hash string to check :returns: * ``True`` if input appears to be a hash string belonging to this algorithm. * ``True`` if input appears to be a configuration string belonging to this algorithm. * ``False`` if no input is an empty string or ``None``. * ``False`` if none of the above conditions was met. .. note:: The goal of this method is positively identify the correct handler for a given hash, and do it as efficiently as possible. In order to accomplish this, many implementations perform only minimal validation of the candidate hashes. Thus, they may return ``True`` for hashes which are identifiable, but malformed enough that a :exc:`ValueError` is raised when the string is passed to :func:`~PasswordHash.verify` or :func:`~PasswordHash.genhash`. Because of this, applications should rely on this method only for identification, not confirmation that a hash is correctly formed. .. classmethod:: PasswordHash.verify(secret, hash, \*\*context_kwds) verify a secret against an existing hash. This checks if a secret matches against the one stored inside the specified hash. :param secret: A string containing the secret to check. :param hash: A string containing the hash to check against. :param \*\*context_kwds: Any additional keywords will be passed to the encrypt method. These should be limited to those listed in :attr:`~PasswordHash.context_kwds`. :raises TypeError: if :samp:`{secret}` is not a bytes or unicode instance. :raises ValueError: * if the hash not specified * if the hash does not match this algorithm's hash format * if the provided secret contains forbidden characters (see :meth:`~PasswordHash.encrypt`) :returns: ``True`` if the secret matches, otherwise ``False``. .. _crypt-methods: Crypt Methods ============= While the application methods are generally the most useful when integrating password support into an application, those methods are for the most part built on top of the crypt interface, which is somewhat simpler for *implementing* new password schemes. It also happens to match more closely with the crypt api of most Unix systems, and consists of two functions: :meth:`~PasswordHash.genconfig` and :meth:`~PasswordHash.genhash`. .. classmethod:: PasswordHash.genconfig(\*\*settings_kwds) returns configuration string encoding settings for hash generation Many hashes have configuration options, and support a format which encodes them into a single configuration string. (This configuration string is usually an abbreviated version of their encoded hash format, sans the actual checksum, and is commonly referred to as a ``salt string``, though it may contain much more than just a salt). This function takes in optional configuration options (a complete list of which should be found in :attr:`~PasswordHash.setting_kwds`), validates the inputs, fills in defaults where appropriate, and returns a configuration string. For algorithms which do not have any configuration options, this function should always return ``None``. While each algorithm may have it's own configuration options, the following keywords (if supported) should always have a consistent meaning: * ``salt`` - algorithm uses a salt. if passed into genconfig, should contain an encoded salt string of length and character set required by the specific handler. salt strings which are too small or have invalid characters should cause an error, salt strings which are too large should be truncated but accepted. * ``rounds`` - algorithm uses a variable number of rounds. if passed into genconfig, should contain an integer number of rounds (this may represent logarithmic rounds, eg bcrypt, or linear, eg sha-crypt). if the number of rounds is too small or too large, it should be clipped but accepted. :param \*\*settings_kwds: this function takes in keywords as specified in :attr:`~PasswordHash.setting_kwds`. commonly supported keywords include ``salt`` and ``rounds``. :raises ValueError: * if any configuration options are required, missing, AND a default value cannot be autogenerated. (for example: salt strings should be autogenerated if not specified). * if any configuration options are invalid, and cannot be normalized in a reasonble manner (eg: salt strings clipped to maximum size). :returns: the configuration string, or ``None`` if the algorithm does not support any configuration options. .. classmethod:: PasswordHash.genhash(secret, config, \*\*context_kwds) encrypt secret to hash takes in a password, optional configuration string, and any required contextual information the algorithm needs, and returns the encoded hash strings. :arg secret: string containing the password to be encrypted :arg config: configuration string to use when encrypting secret. this can either be an existing hash that was previously returned by :meth:`~PasswordHash.genhash`, or a configuration string that was previously created by :meth:`~PasswordHash.genconfig`. :param \*\*context_kwds: All other keywords must be external contextual information required by the algorithm to create the hash. If any, these kwds must be specified in :attr:`~PasswordHash.context_kwds`. :raises TypeError: * if the configuration string is not provided * if required contextual information is not provided * if :samp:`{secret}` is not a bytes or unicode instance. :raises ValueError: * if the configuration string is not in a recognized format. * if the secret contains a forbidden character (rare, but some algorithms have limitations, eg: forbidding null characters) * if the contextual information is invalid :returns: encoded hash matching specified secret, config, and context. .. _optional-attributes: Optional Attributes ================================= Many of the handlers expose the following informational attributes (though their presence is not uniform or required as of this version of Passlib). .. todo:: Consider making these attributes required for all hashes which support the appropriate keyword in :attr:`~PasswordHash.setting_kwds`. .. _optional-rounds-attributes: Rounds Information ------------------ For schemes which support a variable number of rounds (ie, ``'rounds' in PasswordHash.setting_kwds``), the following attributes are usually exposed. (Applications can test for this suites' presence by using :func:`~passlib.utils.has_rounds_info`) .. attribute:: PasswordHash.max_rounds The maximum number of rounds the scheme allows. Specifying values above this will generally result in a warning, and :attr:`~!PasswordHash.max_rounds` will be used instead. Must be a positive integer. .. attribute:: PasswordHash.min_rounds The minimum number of rounds the scheme allows. Specifying values below this will generally result in a warning, and :attr:`~!PasswordHash.min_rounds` will be used instead. Must be within ``range(0, max_rounds+1)``. .. attribute:: PasswordHash.default_rounds The default number of rounds that will be used if not explicitly set when calling :meth:`~PasswordHash.encrypt` or :meth:`~PasswordHash.genconfig`. Must be within ``range(min_rounds, max_rounds+1)``. .. attribute:: PasswordHash.rounds_cost Specifies how the rounds value affects the amount of time taken. Currently used values are: ``linear`` time taken scales linearly with rounds value (eg: :class:`~passlib.hash.sha512_crypt`) ``log2`` time taken scales exponentially with rounds value (eg: :class:`~passlib.hash.bcrypt`) .. _optional-salt-attributes: Salt Information ---------------- For schemes which support a salt (ie, ``'salt' in PasswordHash.setting_kwds``), the following attributes are usually exposed. (Applications can test for this suites' presence by using :func:`~passlib.utils.has_salt_info`) .. attribute:: PasswordHash.max_salt_size maximum number of characters which will be used if a salt string is provided to :meth:`~PasswordHash.genconfig` or :meth:`~PasswordHash.encrypt`. must be one of: * A positive integer - it should accept and silently truncate any salt strings longer than this size. * ``None`` - the scheme should use all characters of a provided salt, no matter how large. .. attribute:: PasswordHash.min_salt_size minimum number of characters required for any salt string provided to :meth:`~PasswordHash.genconfig` or :meth:`~PasswordHash.encrypt`. must be an integer within ``range(0,max_salt_size+1)``. .. attribute:: PasswordHash.default_salt_size size of salts generated by genconfig when no salt is provided by caller. for most hashes, this defaults to :attr:`~PasswordHash.max_salt_size`. this value must be within ``range(min_salt_size, max_salt_size+1)``. .. attribute:: PasswordHash.salt_chars string containing list of all characters which are allowed to be specified in salt parameter. for most :ref:`MCF ` hashes, this is equal to :data:`passlib.utils.h64.CHARS`. this must be a :class:`!unicode` string if the salt is encoded, or (rarely) :class:`!bytes` if the salt is manipulating as unencoded raw bytes. .. todo:: This section lists the behavior for handlers which accept salt strings containing encoded characters. Some handlers may instead expect raw bytes for their salt keyword, and handle encoding / decoding them internally. It should be documented how these attributes behave in that situation. .. not yet documentated, want to make sure this is how we want to do things: .. attribute:: PasswordHash.default_salt_chars sequence of characters used to generated new salts when no salt is provided by caller. for most hashes, this is the same as :attr:`!PasswordHash.salt_chars`; but some hashes accept a much larger range of values than are typically used. This field allows the full range to be accepted, while only a select subset to be used for generation. xxx: what about a bits_per_salt_char or some such, so effective salt strength can be compared? .. _hash-unicode-behavior: Unicode Behavior ================ .. versionadded:: 1.5 Quick summary ------------- For the application developer in a hurry: * Passwords should be provided as :class:`unicode` if possible. While they may be provided as :class:`bytes`, in that case it is strongly suggested they be encoded using ``utf-8`` or ``ascii``. * Passlib will always return hashes as native python strings. This means :class:`unicode` under Python 3, and ``ascii``-encoded :class:`bytes` under Python 2. * Applications should provide hashes as :class:`unicode` if possible. However, ``ascii``-encoded :class:`bytes` are also accepted under Python 2. The following sections detail the issues surrounding encoding password hashes, and the behavior required by handlers implementing this API. It can be skipped by the uninterested. Passwords --------- Applications are strongly encouraged to provide passwords as :class:`unicode`. Two situations where an application might need to provide a password as :class:`bytes`: the application isn't unicode aware (lots of python 2 apps), or it needs to verify a password hash that used a specific encoding (eg ``latin-1``). For either of these cases, application developers should consider the following issues: * Most hashes in Passlib operate on a string of bytes. For handlers implementing such hashes, passwords provided as :class:`unicode` should be encoded to ``utf-8``, and passwords provided as :class:`bytes` should be treated as opaque. A few of these hashes officially specify this behavior; the rest have no preferred encoding at all, so this was chosen as a sensible standard behavior. Unless the underlying algorithm specifies an alternate policy, handlers should always encode unicode to ``utf-8``. * Because of the above behavior for :class:`unicode` inputs, applications which encode their passwords are urged to use ``utf-8`` or ``ascii``, so that hashes they generate with encoded bytes will verify correctly if/when they start using unicode. Applications which need to verify existing hashes using an alternate encoding such as ``latin-1`` should be wary of this future "gotcha". * A few hashes operate on :class:`unicode` strings instead. For handlers implementing such hashes: passwords provided as :class:`unicode` should be handled as appropriate, and passwords provided as :class:`bytes` should be treated as ``utf-8``, and decoded. This behavior was chosen in order to be compatible with the common case (above), combined with the fact that applications should never need to use a specific encoding with these hashes, as they are natively unicode. (The only hashes in Passlib like this are :class:`~passlib.hash.oracle10` and :class:`~passlib.hash.nthash`) Hashes ------ With the exception of plaintext passwords, literally *all* of the hash formats surveyed by the Passlib authors use only the characters found in 7-bit ``ascii``. This has caused most password hashing code (in python and elsewhere) to draw a very blurry line between :class:`unicode` and :class:`bytes`. Because of that, the following behavior was dictated less by design requirements, and more by compatibility and ease of implementation issues: * Handlers should accept hashes as either :class:`unicode` or as ``ascii``-encoded :class:`bytes`. This behavior allows applications to provide hashes as unicode or as bytes, as they please; making (among other things) migration to Python 3 easier. The primary exception to this is handlers implementing plaintext passwords. The implementations in passlib generally use ``utf-8`` to encode unicode passwords, and reproduce existing passwords as opaque bytes. * Internally, it is recommended that handlers operate on :class:`unicode` for parsing / formatting purposes, and using :class:`bytes` only on decoded data to be passed directly into their digest routine. * Handlers should return hashes as native python strings. This means :class:`unicode` under Python 3, and ``ascii``-encoded :class:`bytes` under Python 2. This behavior was chosen to fit with Python 3's unicode-oriented philosophy, while retaining backwards compatibility with Passlib 1.4 and earlier under Python 2. Handlers should use the :func:`passlib.utils.to_hash_str` function to coerce their unicode hashes to whatever is appropriate for the platform before returning them. .. rubric:: Footnotes .. [#otypes] While this specification is written referring to classes and classmethods, password hash handlers can be any type of object (instance, module, etc), so long as they offer attributes and functions with the required signatures. For example, some of the handlers in Passlib are instances of the :class:`~passlib.utils.handlers.PrefixWrapper` class. .. [#supported] all supported password hashes, whether builtin or registered from an external source can be found in the :mod:`passlib.hash` module. passlib-1.5.3/docs/copyright.rst0000644000175000017500000000003011643466373020003 0ustar biscuitbiscuit00000000000000.. include:: ../LICENSE passlib-1.5.3/docs/index.rst0000644000175000017500000000541411643466373017115 0ustar biscuitbiscuit00000000000000========================================== PassLib |release| documentation ========================================== Welcome ======= Passlib is a password hashing library for Python 2 & 3, which provides cross-platform implementations of over 20 password hashing algorithms, as well as a framework for managing existing password hashes. It's designed to be useful for a large range of tasks, including: * quick-start password hashing for new python applications ~ :doc:`quickstart guide ` * constructing a configurable hashing policy to match the needs of any python application ~ :data:`passlib.context` * reading & writing Apache htpasswd / htdigest files ~ :mod:`passlib.apache` * creating & verifying hashes used by MySQL, PostgreSQL, OpenLDAP, and other applications ~ :mod:`passlib.apps` * creating & verifying hashes found in Unix "shadow" files ~ :data:`passlib.hosts` See the library overview for more details and usage examples. Quick Links =========== .. raw:: html
Online Resources ================ .. rst-class:: html-plain-table ================ =================================================== **Homepage**: ``_ **Online Docs**: ``_ **Discussion**: ``_ ---------------- --------------------------------------------------- ---------------- --------------------------------------------------- **PyPI**: ``_ **Downloads**: ``_ **Source**: ``_ ================ =================================================== passlib-1.5.3/docs/install.rst0000644000175000017500000000667311643466373017464 0ustar biscuitbiscuit00000000000000============ Installation ============ Supported Platforms =================== Passlib requires Python 2 (>= 2.5) or Python 3. It is known to work with the following Python implementations: * CPython 2 -- v2.5 or newer. * CPython 3 -- all versions. * PyPy -- v1.5 or newer. * Jython -- v2.5 or newer. Passlib should work with all operating systems and enviroments, as it contains builtin fallbacks for almost all OS-dependant features. Google App Engine is supported as well. .. _optional-libraries: Optional Libraries ================== * `py-bcrypt `_ or `bcryptor `_ If either of these packages are installed, they will be used to provide support for the BCrypt hash algorithm. This is required if you want to handle BCrypt hashes, and your OS does not provide native BCrypt support via stdlib's :mod:`!crypt` (which includes pretty much all non-BSD systems). * `M2Crypto `_ If installed, M2Crypto will be used to accelerate some internal functions used by PBKDF2-based hashes, but it is not required even in that case. Installation Instructions ========================= To download and install using :command:`easy_install`:: easy_install passlib To download and install using :command:`pip`:: pip install passlib To install from a source directory using :command:`setup.py`:: python setup.py install .. note:: Passlib's source ships as Python 2 code, and the setup script invokes the :command:`2to3` tool + a preprocessor to translate the source to Python 3 code at install time. Aside from this internal detail, installation under Python 3 should be identical to that of Python 2. Testing ======= PassLib contains a comprehensive set of unittests providing nearly complete coverage. All unit tests are contained within the :mod:`passlib.tests` subpackage, and are designed to be run using the `Nose `_ unit testing library. Once PassLib and Nose have been installed, the tests may be run from the source directory:: # to run the platform-relevant tests... nosetests -v --tests passlib/tests # to run all tests... PASSLIB_TESTS="all" nosetests -v --tests passlib/tests # to run nose with the optional coverage plugin... # (results will be in build/coverage) PASSLIB_TESTS="all" nosetests -v --tests passlib/tests --with-coverage \ --cover-package=passlib --cover-html --cover-html-dir build/coverage (There will be a large proportion of skipped tests, this is normal). Documentation ============= The latest copy of this documentation should always be available online at ``_. If you wish to generate your own copy of the documentation, you will need to: 1. Install `Sphinx `_ (1.0 or better) 2. Install the `Cloud Sphinx Theme `_. 3. Download the PassLib source 4. From the PassLib source directory, run :samp:`python setup.py build_sphinx`. 5. Once Sphinx completes it's run, point a web browser to the file at :samp:`{$SOURCE}/build/sphinx/html/index.html` to access the PassLib documentation in html format. 6. Alternately, steps 4 & 5 can be replaced by running :samp:`python setup.py docdist`, which will build a zip file of the documentation in :samp:`{$SOURCE}/dist`. passlib-1.5.3/docs/lib/0000755000175000017500000000000011643754212016006 5ustar biscuitbiscuit00000000000000passlib-1.5.3/docs/lib/passlib.apache.rst0000644000175000017500000000372611643466373021435 0ustar biscuitbiscuit00000000000000============================================= :mod:`passlib.apache` - Apache Password Files ============================================= .. module:: passlib.apache :synopsis: reading/writing htpasswd & htdigest files This module provides utilities for reading and writing Apache's htpasswd and htdigest files; though the use of two helper classes. .. index:: apache; htpasswd Htpasswd Files ============== The :class:`!HTpasswdFile` class allows managing of htpasswd files. A quick summary of it's usage:: >>> from passlib.apache import HtpasswdFile >>> #when creating a new file, set to autoload=False, add entries, and save. >>> ht = HtpasswdFile("test.htpasswd", autoload=False) >>> ht.update("someuser", "really secret password") >>> ht.save() >>> #loading an existing file to update a password >>> ht = HtpasswdFile("test.htpasswd") >>> ht.update("someuser", "new secret password") >>> ht.save() >>> #examining file, verifying user's password >>> ht = HtpasswdFile("test.htpasswd") >>> ht.users() [ "someuser" ] >>> ht.verify("someuser", "wrong password") False >>> ht.verify("someuser", "new secret password") True >>> #making in-memory changes and exporting to string >>> ht = HtpasswdFile() >>> ht.update("someuser", "mypass") >>> ht.update("someuser", "anotherpass") >>> print ht.to_string() someuser:$apr1$T4f7D9ly$EobZDROnHblCNPCtrgh5i/ anotheruser:$apr1$vBdPWvh1$GrhfbyGvN/7HalW5cS9XB1 .. autoclass:: HtpasswdFile(path, default=None, autoload=True) .. index:: apache; htdigest Htdigest Files ============== The :class:`!HtdigestFile` class allows management of htdigest files in a similar fashion to :class:`HtpasswdFile`. .. autoclass:: HtdigestFile(path, autoload=True) .. rubric:: Footnotes .. [#] Htpasswd Manual - ``_ .. [#] Apache Auth Configuration - ``_ passlib-1.5.3/docs/lib/passlib.hash.unix_fallback.rst0000644000175000017500000000316311643466373023733 0ustar biscuitbiscuit00000000000000================================================================== :class:`passlib.hash.unix_fallback` - Unix Fallback Helper ================================================================== .. currentmodule:: passlib.hash This class does not provide an encryption scheme, but instead provides a helper for handling disabled / wildcard password fields as found in unix ``/etc/shadow`` files. Usage ===== This class is mainly useful only for plugging into a :class:`~passlib.context.CryptContext`. When used, it should always be the last scheme in the list, as it is designed to provide a fallback behavior. It can be used directly as follows:: >>> from passlib.hash import unix_fallback as uf >>> #'encrypting' a password always results in "!", the default reject hash. >>> uf.encrypt("password") '!' >>> #check if hash is recognized (all strings are recognized) >>> uf.identify('!') True >>> uf.identify('*') True >>> uf.identify('') True >>> #verify against non-empty string - no passwords allowed >>> uf.verify("password", "!") False >>> #verify against empty string: >>> # * by default, no passwords allowed >>> # * all passwords allowed IF enable_wildcard=True >>> uf.verify("password", "") False >>> uf.verify("password", "", enable_wildcard=True) True Interface ========= .. autoclass:: unix_fallback Deviations ========== According to the Linux ``shadow`` man page, an empty string is treated as a wildcard by Linux, allowing all passwords. For security purposes, this behavior is not enabled unless specifically requested by the application. passlib-1.5.3/docs/lib/passlib.hash.des_crypt.rst0000644000175000017500000001233211643466373023123 0ustar biscuitbiscuit00000000000000======================================================================= :class:`passlib.hash.des_crypt` - DES Crypt ======================================================================= .. currentmodule:: passlib.hash This class implements the original DES-based Unix Crypt algorithm. While no longer in active use in most places, it is supported for legacy purposes by many Unix flavors. .. warning:: This algorithm is extremely weak by modern standards, and should not be used if possible. Usage ===== This class can be used directly as follows:: >>> from passlib.hash import des_crypt as dc >>> dc.encrypt("password") #generate new salt, encrypt password 'JQMuyS6H.AGMo' >>> dc.identify('JQMuyS6H.AGMo') #check if hash is recognized True >>> dc.identify('$1$3azHgidD$SrJPt7B.9rekpmwJwtON31') #check if some other hash is recognized False >>> dc.verify("password", 'JQMuyS6H.AGMo') #verify correct password True >>> dc.verify("secret", 'JQMuyS6H.AGMo') #verify incorrect password False Interface ========= .. autoclass:: des_crypt(checksum=None, salt=None, strict=False) Format ====== A des-crypt hash string consists of 13 characters, drawn from ``[./0-9A-Za-z]``. The first 2 characters form a :mod:`hash64 `-encoded 12 bit integer used as the salt, with the remaining characters forming a hash64-encoded 64-bit integer checksum. A des-crypt configuration string is also accepted by this module, consists of only the first 2 characters, corresponding to the salt only. An example hash (of the string ``password``) is ``JQMuyS6H.AGMo``, where the salt is ``JQ``, and the checksum ``MuyS6H.AGMo``. .. rst-class:: html-toggle Algorithm ========= The checksum is formed by a modified version of the DES cipher in encrypt mode: 1. Given a password string and a salt string. 2. The 2 character salt string is decoded to a 12-bit integer salt value; The salt string uses little-endian :func:`hash64 ` encoding. 3. If the password is less than 8 bytes, it's NULL padded at the end to 8 bytes. 4. The lower 7 bits of the first 8 bytes of the password are used to form a 56-bit integer; with the first byte providing the most significant 7 bits, and the 8th byte providing the least significant 7 bits. The remainder of the password (if any) is ignored. 5. 25 repeated rounds of modified DES encryption are performed; starting with a null input block, and using the 56-bit integer from step 4 as the DES key. The salt is used to to mutate the normal DES encrypt operation by swapping bits :samp:`{i}` and :samp:`{i}+24` in the DES E-Box output if and only if bit :samp:`{i}` is set in the salt value. Thus, if the salt is set to ``0``, normal DES encryption is performed. (This was intended to prevent optimized implementations of regular DES encryption to be useful in attacking this algorithm). 6. The 64-bit result of the last round of step 5 is then lsb-padded with 2 zero bits. 7. The resulting 66-bit integer is encoded in big-endian order using the :func:`hash 64 ` format. Security Issues =============== DES-Crypt is no longer considered secure, for a variety of reasons: * It's use of the DES stream cipher, which is vulnerable to practical pre-image attacks, and considered broken, as well as having too-small key and block sizes. * The 12-bit salt is considered to small to defeat rainbow-table attacks (most modern algorithms provide at least a 48-bit salt). * The fact that it only uses the lower 7 bits of the first 8 bytes of the password results in a dangerously small keyspace which needs to be searched. Deviations ========== This implementation of des-crypt differs from others in a few ways: * Minimum salt string: Some implementations of des-crypt permit empty and single-character salt strings. However, behavior in these cases varies wildly; with implementations returning everything from errors to incorrect hashes that never validate. To avoid all this, PassLib will throw an "invalid salt" if the provided salt string is not at least 2 characters. * Restricted salt string character set: The underlying algorithm expects salt strings to use the :mod:`hash64 ` character set to encode a 12-bit integer. Many implementations of des-crypt will accept a salt containing other characters, but vary wildly in how they are handled, including errors and implementation-specific value mappings. To avoid all this, PassLib will throw an "invalid salt" if the salt string contains any non-standard characters. * Unicode Policy: The original des-crypt algorithm was designed for 7-bit ``us-ascii`` encoding only (as evidenced by the fact that it discards the 8th bit of all password bytes). In order to provide support for unicode strings, PassLib will encode unicode passwords using ``utf-8`` before running them through des-crypt. If a different encoding is desired by an application, the password should be encoded before handing it to PassLib. .. rubric:: Footnotes .. [#] A java implementation of des-crypt, used as base for PassLib's pure-python implementation, is located at ``_ passlib-1.5.3/docs/lib/passlib.hash.cta_pbkdf2_sha1.rst0000644000175000017500000000467611643466373024056 0ustar biscuitbiscuit00000000000000================================================================= :class:`passlib.hash.cta_pbkdf2_sha1` - Cryptacular's PBKDF2 hash ================================================================= .. index:: pbkdf2 hash; cryptacular .. currentmodule:: passlib.hash This class provides an implementation of Cryptacular's PBKDF2-HMAC-SHA1 hash format [#cta]_. PBKDF2 is a key derivation function [#pbkdf2]_ that is ideally suited as the basis for a password hash, as it provides variable length salts, variable number of rounds. .. seealso:: * :doc:`passlib.hash.pbkdf2_digest ` for some other PBKDF2-based hashes. * :doc:`passlib.hash.dlitz_pbkdf2_sha1 ` for another hash which looks almost exactly like this one. Usage ===== This class support both rounds and salts, and can be used in the exact same manner as :doc:`SHA-512 Crypt `. Interface ========= .. autoclass:: cta_pbkdf2_sha1() .. rst-class:: html-toggle Format & Algorithm ================== A example hash (of ``password``) is: ``$p5k2$2710$oX9ZZOcNgYoAsYL-8bqxKg==$AU2JLf2rNxWoZxWxRCluY0u6h6c=`` All of this scheme's hashes have the format :samp:`$p5k2${rounds}${salt}${checksum}`, where: * ``$p5k2$`` is used as the :ref:`modular-crypt-format` identifier. * :samp:`{rounds}` is the number of PBKDF2 iterations to perform, stored as lowercase hexidecimal number with no zero-padding (in the example: ``2710`` or 10000 iterations). * :samp:`{salt}` is the salt string encoding using base64 (with ``-_`` as the high values). ``oX9ZZOcNgYoAsYL-8bqxKg==`` in the example. * :samp:`{checksum}` is 28 characters encoding the resulting 20-byte PBKDF2 derived key using base64 (with ``-_`` as the high values). ``AU2JLf2rNxWoZxWxRCluY0u6h6c=`` in the example. In order to generate the checksum, the password is first encoded into UTF-8 if it's unicode. The salt is decoded from it's base64 representation. PBKDF2 is called using the encoded password, the full salt, the specified number of rounds, and using HMAC-SHA1 as it's psuedorandom function. 20 bytes of derived key are requested, and the resulting key is encoded and used as the checksum portion of the hash. .. rubric:: Footnotes .. [#cta] The reference for this hash format - ``_. .. [#pbkdf2] The specification for the PBKDF2 algorithm - ``_. passlib-1.5.3/docs/lib/passlib.hash.phpass.rst0000644000175000017500000000564411643466373022435 0ustar biscuitbiscuit00000000000000.. index:: phpass; portable hash, phpbb3; phpass hash ================================================================== :class:`passlib.hash.phpass` - PHPass' Portable Hash ================================================================== .. currentmodule:: passlib.hash This algorithm is used primarily by PHP software which uses PHPass [#home], a PHP library similar to PassLib. The PHPass Portable Hash is a custom password hash used by PHPass as a fallback when none of it's other hashes are available. Due to it's reliance on MD5, and the simplistic implementation, other hash algorithms should be used if possible. Usage ===== Supporting a variable sized salt and variable number of rounds, this scheme is used in exactly the same way as :doc:`bcrypt `. Interface ========= .. autoclass:: phpass(checksum=None, salt=None, rounds=None, strict=False) Format ================== An example hash (of ``password``) is ``$P$8ohUJ.1sdFw09/bMaAQPTGDNi2BIUt1``. A phpass portable hash string has the format :samp:`$P${rounds}{salt}{checksum}`, where: * ``$P$`` is the prefix used to identify phpass hashes, following the :ref:`modular-crypt-format`. * :samp:`{rounds}` is a single character encoding a 6-bit integer representing the number of rounds used. This is logarithmic, the real number of rounds is ``2**rounds``. (in the example, rounds is encoded as ``8``, or 2**13 iterations). * :samp:`{salt}` is eight characters drawn from ``[./0-9A-Za-z]``, providing a 48-bit salt (``ohUJ.1sd`` in the example). * :samp:`{checksum}` is 22 characters drawn from the same set, encoding the 128-bit checksum (``Fw09/bMaAQPTGDNi2BIUt1`` in the example). .. note:: Note that phpBB3 databases uses the alternate prefix ``$H$``, both prefixes are recognized by this implementation, and the checksums are the same. Algorithm ========= PHPass uses a straightforward algorithm to calculate the checksum: 1. an initial result is generated from the MD5 digest of the salt string + the secret. 2. for :samp:`2**{rounds}` iterations, a new result is created from the MD5 digest of the last result + the password. 3. the last result is then encoded according to the format described above. Deviations ========== This implementation of phpass differs from the specification in one way: * Unicode Policy: The underlying algorithm takes in a password specified as a series of non-null bytes, and does not specify what encoding should be used; though a ``us-ascii`` compatible encoding is implied by nearly all known reference hashes. In order to provide support for unicode strings, PassLib will encode unicode passwords using ``utf-8`` before running them through phpass. If a different encoding is desired by an application, the password should be encoded before handing it to PassLib. .. rubric:: Footnotes .. [#pp] PHPass homepage, which describes the Portable Hash algorithm - ``_ passlib-1.5.3/docs/lib/passlib.hosts.rst0000644000175000017500000001012111643466373021337 0ustar biscuitbiscuit00000000000000============================================ :mod:`passlib.hosts` - OS Password Handling ============================================ .. module:: passlib.hosts :synopsis: encrypting & verifying operating system passwords This module provides :class:`!CryptContext` instances for encrypting & verifying password hashes tied to user accounts of various operating systems. While (most) of the objects are available cross-platform, their use is oriented primarily towards Linux and BSD variants. .. seealso:: :mod:`passlib.context` module for details about how to use a :class:`!CryptContext` instance. Unix Password Hashes ==================== PassLib provides a number of pre-configured :class:`!CryptContext` instances which can identify and manipulate all the formats used by Linux and BSD. See the :ref:`modular crypt identifier list ` for a complete list of which hashes are supported by which operating system. Predefined Contexts ------------------- PassLib provides :class:`!CryptContext` instances for the following Unix variants: .. data:: linux_context context instance which recognizes hashes used by the majority of Linux distributions. encryption defaults to :class:`!sha512_crypt`. .. data:: freebsd_context context instance which recognizes all hashes used by FreeBSD 8. encryption defaults to :class:`!bcrypt`. .. data:: netbsd_context context instance which recognizes all hashes used by NetBSD. encryption defaults to :class:`!bcrypt`. .. data:: openbsd_context context instance which recognizes all hashes used by OpenBSD. encryption defaults to :class:`!bcrypt`. .. note:: All of the above contexts include the :class:`~passlib.hash.unix_fallback` handler as a final fallback. This special handler treats all strings as invalid passwords, particularly the common strings ``!`` and ``*`` which are used to indicate that an account has been disabled [#shadow]_. It can also be configured to treat empty strings as a wildcard allowing in all passwords, though this behavior is disabled by default for security reasons. A quick usage example, using the :data:`!linux_context` instance:: >>> from passlib.hosts import linux_context >>> hash = linux_context.encrypt("password") >>> hash '$6$rounds=31779$X2o.7iqamZ.bAigR$ojbo/zh6sCmUuibhM7lnqR4Vy0aB3xGZXOYVLgtTFgNYiXaTNn/QLUz12lDSTdxJCLXHzsHiWCsaryAlcbAal0' >>> linux_context.verify("password", hash) True >>> linux_context.identify(hash) 'sha512_crypt' >>> linux_context.encrypt("password", scheme="des_crypt") '2fmLLcoHXuQdI' >>> linux_context.identify('2fmLLcoHXuQdI') 'des_crypt' Current Host OS --------------- .. data:: host_context :platform: Unix This :class:`~passlib.context.CryptContext` instance should detect and support all the algorithms the native OS :func:`!crypt` offers. The main differences between this object and :func:`!crypt`: * this object provides introspection about *which* schemes are available on a given system (via ``host_context.policy.schemes()``). * it defaults to the strongest algorithm available, automatically configured to an appropriate strength for encrypting new passwords. * whereas :func:`!crypt` typically defaults to using :mod:`~passlib.hash.des_crypt`; and provides little introspection. As an example, this can be used in conjunction with stdlib's :mod:`!spwd` module to verify user passwords on the local system:: >>> #NOTE/WARNING: this example requires running as root on most systems. >>> import spwd, os >>> from passlib.hosts import host_context >>> hash = spwd.getspnam(os.environ['USER']).sp_pwd >>> host_context.verify("toomanysecrets", hash) True .. versionchanged:: 1.4 This object is only available on systems where the stdlib :mod:`!crypt` module is present. In version 1.3 and earlier, it was available on non-Unix systems, though it did nothing useful. .. rubric:: Footnotes .. [#shadow] Man page for Linux /etc/shadow - ``_ passlib-1.5.3/docs/lib/passlib.utils.pbkdf2.rst0000644000175000017500000000203411643466373022512 0ustar biscuitbiscuit00000000000000============================================================= :mod:`passlib.utils.pbkdf2` - PBKDF2 key derivation algorithm ============================================================= .. module:: passlib.utils.pbkdf2 :synopsis: PBKDF2 and related key derivation algorithms This module provides a couple of key derivation functions, as well as supporting utilities. Primarily, it offers :func:`pbkdf2`, which provides the ability to generate an arbitrary length key using the PBKDF2 key derivation algorithm, as specified in `rfc 2898 `_. This function can be helpful in creating password hashes using schemes which have been based around the pbkdf2 algorithm. PKCS#5 Key Derivation Functions =============================== .. autofunction:: pbkdf1 .. autofunction:: pbkdf2 .. note:: The details of PBKDF1 and PBKDF2 are specified in :rfc:`2898`. Helper Functions ================ .. autofunction:: get_prf .. given how this module is expanding in scope, perhaps it should be renamed "kdf"? passlib-1.5.3/docs/lib/passlib.hash.bcrypt.rst0000644000175000017500000001042411643753130022420 0ustar biscuitbiscuit00000000000000================================================================== :class:`passlib.hash.bcrypt` - BCrypt ================================================================== .. currentmodule:: passlib.hash BCrypt was developed to replace :class:`~passlib.hash.md5_crypt` for BSD systems. It uses a modified version of the Blowfish stream cipher. Featuring a large salt and variable number of rounds, it's currently the default password hash for many systems (notably BSD), and has no known weaknesses. It is one of the three hashes Passlib :ref:`recommends ` for new applications. .. note:: It is strongly recommended to install :ref:`PyBcrypt or BCryptor ` if this algorithm is going to be used. Usage ===== This class can be used directly as follows:: >>> from passlib.hash import bcrypt >>> #generate new salt, encrypt password >>> h = bcrypt.encrypt("password") >>> h '$2a$12$NT0I31Sa7ihGEWpka9ASYrEFkhuTNeBQ2xfZskIiiJeyFXhRgS.Sy' >>> #same, but with explict number of rounds >>> bcrypt.encrypt("password", rounds=8) '$2a$08$8wmNsdCH.M21f.LSBSnYjQrZ9l1EmtBc9uNPGL.9l75YE8D8FlnZC' >>> #check if hash is a bcrypt hash >>> bcrypt.identify(h) True >>> #check if some other hash is recognized >>> bcrypt.identify('$1$3azHgidD$SrJPt7B.9rekpmwJwtON31') False >>> #verify correct password >>> bcrypt.verify("password", h) True >>> #verify incorrect password >>> bcrypt.verify("wrong", h) False Interface ========= .. autoclass:: bcrypt Format & Algorithm ================== Bcrypt is compatible with the :ref:`modular-crypt-format`, and uses ``$2$`` and ``$2a$`` as the identifying prefix for all it's strings (``$2$`` is seen only for legacy hashes which used an older version of Bcrypt). An example hash (of ``password``) is ``$2a$12$GhvMmNVjRW29ulnudl.LbuAnUtN/LRfe1JsBm1Xu6LE3059z5Tr8m``. Bcrypt hashes have the format :samp:`$2a${rounds}${salt}{checksum}`, where: * :samp:`{rounds}` is the cost parameter, encoded as 2 zero-padded decimal digits, which determines the number of iterations used via :samp:`{iterations}=2**{rounds}` (rounds is 12 in the example). * :samp:`{salt}` is the 22 character salt string, using the characters in the regexp range ``[./A-Za-z0-9]`` (``GhvMmNVjRW29ulnudl.Lbu`` in the example). * :samp:`{checksum}` is the 31 character checksum, using the same characters as the salt (``AnUtN/LRfe1JsBm1Xu6LE3059z5Tr8m`` in the example). BCrypt's algorithm is described in detail in it's specification document [#f1]_. Deviations ========== This implementation of bcrypt differs from others in a few ways: * Restricted salt string character set: BCrypt does not specify what the behavior should be when passed a salt string outside of the regexp range ``[./A-Za-z0-9]``. In order to avoid this situtation, PassLib strictly limits salts to the allowed character set, and will throw a ValueError if an invalid salt character is encountered. * Unicode Policy: The underlying algorithm takes in a password specified as a series of non-null bytes, and does not specify what encoding should be used; though a ``us-ascii`` compatible encoding is implied by nearly all implementations of bcrypt as well as all known reference hashes. In order to provide support for unicode strings, PassLib will encode unicode passwords using ``utf-8`` before running them through bcrypt. If a different encoding is desired by an application, the password should be encoded before handing it to PassLib. * Padding Bits BCrypt's base64 encoding results in the last character of the salt encoding only 2 bits of data, the remaining 4 are "padding" bits. Similarly, the last character of the digest contains 4 bits of data, and 2 padding bits. Because of the way they are coded, many BCrypt implementations will reject all passwords if these padding bits are not set to 0. Due to a legacy issue with Passlib <= 1.5.2, Passlib instead prints a warning if it encounters hashes with any padding bits set, and will then validate them correctly. (This behavior will eventually be deprecated and such hashes will throw a :exc:`ValueError` instead). .. rubric:: Footnotes .. [#f1] ``_ - the bcrypt format specification passlib-1.5.3/docs/lib/passlib.context.rst0000644000175000017500000000331211643466373021667 0ustar biscuitbiscuit00000000000000.. index:: CryptContext; usage examples, CryptContext; overview .. _cryptcontext-overview: ============================================== :mod:`passlib.context` - CryptContext Overview ============================================== .. module:: passlib.context :synopsis: CryptContext class for managing multiple password hash schemes Motivation ========== Though there is a wide range of password hashing schemes, within a specific context (like a linux "shadow" file) only a select list of schemes will be used. As time goes on, new schemes are added and made the default, the strength of existing schemes is tweaked, and other schemes are deprecated entirely. Throughout all this, existing password hashes that don't comply with the new policies must be detected and rehashed using the new default configuration. In order to automate as much of these tasks as possible, this module provides the :class:`CryptContext` class. Essentially, a :class:`!CryptContext` instance contains a list of hash handlers that it should recognize, along with information about which ones are deprecated, which is the default, and what configuration constraints an application has placed on a particular scheme. While contexts can be created explicitly, Passlib also offers a number of predefined :class:`!CryptContext` instances which can be used out-of-the box (see :mod:`passlib.apps` and :mod:`passlib.hosts`), or :ref:`modified ` to suit the application. Subtopics ========= New users should see the usage examples in the next section to get a feel for how the :class:`!CryptContext` class works. .. toctree:: :maxdepth: 1 passlib.context-usage passlib.context-interface passlib.context-options passlib-1.5.3/docs/lib/passlib.utils.des.rst0000644000175000017500000000145611643466373022124 0ustar biscuitbiscuit00000000000000============================================= :mod:`passlib.utils.des` - DES routines ============================================= .. module:: passlib.utils.des :synopsis: routines for performing DES encryption .. warning:: NIST has declared DES to be "inadequate" for cryptographic purposes. These routines, and the password hashes based on them, should not be used in new applications. This module contains routines for encrypting blocks of data using the DES algorithm. They do not support multi-block operation or decryption, since they are designed primarily for use in password hash algorithms (such as :class:`~passlib.hash.des_crypt` and :class:`~passlib.hash.bsdi_crypt`). .. autofunction:: expand_des_key .. autofunction:: des_encrypt_block .. autofunction:: mdes_encrypt_int_block passlib-1.5.3/docs/lib/passlib.utils.handlers.rst0000644000175000017500000002063111643466373023145 0ustar biscuitbiscuit00000000000000.. index:: pair: custom hash handler; implementing ========================================================================== :mod:`passlib.utils.handlers` - Helpers for writing password hash handlers ========================================================================== .. module:: passlib.utils.handlers :synopsis: helper classes for writing password hash handlers .. todo:: This module, and the instructions on how to write a custom handler, definitely need to be rewritten for clarity. They are not yet organized, and may leave out some important details. Implementing Custom Handlers ============================ All that is required in order to write a custom handler that will work with PassLib is to create an object (be it module, class, or object) that exposes the functions and attributes required by the :ref:`password-hash-api`. For classes, PassLib does not make any requirements about what a class instance should look like (if the implementation even uses them). That said, most of the handlers built into PassLib are based around the :class:`GenericHandler` class, and it's associated mixin classes. While deriving from this class is not required, doing so will greatly reduce the amount of addition code that is needed for all but the most convoluted password hash schemes. Once a handler has been written, it may be used explicitly, passed into an application's custom :class:`CryptContext` directly, or registered globally with PassLib via the :mod:`passlib.registry` module. See :ref:`testing-hash-handlers` for details about how to test custom handlers against PassLib's unittest suite. The GenericHandler Class ======================== Design ------ Most of the handlers built into PassLib are based around the :class:`GenericHandler` class. This class is designed under the assumption that the common workflow for hashes is some combination of the following: 1. parse hash into constituent parts - performed by :meth:`~GenericHandler.from_string`. 2. validate constituent parts - performed by :class:`!GenericHandler`'s constructor, and the normalization functions such as :meth:`~GenericHandler.norm_checksum` and :meth:`~HasSalt.norm_salt` which are provided by it's related mixin classes. 3. calculate the raw checksum for a specific password - performed by :meth:`~GenericHandler.calc_checksum`. 4. assemble hash, including new checksum, into a new string - performed by :meth:`~GenericHandler.to_string`. With this in mind, :class:`!GenericHandler` provides implementations of most of the :ref:`password-hash-api` methods, eliminating the need for almost all the boilerplate associated with writing a password hash. In order to minimize the amount of unneeded features that must be loaded in, the :class:`!GenericHandler` class itself contains only the parts which are needed by almost all handlers: parsing, rendering, and checksum validation. Validation of all other parameters (such as salt, rounds, etc) is split out into separate :ref:`mixin classes ` which enhance :class:`!GenericHandler` with additional features. Usage ----- In order to use :class:`!GenericHandler`, just subclass it, and then do the following: * fill out the :attr:`name` attribute with the name of your hash. * fill out the :attr:`~PasswordHash.setting_kwds` attribute with a tuple listing all the settings your hash accepts. * provide an implementation of the :meth:`from_string` classmethod. this method should take in a potential hash string, parse it into components, and return an instance of the class which contains the parsed components. It should throw a :exc:`ValueError` if no hash, or an invalid hash, is provided. * provide an implementation of the :meth:`to_string` instance method. this method should render an instance of your handler class (such as returned by :meth:`from_string`), returning a hash string. * provide an implementation of the :meth:`calc_checksum` instance method. this is the heart of the hash; this method should take in the password as the first argument, then generate and return the digest portion of the hash, according to the settings (such as salt, etc) stored in the parsed instance this method was called from. note that it should not return the full hash with identifiers, etc; that job should be performed by :meth:`to_string`. Some additional notes: * In addition to simply subclassing :class:`!GenericHandler`, most handlers will also benefit from adding in some of the mixin classes that are designed to add features to :class:`!GenericHandler`. See :ref:`generic-handler-mixins` for more details. * Most implementations will want to alter/override the default :meth:`~GenericHandler.identify` method. By default, it returns ``True`` for all hashes that :meth:`~GenericHandler.from_string` can parse without raising a :exc:`ValueError`; which is reliable, but somewhat slow. For faster identification purposes, subclasses may fill in the :attr:`~GenericHandler.ident` attribute with the hash's identifying prefix, which :meth:`~GenericHandler.identify` will then test for instead of calling :meth:`~GenericHandler.from_string`. For more complex situations, a custom implementation should be used; the :class:`HasManyIdents` mixin may also be helpful. * This class does not support context kwds of any type, since that is a rare enough requirement inside passlib. Interface --------- .. autoclass:: GenericHandler .. _generic-handler-mixins: GenericHandler Mixins --------------------- .. autoclass:: HasSalt .. autoclass:: HasRounds .. autoclass:: HasManyIdents .. autoclass:: HasManyBackends .. autoclass:: HasRawSalt .. autoclass:: HasRawChecksum Examples -------- .. todo:: Show some walk-through examples of how to use GenericHandler and it's mixins The StaticHandler class ======================= .. autoclass:: StaticHandler .. todo:: Show some examples of how to use StaticHandler .. index:: pair: custom hash handler; testing Other Constructors ================== .. autoclass:: PrefixWrapper .. _testing-hash-handlers: Testing Hash Handlers ===================== Within it's unittests, Passlib provides the :class:`~passlib.tests.utils.HandlerCase` class, which can be subclassed to provide a unittest-compatible test class capable of checking if a handler adheres to the :ref:`password-hash-api`. Usage ----- As an example of how to use :class:`!HandlerCase`, the following is an annoted version of the unittest for :class:`passlib.hash.des_crypt`:: from passlib.hash import des_crypt from passlib.tests.utils import HandlerCase #create a subclass for the handler... class DesCryptTest(HandlerCase): "test des-crypt algorithm" #: [required] - store the handler object itself in the handler attribute handler = des_crypt #: [optional] - if your hash only uses the first X characters of the password, #: set that value here. otherwise leave the default (-1). secret_chars = 8 #: [required] - this should be a list of (password, hash) pairs, # which should all verify correctly using your handler. # it is recommend include pairs which test all of the following: # # * empty string & short strings for passwords # * passwords with 2 byte unicode characters # * hashes with varying salts, rounds, and other options known_correct_hashes = ( #format: (password, hash) ('', 'OgAwTx2l6NADI'), (' ', '/Hk.VPuwQTXbc'), ('test', 'N1tQbOFcM5fpg'), ('Compl3X AlphaNu3meric', 'um.Wguz3eVCx2'), ('4lpHa N|_|M3r1K W/ Cur5Es: #$%(*)(*%#', 'sNYqfOyauIyic'), ('AlOtBsOl', 'cEpWz5IUCShqM'), (u'hell\u00D6', 'saykDgk3BPZ9E'), ) #: [optional] - if there are hashes which are similar in format #: to your handler, and you want to make sure :meth:`identify` #: does not return ``True`` for such hashes, #: list them here. otherwise this can be omitted. # known_unidentified_hashes = [ #bad char in otherwise correctly formatted hash '!gAwTx2l6NADI', ] Interface --------- .. autoclass:: passlib.tests.utils.HandlerCase() passlib-1.5.3/docs/lib/passlib.hash.grub_pbkdf2_sha512.rst0000644000175000017500000000623511643466373024406 0ustar biscuitbiscuit00000000000000============================================================= :class:`passlib.hash.grub_pbkdf2_sha512` - Grub's PBKDF2 Hash ============================================================= .. index:: pbkdf2 hash; grub .. currentmodule:: passlib.hash This class provides an implementation of Grub's PBKDF2-HMAC-SHA512 password hash [#grub]_, as generated by the :command:`grub-mkpasswd-pbkdf2` command, and may be found in Grub2 configuration files. PBKDF2 is a key derivation function [#pbkdf2]_ that is ideally suited as the basis for a password hash, as it provides variable length salts, variable number of rounds. .. seealso:: * :doc:`passlib.hash.pbkdf2_digest ` for some other PBKDF2-based hashes. Usage ===== These classes support both rounds and salts, and can be used in the exact same manner as :doc:`SHA-512 Crypt `. Interface ========= .. autoclass:: grub_pbkdf2_sha512() Format & Algorithm ================== A example hash (of ``password``) is :: grub.pbkdf2.sha512.10000.4483972AD2C52E1F590B3E2260795FDA9CA0B07B 96FF492814CA9775F08C4B59CD1707F10B269E09B61B1E2D11729BCA8D62B7827 B25B093EC58C4C1EAC23137.DF4FCB5DD91340D6D31E33423E4210AD47C7A4DF9 FA16F401663BF288C20BF973530866178FE6D134256E4DBEFBD984B652332EED3 ACAED834FEA7B73CAE851D All of this scheme's hashes have the format :samp:`grub.pbkdf2.sha512.{rounds}.{salt}.{checksum}`, where :samp:`{rounds}` is the number of iteration stored in decimal, :samp:`{salt}` is the salt string encoded using upper-case hexdecimal, and :samp:`{checksum}` is the resulting 64-byte derived key, also encoded in upper-case hexidecimal. It can be identified by the prefix ``grub.pdkdf2.sha512.``. The algorithm used is the same as :class:`pbkdf2_sha1`: the password is encoded into UTF-8 if not already encoded, and passed through :func:`~passlib.utils.pbkdf2.pbkdf2` along with the decoded salt, and the number of rounds. The result is then encoded into hexidecimal. .. Hash Translation ---------------- Note that despite encoding and format differences, :class:`pbkdf2_sha512` and :class:`!grub_pbkdf2_sha512` share an identical algorithm, and one can be converted to the other using the following code:: >>> from passlib.hash import pbkdf2_sha512, grub_pbkdf2_sha512 >>> #given a pbkdf2_sha512 hash... >>> h = pbkdf2_sha512.encrypt("password") >>> h '$pbkdf2-sha512$6400$y6vYff3SihJiqumIrNXwGw$NobVwyUlVI52/Cvrguwli5fX6XgKHNUf7fWWS2VgoWEevaTCiZx4OCYhwGFwzUAuz/g1zQVSIf.9JEb0BEVEEA' >>> #it can be parsed into options >>> hobj = pbkdf2_sha512.from_string(h) >>> rounds, salt, chk = hobj.rounds, hobj.salt, hobj.checksum >>> #and a new grub hash can be created >>> gobj = grub_pbkdf2_sha512(rounds=rounds, salt=salt, checksum=chk) >>> g = gobj.to_string() >>> g >>> grub_pbkdf2_sha512.verify("password", g) True .. rubric:: Footnotes .. [#grub] Information about Grub's password hashes - ``_. .. [#pbkdf2] The specification for the PBKDF2 algorithm - ``_. passlib-1.5.3/docs/lib/passlib.hash.bigcrypt.rst0000644000175000017500000001231711643466373022755 0ustar biscuitbiscuit00000000000000======================================================================= :class:`passlib.hash.bigcrypt` - BigCrypt ======================================================================= .. currentmodule:: passlib.hash This class implements BigCrypt (a modified version of des-crypt) commonly found on HP-UX, Digital Unix, and OSF/1. The main difference with :class:`~passlib.hash.des_crypt` is that bigcrypt uses all the characters of a password, not just the first 8, and has a variable length hash string. .. warning:: This algorithm is dangerously weak, and should not be used if at all possible. Usage ===== This class can be used in exactly the same manner as :class:`~passlib.hash.des_crypt`. Interface ========= .. autoclass:: bigcrypt(checksum=None, salt=None, strict=False) Format ====== An example hash (of the string ``passphrase``) is ``S/8NbAAlzbYO66hAa9XZyWy2``. A bigcrypt hash string has the format :samp:`{salt}{checksum_1}{checksum_2...}{checksum_n}` for some integer :samp:`{n}>0`, where: * :samp:`{salt}` is the salt, stored as a 2 character :func:`hash64 `-encoded 12-bit integer (``S/`` in the example). * each :samp:`{checksum_i}` is a separate checksum, stored as an 11 character :func:`hash64 `-encoded 64-bit integer (``8NbAAlzbYO6`` and ``6hAa9XZyWy2`` in the example). * the integer :samp:`n` (the number of checksums) is determined by the formula :samp:`{n}=min(1, (len({secret})+7)//8)`. .. rst-class:: html-toggle Algorithm ========= The bigcrypt algorithm is designed to re-use the original des-crypt algorithm: 1. Given a password string and a salt string. 2. The password is NULL padded at the end to the smallest non-zero multiple of 8 bytes. 3. The lower 7 bits of the first 8 characters of the password are used to form a 56-bit integer; with the first character providing the most significant 7 bits, and the 8th character providing the least significant 7 bits. 4. The 2 character salt string is decoded to a 12-bit integer salt value; The salt string uses little-endian :func:`hash64 ` encoding. 5. 25 repeated rounds of modified DES encryption are performed; starting with a null input block, and using the 56-bit integer from step 3 as the DES key. The salt is used to to mutate the normal DES encrypt operation by swapping bits :samp:`{i}` and :samp:`{i}+24` in the DES E-Box output if and only if bit :samp:`{i}` is set in the salt value. 6. The 64-bit result of the last round of step 5 is then lsb-padded with 2 zero bits. 7. The resulting 66-bit integer is encoded in big-endian order using the :func:`hash 64 ` format. This forms the first checksum segment. 8. For each additional block of 8 bytes in the padded password (from step 2), an additional checksum is generated by repeating steps 3..7, with the following changes: a. Step 3 uses the specified 8 bytes of the password, instead of the first 8 bytes. b. Step 4 uses the first two characters from the previous checksum as the salt for the next checksum. 9. The final checksum string is the concatenation of the checksum segments generated from steps 7 and 8, in order. .. note:: Because of the chained structure, bigcrypt has the property that the first 13 characters of any bigcrypt hash form a valid :class:`~passlib.hash.des_crypt` hash of the same password; and bigcrypt hashes of any passwords less than 9 characters will be identical to des-crypt. Security Issues =============== BigCrypt is dangerously flawed: * It suffers from all the flaws of :class:`~passlib.hash.des_crypt`. * Since checksum in it's hash is essentially a separate des-crypt checksum, they can be attacked in parallel. * It reveals information about the length of the encoded password (to within 8 characters), further reducing the keyspace that needs to be searched for each of the invididual segments. * The last checksum typically contains only a few characters of the passphrase, and once cracked, can be used to narrow the overall keyspace. Deviations ========== This implementation of bigcrypt differs from others in two ways: * Maximum Password Size: This implementation currently accepts arbitrarily large passwords, producing arbitrarily large hashes. Other implementation have various limits on maximum password length (commonly, 128 chars), and discard the remaining part of the password. Thus, while PassLib should be able to verify all existing bigcrypt hashes, other systems may require hashes generated by PassLib to be truncated to their specific maximum length. * Unicode Policy: The original bigcrypt algorithm was designed for 7-bit ``us-ascii`` encoding only (as evidenced by the fact that it discards the 8th bit of all password bytes). In order to provide support for unicode strings, PassLib will encode unicode passwords using ``utf-8`` before running them through bigcrypt. If a different encoding is desired by an application, the password should be encoded before handing it to PassLib. .. rubric:: Footnotes .. [#] discussion of bigcrypt & crypt16 - ``_ passlib-1.5.3/docs/lib/passlib.hash.sha256_crypt.rst0000644000175000017500000000237311643466373023364 0ustar biscuitbiscuit00000000000000================================================================== :class:`passlib.hash.sha256_crypt` - SHA-256 Crypt ================================================================== .. currentmodule:: passlib.hash Defined by the same specification as :class:`~passlib.hash.sha512_crypt`, SHA256-Crypt is identical to SHA512-Crypt in almost every way, including design and security issues. It's main advantage over SHA512-Crypt is that it may be faster on 32 bit operating systems. .. seealso:: :doc:`SHA512-Crypt ` Usage ===== This class can be used in exactly the same manner as :class:`~passlib.hash.sha512_crypt`. Interface ========= .. autoclass:: sha256_crypt(checksum=None, salt=None, rounds=None, strict=False) Format & Algorithm ================== SHA256-Crypt is defined by the same specification as SHA512-Crypt. The format and algorithm are exactly the same, except for the following notable differences: * it uses the :ref:`modular crypt prefix ` ``$5$``, whereas SHA-512-Crypt uses ``$6$``. * it uses the SHA-256 message digest in place of the SHA-512 message digest. * it's output hash is correspondingly smaller in size, encoding a 256 bit checksum instead of 512. See SHA512-Crypt for more details. passlib-1.5.3/docs/lib/passlib.hash.ldap_pbkdf2_digest.rst0000644000175000017500000000262311643466373024640 0ustar biscuitbiscuit00000000000000================================================================= :samp:`passlib.hash.ldap_pbkdf2_{digest}` - Generic PBKDF2 Hashes ================================================================= .. index:: pbkdf2 hash; generic ldap .. currentmodule:: passlib.hash PassLib provides three custom hash schemes based on the PBKDF2 [#pbkdf2]_ algorithm which are compatible with the :ref:`ldap hash format `: :class:`!ldap_pbkdf2_sha1`, :class:`!ldap_pbkdf2_sha256`, :class:`!ldap_pbkdf2_sha512`. They feature variable length salts, variable rounds. .. seealso:: These classes are simply wrappers around the :doc:`MCF-Compatible Simple PBKDF2 Hashes `. Interface ========= .. class:: ldap_pbkdf2_sha1() this is the same as :class:`pbkdf2_sha1`, except that it uses ``{PBKDF2}`` as it's identifying prefix instead of ``$pdkdf2$``. .. class:: ldap_pbkdf2_sha256() this is the same as :class:`pbkdf2_sha256`, except that it uses ``{PBKDF2-SHA256}`` as it's identifying prefix instead of ``$pdkdf2-sha256$``. .. class:: ldap_pbkdf2_sha512() this is the same as :class:`pbkdf2_sha512`, except that it uses ``{PBKDF2-SHA512}`` as it's identifying prefix instead of ``$pdkdf2-sha512$``. .. rubric:: Footnotes .. [#pbkdf2] The specification for the PBKDF2 algorithm - ``_, part of :rfc:`2898`. passlib-1.5.3/docs/lib/passlib.hash.atlassian_pbkdf2_sha1.rst0000644000175000017500000000370011643466373025251 0ustar biscuitbiscuit00000000000000=========================================================================== :class:`passlib.hash.atlassian_pbkdf2_sha1` - Atlassian's PBKDF2-based Hash =========================================================================== .. index:: pair: atlassian; pbkdf2 hash .. currentmodule:: passlib.hash This class provides an implementation of the PBKDF2 based hash used by Atlassian in Jira and other products. Note that unlike the most PBKDF2 hashes supported by Passlib, this one uses a fixed number of rounds (10000). That is currently a sufficient amount, but it cannot be altered; so this scheme should only be used to read existing hashes, and not used in new applications. .. seealso:: :doc:`passlib.hash.pbkdf2_digest ` for some other PBKDF2-based hashes. Usage ===== These classes support both rounds and salts, and can be used in the exact same manner as :doc:`SHA-512 Crypt `. Interface ========= .. autoclass:: atlassian_pbkdf2_sha1() Format & Algorithm ================== All of this scheme's hashes have the format :``{PKCS5S2}``, where :samp:`` is a 64 character base64 encoded string; which (when decoded), contains a 16 byte salt, and a 32 byte checksum. A example hash (of ``password``) is: ``{PKCS5S2}DQIXJU038u4P7FdsuFTY/+35bm41kfjZa57UrdxHp2Mu3qF2uy+ooD+jF5t1tb8J`` Once decoded, the salt value (in hexdecimal octets) is: ``0d0217254d37f2ee0fec576cb854d8ff`` and the checksum value (in hexidecimal octets) is: ``edf96e6e3591f8d96b9ed4addc47a7632edea176bb2fa8a03fa3179b75b5bf09`` When calculating the checksum: the password is encoded into UTF-8 if not already encoded. Using the specified salt, and a fixed 10000 rounds, PBKDF2-HMAC-SHA1 is used to generate a 32 byte key, which appended to the salt and encoded in base64. .. rubric:: Footnotes .. [#pbkdf2] The specification for the PBKDF2 algorithm - ``_. passlib-1.5.3/docs/lib/passlib.utils.h64.rst0000644000175000017500000000327011643466373021746 0ustar biscuitbiscuit00000000000000================================================ :mod:`passlib.utils.h64` - Hash-64 Codec helpers ================================================ .. module:: passlib.utils.h64 :synopsis: Hash-64 Codec helpers Many of the password hash algorithms in passlib use a encoding scheme very similar to (but not compatible with) the standard base64 encoding scheme. the main differences are that it assigns the characters *completely* different numeric values compared to base64, as well as using ``.`` instead of ``+`` in it's character set. This encoding system appears to have originated with des-crypt hash, but is used by md5-crypt, sha-256-crypt, and others. within passlib, this encoding is referred as ``hash64`` encoding, and this module contains various utilities functions for encoding and decoding strings in that format. .. note:: It may *look* like bcrypt uses this scheme, when in fact bcrypt uses yet another ordering, which does not match hash64 or other base64 schemes. .. _h64charset: Constants ========= .. data:: CHARS = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" The character set used by the Hash-64 format. A character's index in CHARS denotes it's corresponding 6-bit integer value. Bytes <-> Hash64 ================ .. autofunction:: encode_bytes .. autofunction:: decode_bytes .. autofunction:: encode_transposed_bytes .. autofunction:: decode_transposed_bytes Int <-> Hash64 ============== .. autofunction:: decode_int6 .. autofunction:: encode_int6 .. autofunction:: decode_int12 .. autofunction:: encode_int12 .. autofunction:: decode_int24 .. autofunction:: encode_int24 .. autofunction:: decode_int64 .. autofunction:: encode_int64 passlib-1.5.3/docs/lib/passlib.hash.mysql41.rst0000644000175000017500000000346211643466373022445 0ustar biscuitbiscuit00000000000000.. index:: mysql; PASSWORD() ===================================================================== :class:`passlib.hash.mysql41` - MySQL 4.1 password hash ===================================================================== .. currentmodule:: passlib.hash This class implements the second of MySQL's password hash functions, used to store it's user account passwords. Introduced in MySQL 4.1.1 under the function ``PASSWORD()``, it replaced the previous algorithm (:class:`~passlib.hash.mysql323`) as the default used by MySQL, and is still in active use under MySQL 5. .. warning:: This algorithm is extremely weak, and should not be used for any purposes besides manipulating existing Mysql 4.1+ password hashes. .. seealso:: :mod:`!passlib.apps` for a :ref:`list of premade mysql contexts `. Usage ===== Users will most likely find the frontends provided by :mod:`passlib.apps` to be more useful than accessing this class directly. That aside, this class can be used in the same manner as :class:`~passlib.hash.mysql323`. Interface ========= .. autoclass:: mysql41() Format & Algorithm ================== A mysql-41 password hash consists of an asterisk ``*`` followed by 40 hexidecimal digits, directly encoding the 160 bit checksum. An example hash (of ``password``) is ``*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19``. MySQL always uses upper-case letters, and so does PassLib (though PassLib will recognize lower-case letters as well). The checksum is calculated simply, as the SHA1 hash of the SHA1 hash of the password, which is then encoded into hexidecimal. Security Issues =============== Lacking any sort of salt, and using only 2 rounds of the common SHA1 message digest, it's not very secure, and should not be used for *any* purpose but verifying existing MySQL 4.1+ password hashes. passlib-1.5.3/docs/lib/passlib.utils.rst0000644000175000017500000000463711643754032021346 0ustar biscuitbiscuit00000000000000============================================= :mod:`passlib.utils` - Helper Functions ============================================= .. module:: passlib.utils :synopsis: helper functions for implementing password hashes This module contains a number of utility functions used by passlib to implement the builtin handlers, and other code within passlib. They may also be useful when implementing custom handlers for existing legacy formats. Constants ========= .. data:: sys_bits Native bit size of host architecture (either 32 or 64 bit). used for various purposes internally. .. data:: unix_crypt_schemes List of the names of all the handlers in :mod:`passlib.hash` which are supported by the native :func:`crypt()` function of at least one OS. For all hashes in this list, the expression ``get_crypt_handler(name).has_backend("os_crypt")`` will return ``True`` iff there is native OS support for that hash. This list is used by :data:`~passlib.hosts.host_context` and :data:`~passlib.apps.ldap_context` to determine which hashes are supported by the host. See :ref:`mcf-identifiers` for a table of which OSes are known to support which hashes. .. autoexception:: MissingBackendError Decorators ========== .. autofunction:: classproperty String Manipulation =================== .. autofunction:: splitcomma Bytes Manipulation ================== .. autofunction:: bytes_to_int .. autofunction:: int_to_bytes .. autofunction:: xor_bytes Randomness ========== .. data:: rng The random number generator used by passlib to generate salt strings and other things which don't require a cryptographically strong source of randomness. If :func:`os.urandom` support is available, this will be an instance of :class:`!random.SystemRandom`, otherwise it will use the default python PRNG class, seeded from various sources at startup. .. autofunction:: getrandbytes .. autofunction:: getrandstr .. autofunction:: generate_password(size=10, charset=) Object Tests ============ .. autofunction:: is_crypt_handler .. autofunction:: is_crypt_context .. autofunction:: has_rounds_info .. autofunction:: has_salt_info Submodules ========== There are also a few sub modules which provide additional utility functions: .. toctree:: :maxdepth: 1 passlib.utils.des passlib.utils.h64 passlib.utils.md4 passlib.utils.pbkdf2 passlib.utils.handlers passlib-1.5.3/docs/lib/passlib.hash.ldap_crypt.rst0000644000175000017500000000463511643466373023277 0ustar biscuitbiscuit00000000000000=========================================================== :samp:`passlib.hash.ldap_{crypt}` - LDAP crypt() Wrappers =========================================================== .. currentmodule:: passlib.hash Passlib provides support for all the standard LDAP hash formats specified by :rfc:`2307`. One of these, identified by RFC 2307 as the ``{CRYPT}`` scheme, is somewhat different from the others. Instead of specifying a password hashing scheme, it's supposed to wrap the host OS's :func:`!crypt()`. Being host-dependant, the actual hashes supported by this scheme may differ greatly between host systems. In order to provide uniform support across platforms, Passlib defines a corresponding :samp:`ldap_{xxx}_crypt` scheme for each of the :ref:`standard unix hashes `. .. seealso:: * :doc:`passlib.hash.ldap_std` - the other standard LDAP hashes. * :mod:`!passlib.apps` for a :ref:`list of premade ldap contexts `. Usage ===== These classes all wrap the underlying implementations, and are mainly useful only for plugging them into a :class:`~passlib.context.CryptContext`. However, they can be used directly as follows:: >>> from passlib.hash import ldap_md5_crypt as lmc >>> #encrypt password >>> h = lmc.encrypt("password") >>> h '{CRYPT}$1$gwvn5BO0$3dyk8j.UTcsNUPrLMsU6/0' >>> lmc.identify(h) #check if hash is recognized True >>> lmc.identify('JQMuyS6H.AGMo') #check if some other hash is recognized False >>> lmc.verify("password", h) #verify correct password True >>> lmc.verify("secret", h) #verify incorrect password False Interface ========= .. class:: ldap_des_crypt() .. class:: ldap_bsdi_crypt() .. class:: ldap_md5_crypt() .. class:: ldap_bcrypt() .. class:: ldap_sha1_crypt() .. class:: ldap_sha256_crypt() .. class:: ldap_sha512_crypt() All of these classes have the same interface as their corresponding underlying hash (eg :class:`des_crypt`, :class:`md5_crypt`, etc). .. note:: In order to determine if a particular hash is actually supported natively by your host OS, use an test such as ``ldap_des_crypt.has_backend("os_crypt")`` or similar. .. rubric:: Footnotes .. [#pwd] The manpage for :command:`slappasswd` - ``_. .. [#rfc] The basic format for these hashes is laid out in RFC 2307 - ``_ passlib-1.5.3/docs/lib/passlib.hash.django_std.rst0000644000175000017500000001107411643466373023245 0ustar biscuitbiscuit00000000000000.. index:: django; hash formats ============================================================= :samp:`passlib.hash.django_{digest}` - Django-specific Hashes ============================================================= .. currentmodule:: passlib.hash The `Django `_ web framework provides a module for storing user accounts and passwords (:mod:`!django.contrib.auth`). This module's password hashing code supports a few simple salted digests, stored using the format :samp:`{id}${salt}${checksum}` (where :samp:`{id}` is an identifier assigned by Django). Passlib provides support for all the hashes used by Django: * :class:`django_salted_sha1` - simple salted SHA1 digest, currently Django's default. * :class:`django_salted_md5` - simple salted MD5 digest. * :class:`django_des_crypt` - support for legacy :class:`des_crypt` hashes, shoehorned into Django's hash format. * :class:`hex_md5` - historical format used by old Django versions. .. warning:: All of these hashes are suceptible to brute-force attacks; even the strongest of these (:class:`django_salted_sha1`) is a simple single-round salted digest. They should not be used for any purpose besides manipulating existing Django password hashes. .. seealso:: * :data:`passlib.apps.django_context` - a premade Django context which can read all of the formats listed below. * :mod:`passlib.ext.django` - a plugin that updates Django to use a stronger hashing scheme, and migrates existing hashes as users log in. Salted Hashes ============= Usage ----- These classes can be used directly as follows:: >>> from passlib.hash import django_salted_sha1 as dss >>> #encrypt password >>> h = dss.encrypt("password") >>> h 'sha1$c6218$161d1ac8ab38979c5a31cbaba4a67378e7e60845' >>> lms.identify(h) #check if hash is recognized True >>> lms.identify('JQMuyS6H.AGMo') #check if some other hash is recognized False >>> lms.verify("password", h) #verify correct password True >>> lms.verify("secret", h) #verify incorrect password False Interface --------- .. autoclass:: django_salted_md5() .. autoclass:: django_salted_sha1() Format ------ An example :class:`!django_salted_sha1` hash (of ``password``) is: ``sha1$f8793$c4cd18eb02375a037885706d414d68d521ca18c7`` Both of Django's salted hashes have the same basic format, :samp:`{ident}${salt}${checksum}`, where: * :samp:`{ident}` is an identifier (``sha1`` in the case of the example, ``md5`` for :class:`!django_salted_md5`). * :samp:`{salt}` consists of (usually 5) lowercase hexidecimal digits (``f8793`` in the example). * :samp:`{checksum}` is lowercase hexidecimal encoding of the checksum. The checksum is generated by concatenating the salt digits followed by the password, and hashing them using the specified digest (MD5 or SHA-1). The digest is then encoded to hexidecimal. If the password is unicode, it is converted to ``utf-8`` first. Security Issues --------------- Django's salted hashes should not be considered very secure. * They use only a single round of digests with known collision and pre-image attacks (SHA1 & MD5). * While it could be increased, they currently use only 20 bits of entropy in their salt, which is borderline insufficient to defeat rainbow tables. * They digest the encoded hexidecimal salt, not the raw bytes, increasing the odds that a particular salt+password string will be present in a pre-computed tables of ascii digests. Des Crypt ========= .. autoclass:: django_des_crypt() Format ------ An example :class:`!django_des_crypt` hash (of ``password``) is ``crypt$cd1a4$cdlRbNJGImptk``; the general format is the same as the salted hashes: :samp:`{ident}${salt}${checksum}`, where: * :samp:`{ident}` is the identifier ``crypt``. * :samp:`{salt}` is 5 lowercase hexidecimal digits (``cd1a4`` in the example). * :samp:`{checksum}` is a :class:`!des_crypt` hash (``cdlRbNJGImptk`` in the example). It should be noted that this class essentially just shoe-horns :class:`des_crypt` into a format compatible with the Django salted hashes (above). It has a few quirks, such as the fact that only the first two characters of the salt are used by :class:`!des_crypt`, and they are in turn duplicated as the first two characters of the checksum. For security issues relating to :class:`!django_des_crypt`, see :class:`des_crypt`. Other Hashes ============ .. autoclass:: django_disabled .. note:: Older versions of Django may also have passwords encoded using :class:`~passlib.hash.hex_md5`, though this has been deprecated by Django. passlib-1.5.3/docs/lib/passlib.hash.postgres_md5.rst0000644000175000017500000000542411643466373023546 0ustar biscuitbiscuit00000000000000.. index:: postgres; md5 hash ================================================================== :class:`passlib.hash.postgres_md5` - PostgreSQL MD5 password hash ================================================================== .. currentmodule:: passlib.hash This class implements the md5-based hash algorithm used by PostgreSQL to store it's user account passwords. This scheme was introduced in PostgreSQL 7.2; prior to this PostgreSQL stored it's password in plain text. .. warning:: This hash is not secure, and should not be used for any purposes besides manipulating existing PostgreSQL password hashes. Usage ===== Users will most likely find the frontend provided by :mod:`passlib.apps` to be more useful than accessing this class directly. That aside, this class can be used directly as follows:: >>> from passlib.hash import postgres_md5 as pm >>> #encrypt password using specified username >>> h = pm.encrypt("password", "username") >>> h 'md55a231fcdb710d73268c4f44283487ba2' >>> pm.identify(h) #check if hash is recognized True >>> pm.identify('$1$3azHgidD$SrJPt7B.9rekpmwJwtON31') #check if some other hash is recognized False >>> pm.verify("password", h, "username") #verify correct password True >>> pm.verify("password", h, "somebody") #verify correct password w/ wrong username False >>> pm.verify("password", h, "username") #verify incorrect password False Interface ========= .. autoclass:: postgres_md5() Format & Algorithm ================== Postgres-MD5 hashes all have the format :samp:`md5{checksum}`, where :samp:`{checksum}` is 32 hexidecimal digits, encoding a 128-bit checksum. This checksum is the MD5 message digest of the password concatenated with the username. Security Issues =============== This algorithm it not suitable for *any* use besides manipulating existing PostgreSQL account passwords, due to the following flaws: * It's use of the username as a salt value means that common usernames (eg ``admin``, ``root``, ``postgres``) will occur more frequently as salts, weakening the effectiveness of the salt in foiling pre-computed tables. * Since the keyspace of ``user+password`` is still a subset of ascii characters, existing MD5 lookup tables have an increased chance of being able to reverse common hashes. * It's simplicity makes high-speed brute force attacks much more feasible [#brute]_ . .. rubric:: Footnotes .. [#] Discussion leading up to design of algorithm - ``_ .. [#] Message explaining postgres md5 hash algorithm - ``_ .. [#brute] Blog post demonstrating brute-force attack ``_. passlib-1.5.3/docs/lib/passlib.hash.fshp.rst0000644000175000017500000001200611643466373022065 0ustar biscuitbiscuit00000000000000========================================================== :class:`passlib.hash.fshp` - Fairly Secure Hashed Password ========================================================== .. index:: fshp .. currentmodule:: passlib.hash The Fairly Secure Hashed Password (FSHP) scheme [#home]_ is a cross-platform hash based on PBKDF1 [#pbk]_, and uses an LDAP-style hash format. It features a variable length salt, variable rounds, and support for cryptographic hashes from SHA-1 up to SHA-512. .. warning:: While the SHA-2 variants of PBKDF1 have no critical security vulnerabilities, PBKDF1 itself has been deprecated in favor of it's successor, PBKDF2. Furthermore, FSHP has been listed as insecure by it's author (for unspecified reasons); so this scheme should probably only be used to support existing hashes. Usage ===== This class supports the standard passlib options for rounds and salt, as well as a special digest keyword for selecting the variant of FSHP to use. This class can be used directly as follows:: >>> from passlib.hash import fshp >>> #generate new salt, encrypt password >>> h = fshp.encrypt("password") >>> h '{FSHP1|16|16384}PtoqcGUetmVEy/uR8715TNqKa8+teMF9qZO1lA9lJNUm1EQBLPZ+qPRLeEPHqy6C' >>> #same, but with explict number of rounds, larger salt, and specific variant >>> fshp.encrypt("password", rounds=40000, salt_size=32, variant="sha512") '{FSHP3|32|40000}cB8yE/CuADSgUTQZjWy+YTf/cvbU11D/rHNKiUiB6z4dIaO77U/rmNWpgZcZllZbCra5GJ8ZfFRNwCHirPqvYTAnbaQQeFQbWym/frRrRev3buoygFQRYexl4091Pc5m' >>> #check if hash is recognized >>> fshp.identify(h) True >>> #check if some other hash is recognized >>> fshp.identify('$1$3azHgidD$SrJPt7B.9rekpmwJwtON31') False >>> #verify correct password >>> fshp.verify("password", h) True >>> fshp.verify("secret", h) #verify incorrect password False Interface ========= .. autoclass:: fshp() Format & Algorithm ================== All of this scheme's hashes have the format: ``{FSHP||}``. A example hash (of ``password``) is: ``{FSHP1|16|16384}PtoqcGUetmVEy/uR8715TNqKa8+teMF9qZO1lA9lJNUm1EQBLPZ+qPRLeEPHqy6C`` * :samp:`` is a decimal integer identifying the version of FSHP; in particular, which cryptographic hash function should be used to calculate the checksum. ``1`` in the example. (see the class description above for a list of possible values). * :samp:`` is a decimal integer identifying the number of bytes in the salt. ``16`` in the example. * :samp:`` is a decimal integer identifying the number of rounds to apply when calculating the checksum (see below). ``16384`` in the example. * :samp:`` is a base64-encoded string which, when decoded, contains a salt string of the specified size, followed by the checksum. In the example, the data portion decodes to a salt value (in hexdecimal octets) of: ``3eda2a70651eb66544cbfb91f3bd794c`` and a checksum value (in hexidecimal octets) of: ``da8a6bcfad78c17da993b5940f6524d526d444012cf67ea8f44b7843c7ab2e82`` FSHP is basically just a wrapper around PBKDF1: The checksum is calculated using :func:`~passlib.utils.pbkdf2.pbkdf1`, passing in the password, the decoded salt string, the number of rounds, and hash function specified by the variant identifier. FSHP has one quirk in that the password is passed in as the pbkdf1 salt, and the salt is passed in as the pbkdf1 password. Security Issues =============== * A minor issue is that FSHP swaps the location the password and salt from what is described in the PBKDF1 standard. This issue is mainly noted in order to dismiss it: while the swap permits an attacker to pre-calculate part of the initial digest, the impact of this is negligible when a large number of rounds is used. * Since PBKDF1 is based on repeated composition of a hash, it is vulnerable to any first-preimage attacks on the underlying hash. This has led to the deprecation of using SHA-1 or earlier hashes with PBKDF1. In contrast, it's successor PBKDF2 was designed to mitigate this weakness (among other things), and enjoys much stronger preimage resistance when used with the same cryptographic hashes. Deviations ========== * Unicode Policy: The official FSHP python implementation takes in a password specified as a series of bytes, and does not specify what encoding should be used; though a ``us-ascii`` compatible encoding is implied by the implementation, as well as all known reference hashes. In order to provide support for unicode strings, PassLib will encode unicode passwords using ``utf-8`` before running them through FSHP. If a different encoding is desired by an application, the password should be encoded before handing it to PassLib. .. rubric:: Footnotes .. [#home] The FSHP homepage contains implementations in a wide variety of programming languages -- ``_. .. [#pbk] rfc defining PBKDF1 & PBKDF2 - ``_ - passlib-1.5.3/docs/lib/passlib.hash.ldap_std.rst0000644000175000017500000001001411643466373022714 0ustar biscuitbiscuit00000000000000============================================================= :samp:`passlib.hash.ldap_{digest}` - RFC2307 Standard Digests ============================================================= .. currentmodule:: passlib.hash Passlib provides support for all the standard LDAP hash formats specified by :rfc:`2307`. This includes ``{MD5}``, ``{SMD5}``, ``{SHA}``, ``{SSHA}``. These schemes range from somewhat to very insecure, and should not be used except when required. .. note:: RFC 2307 also specifies a ``{CRYPT}`` scheme, which is supposed to wrap the host OS's :func:`!crypt()`. Being host-dependant, this scheme is somewhat different, and is details in :doc:`passlib.hash.ldap_crypt`. .. seealso:: * :doc:`passlib.hash.ldap_crypt` * :mod:`!passlib.apps` for a :ref:`list of premade ldap contexts `. Usage ===== These classes all wrap the underlying hashlib implementations, and are mainly useful only for plugging them into a :class:`~passlib.context.CryptContext`. However, they can be used directly as follows:: >>> from passlib.hash import ldap_salted_md5 as lsm >>> #encrypt password >>> h = lsm.encrypt("password") >>> h '{SMD5}OqsUXNHIhHbznxrqHoIM+ZT8DmE=' >>> lms.identify(h) #check if hash is recognized True >>> lms.identify('JQMuyS6H.AGMo') #check if some other hash is recognized False >>> lms.verify("password", h) #verify correct password True >>> lms.verify("secret", h) #verify incorrect password False Plain Hashes ============ .. warning:: These hashes should be considered secure in any manner, as they are nothing but raw MD5 & SHA-1 digests, which are extremely vulnerable to brute-force attacks. .. autoclass:: ldap_md5() .. autoclass:: ldap_sha1() Format ------ These hashes have the format :samp:`{prefix}{checksum}`. * :samp:`{prefix}` is `{MD5}` for ldap_md5, and `{SHA}` for ldap_sha1. * :samp:`{checksum}` is the base64 encoding of the raw message digest of the password, using the appropriate digest algorithm. An example ldap_md5 hash (of ``password``) is ``{MD5}X03MO1qnZdYdgyfeuILPmQ==``. An example ldap_sha1 hash (of ``password``) is ``{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=``. Salted Hashes ============= .. autoclass:: ldap_salted_md5() .. autoclass:: ldap_salted_sha1() These hashes have the format :samp:`{prefix}{data}`. * :samp:`{prefix}` is `{SMD5}` for ldap_salted_md5, and `{SSHA}` for ldap_salted_sha1. * :samp:`{data}` is the base64 encoding of :samp:`{checksum}{salt}`; and in turn :samp:`{salt}` is a 4 byte binary salt, and :samp:`{checksum}` is the raw digest of the the string :samp:`{password}{salt}`, using the appropriate digest algorithm. Format ------ An example hash (of ``password``) is ``{SMD5}jNoSMNY0cybfuBWiaGlFw3Mfi/U=``. After decoding, this results in a raw salt string ``s\x1f\x8b\xf5``, and a raw MD5 checksum of ``\x8c\xda\x120\xd64s&\xdf\xb8\x15\xa2hiE\xc3``. An example hash (of ``password``) is ``{SSHA}pKqkNr1tq3wtQqk+UcPyA3HnA2NsU5NJ``. After decoding, this results in a raw salt string ``lS\x93I``, and a raw SHA1 checksum of ``\xa4\xaa\xa46\xbdm\xab|-B\xa9>Q\xc3\xf2\x03q\xe7\x03c``. Security Issues --------------- The LDAP salted hashes should not be considered very secure. * They use only a single round of digests with known collision and pre-image attacks (SHA1 & MD5). * They currently use only 32 bits of entropy in their salt, which is only borderline sufficient to defeat rainbow tables, and cannot (portably) be increased. Plaintext ========= .. autoclass:: ldap_plaintext() This handler does not hash passwords at all, rather it encoded them into UTF-8. The only difference between this class and :class:`passlib.hash.plaintext` is that this class will NOT recognize any strings using the ``{SCHEME}HASH`` format. .. rubric:: Footnotes .. [#pwd] The manpage for :command:`slappasswd` - ``_. .. [#rfc] The basic format for these hashes is laid out in RFC 2307 - ``_ passlib-1.5.3/docs/lib/passlib.hash.crypt16.rst0000644000175000017500000001057011643466373022441 0ustar biscuitbiscuit00000000000000======================================================================= :class:`passlib.hash.crypt16` - Crypt16 ======================================================================= .. currentmodule:: passlib.hash This class implements the Crypt16 password hash, commonly found on Ultrix and Tru64. It's a minor modification of :class:`~passlib.hash.des_crypt`, which allows passwords of up to 16 characters. .. warning:: This algorithm is dangerously weak, and should not be used if at all possible. .. note:: This format is frequently confused with :class:`~passlib.hash.bigcrypt`, another derivative of des-crypt, because (for passwords between 9 and 16 chars in length) bigcrypt hashes will have the same size and character set. Usage ===== This class can be used in exactly the same manner as :class:`~passlib.hash.des_crypt`. Interface ========= .. autoclass:: crypt16(checksum=None, salt=None, strict=False) Format ====== An example hash (of the string ``passphrase``) is ``aaX/UmCcBrceQ0kQGGWKTbuE``. A crypt16 hash string has the format :samp:`{salt}{checksum_1}{checksum_2}`, where: * :samp:`{salt}` is the salt, stored as a 2 character :func:`hash64 `-encoded 12-bit integer (``aa`` in the example). * each :samp:`{checksum_i}` is a separate checksum, stored as an 11 character :func:`hash64 `-encoded 64-bit integer (``X/UmCcBrceQ`` and ``0kQGGWKTbuE`` in the example). .. rst-class:: html-toggle Algorithm ========= The crypt16 algorithm uses a weakened version of the des-crypt algorithm: 1. Given a password string and a salt string. 2. The 2 character salt string is decoded to a 12-bit integer salt value; The salt string uses little-endian :func:`hash64 ` encoding. 3. The password is NULL padded at the end or truncated to 16 bytes, as appropriate. 4. The lower 7 bits of the first 8 characters of the password are used to form a 56-bit integer; with the first character providing the most significant 7 bits, and the 8th character providing the least significant 7 bits. 5. 20 repeated rounds of modified DES encryption are performed; starting with a null input block, and using the 56-bit integer from step 4 as the DES key. The salt is used to to mutate the normal DES encrypt operation by swapping bits :samp:`{i}` and :samp:`{i}+24` in the DES E-Box output if and only if bit :samp:`{i}` is set in the salt value. 6. The 64-bit result of the last round of step 5 is then lsb-padded with 2 zero bits. 7. The resulting 66-bit integer is encoded in big-endian order using the :func:`hash 64 ` format. This is the first checksum segment. 8. The second checksum segment is created by repeating steps 4..7 using the second 8 bytes of the padding password (from step 3). The only difference is that step 5 uses only 5 rounds. 9. The final checksum string is the concatenation of the two checksum segments, in order. Security Issues =============== Crypt16 is dangerously flawed: * It suffers from all the flaws of :class:`~passlib.hash.des_crypt`. * Compared to des-crypt, it's smaller number of rounds makes it even *more* vulnerable to brute-force attacks. * For a given salt, passwords under 9 characters all have the same 2nd checksum. Given the 12-bit salt size, all such 2nd checksums can be easily pre-computed; making an attack easier, and giving away information about password size. * Since both checksums use the same salt, they can be attacked at once (by doing 5 rounds, checking the result against checksum 2, doing 15 rounds more, and checking the result against checksum 1). Deviations ========== This implementation of crypt16 deviates from public documentation of the format in one way: * Unicode Policy: The original crypt16 algorithm was designed for 7-bit ``us-ascii`` encoding only (as evidenced by the fact that it discards the 8th bit of all password bytes). In order to provide support for unicode strings, PassLib will encode unicode passwords using ``utf-8`` before running them through crypt16. If a different encoding is desired by an application, the password should be encoded before handing it to PassLib. .. rubric:: Footnotes .. [#] One source of information about bigcrypt & crypt16 - ``_ passlib-1.5.3/docs/lib/passlib.hash.hex_digests.rst0000644000175000017500000000352211643466373023436 0ustar biscuitbiscuit00000000000000============================================================== :samp:`passlib.hash.hex_{digest}` - Generic Hexdecimal Digests ============================================================== .. currentmodule:: passlib.hash Some existing applications store passwords by storing them using hexidecimal-encoded message digests, such as MD5 or SHA1. Such schemes are *extremely* vulnerable to pre-computed brute-force attacks, and should not be used in new applications. However, for the sake of backwards compatibility when converting existing applications, PassLib provides wrappers for few of the common hashes. .. warning:: To reiterate the above: Using a single round of any cryptographic hash (especially without a salt) is so insecure that it's barely better than plaintext. Do not use these schemes in new applications. Usage ===== These classes all wrap the underlying hashlib implementations, and are mainly useful only for plugging them into a :class:`~passlib.context.CryptContext`. However, they can be used directly as follows:: >>> from passlib.hash import hex_sha1 as hs >>> #encrypt password >>> h = hs.encrypt("password") >>> h '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8' >>> hs.identify(h) #check if hash is recognized True >>> hs.identify('JQMuyS6H.AGMo') #check if some other hash is recognized False >>> hs.verify("password", h) #verify correct password True >>> hs.verify("secret", h) #verify incorrect password False Interface ========= .. autoclass:: hex_md4() .. autoclass:: hex_md5() .. autoclass:: hex_sha1() .. autoclass:: hex_sha256() .. autoclass:: hex_sha512() Format & Algorithm ================== All of these classes just report the result of the specified digest, encoded as a series of lowercase hexidecimal characters; though upper case is accepted as input. passlib-1.5.3/docs/lib/passlib.hash.oracle10.rst0000644000175000017500000001141111643466373022532 0ustar biscuitbiscuit00000000000000================================================================== :class:`passlib.hash.oracle10` - Oracle 10g password hash ================================================================== .. currentmodule:: passlib.hash This class implements the hash algorithm used by the Oracle Database up to version 10g Rel.2. It was superceded by a newer algorithm in :class:`Oracle 11 `. .. warning:: This hash is not secure, and should not be used for any purposes besides manipulating existing Oracle 10 password hashes. .. warning:: This implementation has not been compared very carefully against the official implementation or reference documentation, and it's behavior may not match under various border cases. It should not be relied on for anything but novelty purposes for the time being. Usage ===== This class can be used directly as follows (note that this class requires a username for all encrypt/verify operations):: >>> from passlib.hash import oracle10 as or10 >>> #encrypt password using specified username >>> h = or10.encrypt("password", "username") >>> h '872805F3F4C83365' >>> or10.identify(h) #check if hash is recognized True >>> or10.identify('$1$3azHgidD$SrJPt7B.9rekpmwJwtON31') #check if some other hash is recognized False >>> or10.verify("password", h, "username") #verify correct password True >>> or10.verify("password", h, "somebody") #verify correct password w/ wrong username False >>> or10.verify("password", h, "username") #verify incorrect password False Interface ========= .. autoclass:: oracle10() .. rst-class:: html-toggle Format & Algorithm ================== Oracle10 hashes all consist of a series of 16 hexidecimal digits, representing the resulting checksum. Oracle10 hashes can be formed by the following procedure: 1. Concatenate the username and password together. 2. Convert the result to upper case 3. Encoding the result in a multi-byte format [#enc]_ such that ascii characters (eg: ``USER``) are represented with additional null bytes inserted (eg: ``\x00U\x00S\x00E\x00R``). 4. Right-pad the result with null bytes, to bring the total size to an integer multiple of 8. this is the final input string. 5. The input string is then encoded using DES in CBC mode. The string ``\x01\x23\x45\x67\x89\xAB\xCD\xEF`` is used as the DES key, and a block of null bytes is used as the CBC initialization vector. All but the last block of ciphertext is discarded. 6. The input string is then run through DES-CBC a second time; this time the last block of ciphertext from step 5 is used as the DES key, a block of null bytes is still used as the CBC initialization vector. All but the last block of ciphertext is discarded. 7. The last block of ciphertext of step 6 is converted to a hexdecimal string, and returned as the checksum. Security Issues =============== This algorithm it not suitable for *any* use besides manipulating existing Oracle10 account passwords, due to the following flaws [#flaws]_: * It's use of the username as a salt value means that common usernames (eg ``system``) will occur more frequently as salts, weakening the effectiveness of the salt in foiling pre-computed tables. * The fact that is it case insensitive, and simply concatenates the username and password, greatly reduces the keyspace for brute-force or pre-computed attacks. * It's simplicity makes high-speed brute force attacks much more feasible. Deviations ========== PassLib's implementation of the Oracle10g hash may deviate from the official implementation in unknown ways, as there is no official documentation. There is only one known issue: * Unicode Policy Lack of testing (and test vectors) leaves it unclear as to how Oracle 10g handles passwords containing non-7bit ascii. In order to provide support for unicode strings, PassLib will encode unicode passwords using ``utf-16-be`` [#enc]_ before running them through the Oracle10g algorithm. This behavior may be altered in the future, if further testing reveals another behavior is more in line with the official representation. This note applies as well to any provided username, as they are run through the same policy. .. rubric:: Footnotes .. [#enc] The exact encoding used in step 3 of the algorithm is not clear from known references. PassLib uses ``utf-16-be``, as this is both compatible with existing test vectors and supports unicode input. .. [#flaws] Whitepaper analyzing flaws in this algorithm - ``_. .. [#] Description of Oracle10g and Oracle11g algorithms - ``_. passlib-1.5.3/docs/lib/passlib.hash.sun_md5_crypt.rst0000644000175000017500000002120011643466373023714 0ustar biscuitbiscuit00000000000000.. index:: solaris; sun_md5_crypt ================================================================= :class:`passlib.hash.sun_md5_crypt` - Sun MD5 Crypt ================================================================= .. currentmodule:: passlib.hash This algorithm was developed by Alec Muffett [#mct]_ for Solaris, as a replacement for the aging :class:`~passlib.hash.des_crypt`. It was introduced in Solaris 9u2. While based on the MD5 message digest, it has very little at all in common with the :class:`~passlib.hash.md5_crypt` algorithm. It supports 32 bit variable rounds and an 8 character salt. .. warning:: The original Solaris implementation has some hash encoding quirks which may not be properly accounted for in Passlib. For now, this implementation should not be relied on for anything but novelty purposes. Usage ===== This class supports both rounds and salts, and so can be used in the exact same manner as :doc:`SHA-512 Crypt `. Interface ========= .. autoclass:: sun_md5_crypt(checksum=None, salt=None, rounds=None, bare_salt=False, strict=False) Format ====== An example hash (of ``passwd``) is ``$md5,rounds=5000$GUBv0xjJ$$mSwgIswdjlTY0YxV7HBVm0``. A sun-md5-crypt hash string has the format :samp:`$md5,rounds={rounds}${salt}$${checksum}`, where: * ``$md5,`` is the prefix used to identify the hash. * :samp:`{rounds}` is the decimal number of rounds to use (5000 in the example). * :samp:`{salt}` is 0-8 salt characters drawn from ``[./0-9A-Za-z]`` (``GUBv0xjJ`` in the example). * :samp:`{checksum}` is 22 characters drawn from the same set, encoding a 128-bit checksum (``mSwgIswdjlTY0YxV7HBVm0`` in the example). An alternate format, :samp:`$md5${salt}$${checksum}` is used when the rounds value is 0. There also exists some hashes which have only a single ``$`` between the salt and the checksum; these have a slightly different checksum calculation (see :ref:`smc-bare-salt` for details). .. note:: Solaris seems to deviate from the :ref:`modular-crypt-format` in that it considers ``,`` to indicate the end of the identifier in addition to ``$``. .. rst-class:: html-toggle Algorithm ========= The algorithm used is based around the MD5 message digest and the "Muffett Coin Toss" algorithm. 1. Given a password, the number of rounds, and a salt string. .. _smc-digest-step: 2. an initial MD5 digest is created from the concatentation of the password, and the configuration string (using the format :samp:`$md5,rounds={rounds}${salt}$`, or :samp:`$md5${salt}$` if rounds is 0). (See :ref:`smc-bare-salt` for details about an issue affecting this step) 3. for rounds+4096 iterations, a new digest is created: i. a buffer is initialized, containing the previous round's MD5 digest (for the first round, the digest from step 2 is used). ii. ``MuffetCoinToss(rounds, previous digest)`` is called, resulting in a 0 or 1. iii. If step 3.ii results in a 1, a constant data string is added to the buffer; if the result is a 0, the string is not added for this round. The constant data string is a 1517 byte excerpt from Hamlet [#f2]_ (``To be, or not to be...all my sins remember'd.\n``), including an appended null character. iv. the current iteration as a zero-indexed integer is converted to a string (not zero-padded) and added to the buffer. v. the output for this iteration is the MD5 digest of the buffer's contents. 4. The final digest is then encoded into :mod:`hash64 ` format using the same transposed byte order that :class:`~passlib.hash.md5_crypt` uses, and returned. Muffet Coin Toss ---------------- The Muffet Coin Toss algorithm is as follows: Given the current round number, and a 16 byte MD5 digest, it returns a 0 or 1, using the following formula: .. note:: All references below to a specific bit of the digest should be interpreted mod 128. All references below to a specific byte of the digest should be interpreted mod 16. 1. A 8-bit integer :samp:`{X}` is generated from the following formula: for each :samp:`{i}` in 0..7 inclusive: * let :samp:`{A}` be the :samp:`{i}`'th byte of the digest, as an 8-bit int. * let :samp:`{B}` be the :samp:`{i}+3`'rd byte of the digest, as an 8-bit int. * let :samp:`{R}` be :samp:`{A}` shifted right by :samp:`{B} % 5` bits. * let :samp:`{V}` be the :samp:`{R}`'th byte of the digest. * if the :samp:`{A} % 8`'th bit of :samp:`{B}` is 1, divide :samp:`{V}` by 2. * use the :samp:`{V}`'th bit of the digest as the :samp:`{i}`'th bit of :samp:`{X}`. 2. Another 8-bit integer, :samp:`{Y}`, is generated exactly the same manner as :samp:`{X}`, except that: * :samp:`{A}` is the :samp:`{i}+8`'th byte of the digest, * :samp:`{B}` is the :samp:`{i}+11`'th byte of the digest. 3. if bit :samp:`{round}` of the digest is 1, :samp:`{X}` is divided by 2. 4. if bit :samp:`{round}+64` of the digest is 1, :samp:`{Y}` is divided by 2. 5. the final result is :samp:`{X}`'th bit of the digest XORed against :samp:`{Y}`'th bit of the digest. .. _smc-bare-salt: Bare Salt Issue --------------- According to the only existing documentation of this algorithm [#mct]_, it's hashes were supposed to have the format :samp:`$md5${salt}${checksum}`, and include only the bare string :samp:`$md5${salt}` in the salt digest step (see :ref:`step 2 `, above). However, almost all hashes encountered in production environments have the format :samp:`$md5${salt}$${checksum}` (note the double ``$$``). Unfortunately, it is not merely a cosmetic difference: hashes of this format incorporate the first ``$`` after the salt within the salt digest step, so the resulting checksum is different. The documentation hints that this stems from a bug within the production implementation's parser. This bug causes the implementation to return ``$$``-format hashes when passed a configuration string that ends with ``$``. It returns the intended original format & checksum only if there is at least one letter after the ``$``, eg :samp:`$md5${salt}$x`. Passlib attempts to accomodate both formats using the special ``bare_salt`` keyword. It is set to ``True`` to indicate a configuration or hash string which contains only a single ``$``, and does not incorporate it into the hash calculation. The ``$$`` hash is encountered more often in production since it seems the Solaris salt generator always appends a ``$``; because of this ``bare_salt=False`` was chosen as the default, so that hashes will be generated which by default conform to what users are used to. Deviations ========== PassLib's implementation of Sun-MD5-Crypt deliberately deviates from the official implementation in the following ways: * Unicode Policy: The underlying algorithm takes in a password specified as a series of non-null bytes, and does not specify what encoding should be used; though a ``us-ascii`` compatible encoding is implied by all known reference hashes. In order to provide support for unicode strings, PassLib will encode unicode passwords using ``utf-8`` before running them through sun-md5-crypt. If a different encoding is desired by an application, the password should be encoded before handing it to PassLib. * Rounds encoding The underlying scheme implicitly allows rounds to have zero padding (eg ``$md5,rounds=001$abc$``), and also allows 0 rounds to be specified two ways (``$md5$abc$`` and ``$md5,rounds=0$abc$``). Allowing either of these would result in multiple possible checksums for the same password & salt. To prevent ambiguity, Passlib will throw a :exc:`ValueError` if the rounds value is zero-padded, or specified explicitly as 0 (eg ``$md5,rounds=0$abc$``). .. _smc-quirks: Given the lack of documentation, lack of test vectors, and known bugs which accompany the original Solaris implementation, Passlib may not accurately be able to generate and verify all hashes encountered in a Solaris environment. Issues of concern include: * Some hashes found on the web use a ``$`` in place of the ``,``. It is unclear whether this is an accepted alternate format or just a typo, nor whether this is supposed to affect the checksum in the resulting hash string. * The current implementation needs addition test vectors; especially ones which contain an explicitly specific number of rounds. * More information is needed about the parsing / formatting issue described in the :ref:`smc-bare-salt` section. .. rubric:: Footnotes .. [#mct] Overview of & motivations for the algorithm - ``_ .. [#f2] The source of Hamlet's speech, used byte-for-byte as the constant data - ``_ passlib-1.5.3/docs/lib/passlib.context-options.rst0000644000175000017500000002357311643466373023373 0ustar biscuitbiscuit00000000000000.. index:: CryptContext; constructor options .. _cryptcontext-options: ============================================= :mod:`passlib.context` - Constructor Options ============================================= .. currentmodule:: passlib.context The :class:`CryptContext` accepts a number of keyword options. These are divides into the "context options", which affect the context instance directly, and the "hash options", which affect the context treats a particular type of hash: .. seealso:: * :doc:`passlib.context-usage` * :doc:`passlib.context-interface` Context Options =============== The following keyword options are accepted by both the :class:`CryptContext` and :class:`CryptPolicy` constructors, and directly affect the behavior of the :class:`!CryptContext` instance itself: ``schemes`` List of handler names and/or instances which the CryptContext should recognize. This is usually required. For use in INI files, this may also be specified as a single comma-separated string of handler names. Potential names can include the name of any class importable from the :mod:`passlib.hash` module. For example, to specify the :class:`passlib.hash.sha256_crypt` and the :class:`passlib.hash.des_crypt` schemes should be supported for your new context:: >>> myctx = CryptContext(schemes=["sha256_crypt", "des_crypt"]) ``deprecated`` List of handler names which should be considered deprecated by the CryptContext. This should be a subset of the names of the handlers listed in schemes. This is optional, if not specified, no handlers will be considered deprecated. For use in INI files, this may also be specified as a single comma-separated string of handler names. This is primarily used by :meth:`CryptContext.hash_needs_update` and :meth:`CryptPolicy.handler_is_deprecated`. If the application does not use these methods, this option can be ignored. Example: ``deprecated=["des_crypt"]``. ``default`` Specifies the name of the default handler to use when encrypting a new password. If no default is specified, the first handler listed in ``schemes`` will be used. Any name specified *must* be in the list of supported schemes (see the ``schemes`` kwd). Example: ``default="sha256_crypt"``. ``min_verify_time`` If specified, all :meth:`CryptContext.verify` calls will take at least this many seconds. If set to an amount larger than the time used by the strongest hash in the system, this prevents an attacker from guessing the strength of particular hashes through timing measurements. Specified in integer or fractional seconds. Example: ``min_verify_time=0.1``. .. note:: For symmetry with the format of the hash option keywords (below), all of the above context option keywords may also be specified using the format :samp:`context__{option}` (note double underscores), or :samp:`context.{option}` within INI files. .. note:: To override context options for a particular :ref:`user category `, use the format :samp:`{category}__context__{option}`, or :samp:`{category}.context.{option}` within an INI file. Hash Options ============ The following keyword options are accepted by both the :class:`CryptContext` and :class:`CryptPolicy` constructors, and affect how a :class:`!CryptContext` instance treats hashes belonging to a particular hash scheme, as identified by the hash's handler name. All hash option keywords should be specified using the format :samp:`{hash}__{option}` (note double underscores); where :samp:`{hash}` is the name of the hash's handler, and :samp:`{option}` is the name of the specific options being set. Within INI files, this may be specified using the alternate format :samp:`{hash}.{option}`. :samp:`{hash}__default_rounds` Sets the default number of rounds to use when generating new hashes (via :meth:`CryptContext.encrypt`). If not set, this will use max rounds hash option (see below), or fall back to the algorithm-specified default. For hashes which do not support a rounds parameter, this option is ignored. :samp:`{hash}__vary_rounds` if specified along with :samp:`{hash}__default_rounds`, this will cause each new hash created by :meth:`CryptContext.encrypt` to have a rounds value random chosen from the range :samp:`{default_rounds} +/- {vary_rounds}`. this may be specified as an integer value, or as a string containing an integer with a percent suffix (eg: ``"10%"``). if specified as a percent, the amount varied will be calculated as a percentage of the :samp:`{default_rounds}` value. The default passlib policy sets this to ``"10%"``. .. note:: If this is specified as a percentage, and the hash algorithm uses a logarithmic rounds parameter, the amount varied will be calculated based on the effective number of linear rounds, not the actual rounds value. This allows ``vary_rounds`` to be given a default value for all hashes within a context, and behave sanely for both linear and logarithmic rounds parameters. :samp:`{hash}__min_rounds`, :samp:`{hash}__max_rounds` Place limits on the number of rounds allowed for a specific hash. ``min_rounds`` defaults to 0, ``max_rounds`` defaults to unlimited. When encrypting new passwords with the specified hash (via :meth:`CryptContext.encrypt`), the number of rounds will be clipped to these boundaries. When checking for out-of-date hashes (via :meth:`CryptContext.hash_needs_update`), it will flag any whose rounds are outside the range specified as needing to be re-encrypted. For hashes which do not support a rounds parameter, these options are ignored. .. note:: These are configurable per-context limits, they will be clipped by any hard limits set in the hash algorithm itself. :samp:`{hash}__{setting}` Any other option values, which match the name of a parameter listed in the hash algorithm's ``handler.setting_kwds`` attribute, will be passed directly to that hash whenever :meth:`CryptContext.encrypt` is called. For security purposes, ``salt`` is *forbidden* from being used in this way. If ``rounds`` is specified directly, it will override the entire min/max/default_rounds framework. .. note:: Default options which will be applied to all hashes within the context can be specified using the special hash name ``all``. For example, ``all__vary_rounds="10%"`` would set the ``vary_rounds`` option to ``"10%"`` for all hashes, unless it was overridden for a specific hash, such as by specifying ``sha256_crypt__vary_rounds="5%"``. This feature is generally only useful for the ``vary_rounds`` hash option. .. _user-categories: User Categories =============== CryptContext offers an optional feature of "user categories": User categories take the form of a string (eg: ``admin`` or ``guest``), passed to the CryptContext when one of it's methods is called. These may be set by an application to indicate the hash belongs to a user account which should be treated according to a slightly different set of configuration options from normal user accounts; this may involve requiring a stronger hash scheme, a larger number of rounds for that scheme, or just a longer verify time. If an application wishes to use this feature, it all that is needed is to prefix the name of any hash or context options with the name of the category string it wants to use, and add an additional separator to the keyword: :samp:`{category}__{hash}__{option}`` or ``{category}__context__{option}``. .. note:: For implementation & predictability purposes, the context option ``schemes`` cannot be overridden per-category, though all other options are allowed. In most cases, the need to use a different hash for a particular category can instead be acheived by overridden the ``default`` context option. Default Policy ============== PassLib defines a library-default policy, providing (hopefully) sensible defaults for new contexts. When a new CryptContext is created, a policy is generated from it's constructor arguments, which is then composited over the library-default policy. You may optionally override the default policy used by overriding the ``policy`` keyword of CryptContext. This default policy object may be imported as :data:`passlib.context.default_policy`, or viewed in the source code under ``$SOURCE/passlib/default.cfg``. Sample Policy File ================== A sample policy file: .. code-block:: ini [passlib] #configure what schemes the context supports (note the "context." prefix is implied for these keys) schemes = md5_crypt, sha512_crypt, bcrypt deprecated = md5_crypt default = sha512_crypt min_verify_time = 0.1 #set some common options for all schemes all.vary_rounds = 10%% ; NOTE the '%' above has to be escaped due to configparser interpolation #setup some hash-specific defaults sha512_crypt.min_rounds = 40000 bcrypt.min_rounds = 10 #create a "admin" category, which uses bcrypt by default, and has stronger hashes admin.context.default = bcrypt admin.sha512_crypt.min_rounds = 100000 admin.bcrypt.min_rounds = 13 And the equivalent as a set of python keyword options:: dict( #configure what schemes the context supports (note the "context." prefix is implied for these keys) schemes = ["md5_crypt", "sha512_crypt", "bcrypt" ], deprecated = ["md5_crypt"], default = "sha512_crypt", min_verify_time = 0.1, #set some common options for all schemes all__vary_rounds = "10%", #setup some hash-specific defaults sha512_crypt__min_rounds = 40000, bcrypt__min_rounds = 10, #create a "admin" category, which uses bcrypt by default, and has stronger hashes admin__context__default = bcrypt admin__sha512_crypt__min_rounds = 100000 admin__bcrypt__min_rounds = 13 ) passlib-1.5.3/docs/lib/passlib.hash.dlitz_pbkdf2_sha1.rst0000644000175000017500000000513311643466373024422 0ustar biscuitbiscuit00000000000000=========================================================================== :class:`passlib.hash.dlitz_pbkdf2_sha1` - Dwayne Litzenberger's PBKDF2 hash =========================================================================== .. index:: pbkdf2 hash; dlitz .. currentmodule:: passlib.hash This class provides an implementation of Dwayne Litzenberger's PBKDF2-HMAC-SHA1 hash format [#dlitz]_. PBKDF2 is a key derivation function [#pbkdf2]_ that is ideally suited as the basis for a password hash, as it provides variable length salts, variable number of rounds. .. seealso:: * :doc:`passlib.hash.pbkdf2_digest ` for some other PBKDF2-based hashes. * :doc:`passlib.hash.cta_pbkdf2_sha1 ` for another hash which looks almost exactly like this one. Usage ===== This class support both rounds and salts, and can be used in the exact same manner as :doc:`SHA-512 Crypt `. Interface ========= .. autoclass:: dlitz_pbkdf2_sha1() .. rst-class:: html-toggle Format & Algorithm ================== A example hash (of ``password``) is: ``$p5k2$2710$.pPqsEwHD7MiECU0$b8TQ5AMQemtlaSgegw5Je.JBE3QQhLbO``. All of this scheme's hashes have the format :samp:`$p5k2${rounds}${salt}${checksum}`, where: * ``$p5k2$`` is used as the :ref:`modular-crypt-format` identifier. * :samp:`{rounds}` is the number of PBKDF2 iterations to perform, stored as lowercase hexidecimal number with no zero-padding (in the example: ``2710`` or 10000 iterations). * :samp:`{salt}` is the salt string, which can be any number of characters, drawn from the :ref:`hash64 charset ` (``.pPqsEwHD7MiECU0`` in the example). * :samp:`{checksum}` is 32 characters, which encode the resulting 24-byte PBKDF2 derived key using :func:`~passlib.utils.adapted_b64_encode` (``b8TQ5AMQemtlaSgegw5Je.JBE3QQhLbO`` in the example). In order to generate the checksum, the password is first encoded into UTF-8 if it's unicode. Then, the entire configuration string (all of the hash except the checksum, ie :samp:`$p5k2${rounds}${salt}`) is used as the PBKDF2 salt. PBKDF2 is called using the encoded password, the full salt, the specified number of rounds, and using HMAC-SHA1 as it's psuedorandom function. 24 bytes of derived key are requested, and the resulting key is encoded and used as the checksum portion of the hash. .. rubric:: Footnotes .. [#dlitz] The reference for this hash format - ``_. .. [#pbkdf2] The specification for the PBKDF2 algorithm - ``_. passlib-1.5.3/docs/lib/passlib.hash.pbkdf2_digest.rst0000644000175000017500000001071111643466373023635 0ustar biscuitbiscuit00000000000000=============================================================== :samp:`passlib.hash.pbkdf2_{digest}` - Generic PBKDF2 Hashes =============================================================== .. index:: pbkdf2 hash; generic mcf .. currentmodule:: passlib.hash PassLib provides three custom hash schemes based on the PBKDF2 [#pbkdf2]_ algorithm which are compatible with the :ref:`modular crypt format `: :class:`!pbkdf2_sha1`, :class:`!pbkdf2_sha256`, :class:`!pbkdf2_sha512`. They feature variable length salts, variable rounds. Security-wise, PBKDF2 is currently one of the leading key derivation functions, and has no known security issues. Though the original PBKDF2 specification uses the SHA-1 message digest, it is not vulnerable to any of the known weaknesses of SHA-1 [#hmac-sha1]_, and can be safely used. However, for those still concerned, SHA-256 and SHA-512 versions are offered as well. PBKDF2-SHA512 is one of the three hashes Passlib :ref:`recommends ` for new applications. .. seealso:: Alternate version of these hashes - :doc:`LDAP-Compatible Simple PBKDF2 Hashes ` Usage ===== All of the following classes can be used directly as follows:: >>> from passlib.hash import pbkdf2_sha256 as engine >>> #generate new salt, encrypt password >>> hash = engine.encrypt("password") >>> hash '$pbkdf2-sha256$6400$0ZrzXitFSGltTQnBWOsdAw$Y11AchqV4b0sUisdZd0Xr97KWoymNE0LNNrnEgY4H9M' >>> #same, but with explicit number of rounds and salt length >>> engine.encrypt("password", rounds=8000, salt_size=10) '$pbkdf2-sha256$8000$XAuBMIYQQogxRg$tRRlz8hYn63B9LYiCd6PRo6FMiunY9ozmMMI3srxeRE' >>> #check if hash is a pbkdf2-sha256 hash >>> engine.identify(hash) True >>> #check if some other hash is recognized >>> engine.identify('$1$3azHgidD$SrJPt7B.9rekpmwJwtON31') False >>> #verify correct password >>> engine.verify("password", hash) True >>> #verify incorrect password >>> engine.verify("wrong", hash) False Interface ========= .. class:: pbkdf2_sha1() except for the choice of message digest, this class is the same as :class:`pbkdf2_sha512`. .. class:: pbkdf2_sha256() except for the choice of message digest, this class is the same as :class:`pbkdf2_sha512`. .. autoclass:: pbkdf2_sha512() .. _mcf-pbkdf2-format: Format & Algorithm ================== An example :class:`!pbkdf2_sha256` hash (of ``password``):: $pbkdf2-sha256$6400$.6UI/S.nXIk8jcbdHx3Fhg$98jZicV16ODfEsEZeYPGHU3kbrUrvUEXOPimVSQDD44 All of the pbkdf2 hashes defined by passlib follow the same format, :samp:`$pbkdf2-{digest}${rounds}${salt}${checksum}`. * :samp:`$pbkdf2-{digest}$`` is used as the :ref:`modular-crypt-format` identifier (``$pbkdf2-sha256$`` in the example). * :samp:`{digest}` - this specifies the particular cryptographic hash used in conjunction with HMAC to form PBKDF2's pseudorandom function for that particular hash (``sha256`` in the example). * :samp:`{rounds}` - the number of iterations that should be performed. this is encoded as a positive decimal number with no zero-padding (``6400`` in the example). * :samp:`{salt}` - this is the :func:`adapted base64 encoding ` of the raw salt bytes passed into the PBKDF2 function. * :samp:`{checksum}` - this is the :func:`adapted base64 encoding ` of the raw derived key bytes returned from the PBKDF2 function. Each scheme uses output size of it's specific :samp:`{digest}` as the size of the raw derived key. This is enlarged by appromixately 4/3 by the base64 encoding, resulting in a checksum size of 27, 43, and 86 for each of the respective algorithms. The algorithm used by all of these schemes is deliberately identical and simple: The password is encoded into UTF-8 if not already encoded, and passed through :func:`~passlib.utils.pbkdf2.pbkdf2` along with the decoded salt, the number of rounds, and a prf built from HMAC + the respective message digest. The result is then encoded using :func:`~passlib.utils.adapted_b64_encode`. .. rubric:: Footnotes .. [#pbkdf2] The specification for the PBKDF2 algorithm - ``_, part of :rfc:`2898`. .. [#hmac-sha1] While SHA1 has fallen to collision attacks, HMAC-SHA1 as used by PBKDF2 is still considered secure - ``_. passlib-1.5.3/docs/lib/passlib.hash.mysql323.rst0000644000175000017500000000471511643466373022532 0ustar biscuitbiscuit00000000000000.. index:: mysql; OLD_PASSWORD() ======================================================================== :class:`passlib.hash.mysql323` - MySQL 3.2.3 password hash ======================================================================== .. currentmodule:: passlib.hash This class implements the first of MySQL's password hash functions, used to store it's user account passwords. Introduced in MySQL 3.2.3 under the function ``PASSWORD()``, this function was renamed to ``OLD_PASSWORD()`` under MySQL 4.1, when a newer password hash algorithm was introduced (see :class:`~passlib.hash.mysql41`). .. warning:: This algorithm is extremely weak, and should not be used for any purposes besides manipulating existing Mysql 3.2.3-4.0 password hashes. .. seealso:: :mod:`!passlib.apps` for a list of predefined :ref:`mysql contexts `. Usage ===== Users will most likely find the frontends provided by :mod:`passlib.apps` to be more useful than accessing this class directly. That aside, this class can be used as follows:: >>> from passlib.hash import mysql323 as mold >>> mold.encrypt("password") #encrypt password '5d2e19393cc5ef67' >>> mold.identify('5d2e19393cc5ef67') #check if hash is recognized True >>> mold.identify('$1$3azHgidD$SrJPt7B.9rekpmwJwtON31') #check if another type of hash is recognized False >>> mold.verify("password", '5d2e19393cc5ef67') #verify correct password True >>> mold.verify("secret", '5d2e19393cc5ef67') #verify incorrect password False Interface ========= .. autoclass:: mysql323() Format & Algorithm ================== A mysql-323 password hash consists of 16 hexidecimal digits, directly encoding the 64 bit checksum. MySQL always uses lower-case letters, and so does PassLib (though PassLib will recognize upper case letters as well). The algorithm used is extremely simplistic, for details, see the source implementation in the footnotes [#f1]_. Security Issues =============== Lacking any sort of salt, ignoring all whitespace, and having a simplistic algorithm that amounts to little more than a checksum, this is not secure, and should not be used for *any* purpose but verifying existing MySQL 3.2.3 - 4.0 password hashes. .. rubric:: Footnotes .. [#f1] Source of implementation used by passlib - ``_ .. [#f2] Mysql document describing transition - ``_ passlib-1.5.3/docs/lib/passlib.ext.django.rst0000644000175000017500000001155611643466373022255 0ustar biscuitbiscuit00000000000000.. index:: django; password hashing app ================================================== :mod:`passlib.ext.django` - Django Password Helper ================================================== .. module:: passlib.ext.django .. warning:: This module is currently under development. It works and has good unittest coverage, but has not seen very much real-world use; *caveat emptor*. .. todo:: This documentation needs to be cleaned up significantly for new users. Overview ======== This module is intended for use with `Django `_-based web applications. It contains a Django app which allows you to override Django's builtin password hashing routine with any Passlib :doc:`CryptContext ` instance. By default, it comes configured to add support for :class:`~passlib.hash.sha512_crypt`, and will automatically upgrade all existing Django password hashes as your users log in. :doc:`SHA512-Crypt ` was chosen as the best choice for the average Django deployment: accelerated implementations are available on most stock Linux systems, as well as Google App Engine, and Passlib provides a pure-python fallback for all other platforms. Installation ============= Installation is simple: just add ``"passlib.ext.django"`` to Django's ``settings.INSTALLED_APPS``. This app will handle everything else. Once done, when this app is imported by Django, it will automatically monkeypatch :class:`!django.contrib.auth.models.User` to use a Passlib :class:`~passlib.context.CryptContext` instance in place of the normal Django password authentication routines. This provides hash migration, the ability to set stronger policies for superuser & staff passwords, and stronger password hashing schemes. Configuration ============= Once installed, you can set the following options in django ``settings.py``: ``PASSLIB_CONTEXT`` This may be one of a number of values: * The string ``"passlib-default"``, which will cause Passlib to replace Django's hash routines with a builtin policy that supports all existing django hashes; but as users log in, upgrades them all to :class:`~passlib.hash.pbkdf2_sha256`. It also supports stronger hashing for the superuser account. This is the default behavior if ``PASSLIB_CONTEXT`` is not set. The exact default policy used can be found in :data:`~passlib.ext.django.utils.DEFAULT_CTX`. * ``None``, in which case this app will do nothing when Django is loaded. * A multiline configuration string suitable for passing to :meth:`passlib.context.CryptPolicy.from_string`. It is *strongly* recommended to use a configuration which will support the existing Django hashes (see :data:`~passlib.ext.django.utils.STOCK_CTX`). ``PASSLIB_GET_CATEGORY`` By default, Passlib will invoke the specified context with a category string that's dependant on the User instance. superusers will be assigned to the ``superuser`` category, staff to the ``staff`` category, and all other accounts assigned to ``None``. This configuration option allows overriding that logic by specifying an alternate function with the call signature ``get_category(user) -> category|None``. .. seealso:: See :ref:`user-categories` for more details about the category system in Passlib. Utility Functions ================= .. module:: passlib.ext.django.utils Whether or not you install this application into Django, the following utility functions are available for overriding Django's password hashes: .. data:: DEFAULT_CTX This is a string containing the default hashing policy that will be used by this application if none is specified via ``settings.PASSLIB_CONTEXT``. It defaults to the following:: [passlib] schemes = sha512_crypt, django_salted_sha1, django_salted_md5, django_des_crypt, hex_md5, django_disabled default = sha512_crypt deprecated = django_salted_sha1, django_salted_md5, django_des_crypt, hex_md5 all__vary_rounds = 5%% sha512_crypt__default_rounds = 15000 staff__sha512_crypt__default_rounds = 25000 superuser__sha512_crypt__default_rounds = 35000 .. data:: STOCK_CTX This is a string containing the a hashing policy which should be exactly the same as Django's default behavior. It is mainly useful as a template for building off of when defining your own custom hashing policy via ``settings.PASSLIB_CONTEXT``. It defaults to the following:: [passlib] schemes = django_salted_sha1, django_salted_md5, django_des_crypt, hex_md5, django_disabled default = django_salted_sha1 deprecated = hex_md5 .. autofunction:: get_category .. autofunction:: set_django_password_context passlib-1.5.3/docs/lib/passlib.registry.rst0000644000175000017500000000447511643466373022066 0ustar biscuitbiscuit00000000000000=================================================== :mod:`passlib.registry` - Password Handler Registry =================================================== .. module:: passlib.registry :synopsis: registry for tracking password hash handlers. This module contains the code PassLib uses to track all password hash handlers that it knows about. While custom handlers can be used directly within an application, or even handed to a :class:`!CryptContext`; it is frequently useful to register them globally within a process and then refer to them by name. This module provides facilities for that, as well as programmatically querying passlib to detect what algorithms are available. Interface ========= .. autofunction:: get_crypt_handler .. autofunction:: list_crypt_handlers .. autofunction:: register_crypt_handler_path .. autofunction:: register_crypt_handler .. note:: All password hashes registered with passlib can be imported by name from the :mod:`passlib.hash` module. This is true not just of the built-in hashes, but for any hash registered with the registration functions in this module. Usage ===== Example showing how to use :func:`!registry_crypt_handler_path`:: >>> #register the location of a handler without loading it >>> from passlib.registry import register_crypt_handler_path >>> register_crypt_handler_path("myhash", "myapp.support.hashes") >>> #even before being loaded, it's name will show up as available >>> from passlib.registry import list_crypt_handlers >>> 'myhash' in list_crypt_handlers() True >>> 'myhash' in list_crypt_handlers(loaded_only=True) False >>> #when the name "myhash" is next referenced, >>> #the class "myhash" will be imported from the module "myapp.support.hashes" >>> from passlib.context import CryptContext >>> cc = CryptContext(schemes=["myhash"]) #<-- this will cause autoimport Example showing how to load a hash by name:: >>> from passlib.registry import get_crypt_handler >>> get_crypt_handler("sha512_crypt") >>> get_crypt_handler("missing_hash") KeyError: "no crypt handler found for algorithm: 'missing_hash'" >>> get_crypt_handler("missing_hash", None) None passlib-1.5.3/docs/lib/passlib.hash.plaintext.rst0000644000175000017500000000231611643466373023140 0ustar biscuitbiscuit00000000000000================================================================== :class:`passlib.hash.plaintext` - Plaintext ================================================================== .. currentmodule:: passlib.hash This class stores passwords in plaintext. This is, of course, ridiculously insecure; it is provided for backwards compatibility when migrating existing applications. *It should not be used* for any other purpose. .. seealso:: * :class:`passlib.hash.ldap_plaintext` is probably more appropriate to use in conjunction with other LDAP style hashes. Usage ===== This class is mainly useful only for plugging into a :class:`~passlib.context.CryptContext`. When used, it should always be the last scheme in the list, as it will recognize all hashes. It can be used directly as follows:: >>> from passlib.hash import plaintext as pt >>> #"encrypt" password >>> pt.encrypt("password") 'password' >>> nt.identify('password') #check if hash is recognized (all hashes are recognized) True >>> nt.verify("password", "password") #verify correct password True >>> nt.verify("secret", "password") #verify incorrect password False Interface ========= .. autoclass:: plaintext passlib-1.5.3/docs/lib/passlib.hash.bsdi_crypt.rst0000644000175000017500000001300511643753130023255 0ustar biscuitbiscuit00000000000000================================================================================= :class:`passlib.hash.bsdi_crypt` - BSDi Crypt ================================================================================= .. currentmodule:: passlib.hash This algorithm was developed by BSDi for their BSD/OS distribution. It's based on :class:`~passlib.hash.des_crypt`, and contains a larger salt and a variable number of rounds. This algorithm is also known as "Extended DES Crypt". This algorithm is weak by modern standards, and should not be used in new applications. Usage ===== This class can be used directly as follows:: >>> from passlib.hash import bsdi_crypt as bc >>> bc.encrypt("password") #generate new salt, encrypt password '_cD..Bf/46u7tr9IAJ6M' >>> bc.encrypt("password", rounds=10000) #same, but with explict number of rounds '_EQ0.amG/Pp5b0hIpggo' >>> bc.identify('_cD..Bf/46u7tr9IAJ6M') #check if hash is recognized True >>> bc.identify('$1$3azHgidD$SrJPt7B.9rekpmwJwtON31') #check if some other hash is recognized False >>> bc.verify("password", '_cD..Bf/46u7tr9IAJ6M') #verify correct password True >>> bc.verify("secret", '_cD..Bf/46u7tr9IAJ6M') #verify incorrect password False Interface ========= .. autoclass:: bsdi_crypt(checksum=None, salt=None, rounds=None, strict=False) Format ====== An example hash (of the string ``password``) is ``_EQ0.jzhSVeUyoSqLupI``. A bsdi_crypt hash string consists of a 21 character string of the form :samp:`_{rounds}{salt}{checksum}`. All characters except the underscore prefix are drawn from ``[./0-9A-Za-z]``. * ``_`` - the underscore is used to distinguish this scheme from others, such as des-crypt. * :samp:`{rounds}` is the number of rounds, stored as a 4 character :mod:`hash64 `-encoded 24-bit integer (``EQ0.`` in the example). * :samp:`{salt}` is the salt, stored as as a 4 character hash64-encoded 24-bit integer (``jzhS`` in the example). * :samp:`{checksum}` is the checksum, stored as an 11 character hash64-encoded 64-bit integer (``VeUyoSqLupI`` in the example). A bsdi_crypt configuration string is also accepted by this module; and has the same format as the hash string, but with the checksum portion omitted. .. rst-class:: html-toggle Algorithm ========= The checksum is formed by a modified version of the DES cipher in encrypt mode: 1. Given a password string, a salt string, and rounds string. 2. The 4 character rounds string is decoded to a 24-bit integer rounds value; The rounds string uses little-endian :func:`hash64 ` encoding. 3. The 4 character salt string is decoded to a 24-bit integer salt value; The salt string uses little-endian :func:`hash64 ` encoding. 4. The password is NULL-padded on the end to the smallest non-zero multiple of 8 bytes. 5. The lower 7 bits of the first 8 bytes of the password are used to form a 56-bit integer; with the first byte providing the most significant 7 bits, and the 8th byte providing the least significant 7 bits. This is the DES key. 6. For each additional block of 8 bytes in the padded password: a. The current DES key is encrypted using a single round of normal DES, with itself as the input block. b. Step 5 is repeated for the current 8-byte block, and xored against the existing DES key. 7. Repeated rounds of (modified) DES encryption are performed; starting with a null input block, and using the 56-bit integer from step 5/6 as the DES key. The salt is used to to mutate the normal DES encrypt operation by swapping bits :samp:`{i}` and :samp:`{i}+24` in the DES E-Box output if and only if bit :samp:`{i}` is set in the salt value. The number of rounds is controlled by the value decoded in step 2. 8. The 64-bit result of the last round of step 7 is then lsb-padded with 2 zero bits. 9. The resulting 66-bit integer is encoded in big-endian order using the :func:`hash 64 ` format. Security Issues =============== BSDi Crypt should not be considered sufficiently secure, for a number of reasons: * It's use of the DES stream cipher, which is vulnerable to practical pre-image attacks, and considered broken, as well as having too-small key and block sizes. * The 24-bit salt is too small to defeat rainbow-table attacks (most modern algorithms provide at least a 48-bit salt). * The fact that it only uses the lower 7 bits of each byte of the password restricts the keyspace which needs to be searched. .. note:: This algorithm is none-the-less stronger than des-crypt itself, since it supports variable rounds, a larger salt size, and uses all bytes of the password. Deviations ========== This implementation of bsdi-crypt differs from others in one way: * Unicode Policy: The original bsdi-crypt algorithm was designed for 7-bit ``us-ascii`` encoding only (as evidenced by the fact that it discards the 8th bit of all password bytes). In order to provide support for unicode strings, PassLib will encode unicode passwords using ``utf-8`` before running them through bsdi-crypt. If a different encoding is desired by an application, the password should be encoded before handing it to PassLib. .. rubric:: Footnotes .. [#] Primary source used for description of bsdi-crypt format & algorithm - ``_ .. [#] Another source describing algorithm - ``_ passlib-1.5.3/docs/lib/passlib.apps.rst0000644000175000017500000001507411643466373021156 0ustar biscuitbiscuit00000000000000================================================================== :mod:`passlib.apps` - Helpers for various applications ================================================================== .. module:: passlib.apps :synopsis: encrypting & verifying passwords used in sql servers and other applications This lists a number of :class:`!CryptContext` instances that are predefined by PassLib for easily handling the multiple formats used by various applications. (For details about how to use a :class:`!CryptContext` instance, see :doc:`passlib.context-usage`). .. _quickstart-custom-applications: Custom Applications =================== .. data:: custom_app_context This :class:`!CryptContext` object is provided for new python applications to quickly and easily add password hashing support. It offers: * Support for :class:`~passlib.hash.sha256_crypt` and :class:`~passlib.hash.sha512_crypt` * Defaults to SHA256-Crypt under 32 bit systems; SHA512-Crypt under 64 bit systems. * Comes pre-configured with strong rounds settings. For applications which want to quickly add a password hash, all they need to do is the following:: >>> #import the context under an app-specific name (so it can easily be replaced later) >>> from passlib.apps import custom_app_context as pwd_context >>> #encrypting a password... >>> hash = pwd_context.encrypt("somepass") >>> #verifying a password... >>> ok = pwd_context.verify("somepass", hash) >>> #[optional] encrypting a password for an admin account - uses stronger settings >>> hash = pwd_context.encrypt("somepass", category="admin") .. seealso:: The :doc:`/new_app_quickstart`. .. index:: django; crypt context Django ====== .. data:: django_context This object provides a pre-configured :class:`!CryptContext` instance for handling `Django `_ password hashes, as used by Django's ``django.contrib.auth`` module. It recognizes all the :doc:`builtin Django hashes `. It defaults to using the :class:`~passlib.hash.django_salted_sha1` hash. .. _ldap-contexts: LDAP ==== Passlib provides two contexts related to ldap hashes: .. data:: ldap_context This object provides a pre-configured :class:`!CryptContext` instance for handling LDAPv2 password hashes. It recognizes all the :ref:`standard ldap hashes `. It defaults to using the ``{SSHA}`` password hash. For times when there should be another default, using code such as the following:: >>> from passlib.apps import ldap_context >>> ldap_context = ldap_context.replace(default="ldap_salted_md5") >>> #the new context object will now default to {SMD5}: >>> ldap_context.encrypt("password") '{SMD5}T9f89F591P3fFh1jz/YtW4aWD5s=' .. data:: ldap_nocrypt_context This object recognizes all the standard ldap schemes that :data:`!ldap_context` does, *except* for the ``{CRYPT}``-based schemes. .. index:: mysql; crypt context .. _mysql-contexts: MySQL ===== This module provides two pre-configured :class:`!CryptContext` instances for handling MySQL user passwords: .. data:: mysql_context This object should recognize the new :class:`~passlib.hash.mysql41` hashes, as well as any legacy :class:`~passlib.hash.mysql323` hashes. It defaults to mysql41 when generating new hashes. This should be used with MySQL version 4.1 and newer. .. data:: mysql3_context This object is for use with older MySQL deploys which only recognize the :class:`~passlib.hash.mysql323` hash. This should be used only with MySQL version 3.2.3 - 4.0. .. index:: drupal; crypt context, wordpress; crypt context, phpbb3; crypt context, phpass; crypt context PHPass ====== `PHPass `_ is a PHP password hashing library, and hashes derived from it are found in a number of PHP applications. It is found in a wide range of PHP applications, including Drupal and Wordpress. .. data:: phpass_context This object following the standard PHPass logic: it supports :class:`~passlib.hash.bcrypt`, :class:`~passlib.hash.bsdi_crypt`, and implements an custom scheme called the "phpass portable hash" :class:`~passlib.hash.phpass` as a fallback. BCrypt is used as the default if support is available, otherwise the Portable Hash will be used as the default. .. versionchanged:: 1.5 Now uses Portable Hash as fallback if BCrypt isn't available. Previously used BSDI-Crypt as fallback (per original PHPass implementation), but it was decided PHPass is in fact more secure. .. data:: phpbb3_context This object supports phpbb3 password hashes, which use a variant of :class:`~passlib.hash.phpass`. .. index:: postgres; crypt context PostgreSQL ========== .. data:: postgres_context This object should recognize password hashes stores in PostgreSQL's ``pg_shadow`` table; which are all assumed to follow the :class:`~passlib.hash.postgres_md5` format. Note that the username must be provided whenever encrypting or verifying a postgres hash:: >>> from passlib.apps import postgres_context >>> #encrypting a password... >>> postgres_context.encrypt("somepass", user="dbadmin") 'md578ed0f0ab2be0386645c1b74282917e7' >>> #verifying a password... >>> postgres_context.verify("somepass", 'md578ed0f0ab2be0386645c1b74282917e7', user="dbadmin") True >>> postgres_context.verify("wrongpass", 'md578ed0f0ab2be0386645c1b74282917e7', user="dbadmin") False .. index:: roundup; crypt context Roundup ======= The `Roundup Issue Tracker `_ has long supported a series of different methods for encoding passwords. The following contexts are available for reading Roundup password hash fields: .. data:: roundup10_context This object should recognize all password hashes used by Roundup 1.4.16 and earlier: :class:`~passlib.hash.ldap_hex_sha1` (the default), :class:`~passlib.hash.ldap_hex_md5`, :class:`~passlib.hash.ldap_des_crypt`, and :class:`~passlib.hash.roundup_plaintext`. .. data:: roundup15_context Roundup 1.4.17 adds support for :class:`~passlib.hash.ldap_pbkdf2_sha1` as it's preferred hash format. This context supports all the :data:`roundup10_context` hashes, but adds that hash as well (and uses it as the default). .. data:: roundup_context this is an alias for the latest version-specific roundup context supported by passlib, currently the :data:`!roundup15_context`. passlib-1.5.3/docs/lib/passlib.hash.nthash.rst0000644000175000017500000000410511643466373022413 0ustar biscuitbiscuit00000000000000================================================================== :class:`passlib.hash.nthash` - Windows NT-HASH for Unix ================================================================== .. currentmodule:: passlib.hash This class implements the Windows NT-HASH algorithm, encoded in a manner compatible with the :ref:`modular-crypt-format`. It is found on some unix systems where the administrator has decided to store user passwords in a manner compatible with the SMB/CIFS protocol. .. warning:: This scheme is notoriously weak (since it's based on :mod:`~passlib.utils.md4`). Online tables exist for quickly performing pre-image attacks on this scheme. **Do not use** in new code. Stop using in old code if possible. Usage ===== This class can be used directly as follows:: >>> from passlib.hash import nthash as nt >>> #encrypt password >>> h = nt.encrypt("password") >>> h '$3$$8846f7eaee8fb117ad06bdd830b7586c' >>> nt.identify(h) #check if hash is recognized True >>> nt.identify('$1$3azHgidD$SrJPt7B.9rekpmwJwtON31') #check if some other hash is recognized False >>> nt.verify("password", h) #verify correct password True >>> nt.verify("secret", h) #verify incorrect password False Interface ========= .. autoclass:: nthash In addition to the standard methods, this class exposes the following: .. staticmethod:: passlib.hash.nthash.raw_nthash(secret, hex=False) perform raw nthash calculation, returning either raw digest, or as lower-case hexidecimal characters. Format & Algorithm ================== A nthash encoded for crypt consists of :samp:`$3$${checksum}` or :samp:`$NT${checksum}`; where :samp:`{checksum}` is 32 hexidecimal digits encoding the checksum. An example hash (of ``password``) is ``$3$$8846f7eaee8fb117ad06bdd830b7586c``. The checksum is simply the :mod:`~passlib.utils.md4` digest of the secret using the ``UTF16-LE`` encoding, encoded in hexidecimal. Security Issues =============== This algorithm should be considered *completely* broken: rainbow tables exist for quickly reversing this hash. passlib-1.5.3/docs/lib/passlib.hash.apr_md5_crypt.rst0000644000175000017500000000274411643466373023705 0ustar biscuitbiscuit00000000000000.. index:: apache; md5 password hash ====================================================================== :class:`passlib.hash.apr_md5_crypt` - Apache's MD5-Crypt variant ====================================================================== .. currentmodule:: passlib.hash This format is a variation of :class:`~passlib.hash.md5_crypt`, primarily used by the Apache webserver in ``htpasswd`` files. It contains only minor changes to md5-crypt, and should be considered just as strong / weak as md5-crypt itself. .. seealso:: :doc:`md5_crypt `, :mod:`passlib.apache` Usage ===== This algorithm can be used in exactly the same way as :class:`~passlib.hash.md5_crypt`, see that class for details. Interface ========= .. autoclass:: apr_md5_crypt(checksum=None, salt=None, strict=False) Format & Algorithm ================== This format and algorithm of Apache's MD5-Crypt is identical to the original MD5-Crypt, except for two changes: 1. The encoded string uses ``$apr1$`` as it's prefix, while md5-crypt uses ``$1$``. 2. The algorithm uses ``$apr1$`` as a constant in the step where md5-crypt uses ``$1$`` in it's calculation of digest B (see the :ref:`md5-crypt algorithm `). Because of this change, even raw checksums generated by apr-md5-crypt and md5-crypt are not compatible with eachother. .. rubric:: Footnotes .. [#] Apache's description of Apr-MD5-Crypt - ``_ passlib-1.5.3/docs/lib/passlib.hash.oracle11.rst0000644000175000017500000000601711643466373022541 0ustar biscuitbiscuit00000000000000================================================================== :class:`passlib.hash.oracle11` - Oracle 11g password hash ================================================================== .. currentmodule:: passlib.hash This class implements the hash algorithm introduced in version 11g of the Oracle Database. It supercedes the :class:`Oracle 10 ` password hash. .. warning:: This implementation has not been compared very carefully against the official implementation or reference documentation, and it's behavior may not match under various border cases. It should not be relied on for anything but novelty purposes for the time being. Usage ===== PassLib provides an oracle11 class, which can be can be used directly as follows:: >>> from passlib.hash import oracle11 as or11 >>> #generate new salt, encrypt password >>> h = or11.encrypt("password") >>> h 'S:4143053633E59B4992A8EA17D2FF542C9EDEB335C886EED9C80450C1B4E6' >>> or11.identify(h) #check if hash is recognized True >>> or11.identify('JQMuyS6H.AGMo') #check if some other hash is recognized False >>> or11.verify("password", h) #verify correct password True >>> or11.verify("secret", h) #verify incorrect password False Interface ========= .. autoclass:: oracle11(checksum=None, salt=None, strict=False) Format & Algorithm ================== An example oracle11 hash (of the string ``password``) is: ``S:4143053633E59B4992A8EA17D2FF542C9EDEB335C886EED9C80450C1B4E6`` An oracle11 hash string has the format :samp:`S:{checksum}{salt}`, where: * ``S:`` is the prefix used to identify oracle11 hashes (as distinct from oracle10 hashes, which have no constant prefix). * :samp:`{checksum}` is 40 hexidecimal characters; encoding a 160-bit checksum. (``4143053633E59B4992A8EA17D2FF542C9EDEB335`` in the example) * :samp:`{salt}` is 20 hexidecimal characters; providing a 80-bit salt (``C886EED9C80450C1B4E6`` in the example). The Oracle 11 hash has a very simple algorithm: The salt is decoded from it's hexidecimal representation into binary, and the SHA-1 digest of :samp:`{password}{raw_salt}` is then encoded into hexidecimal, and returned as the checksum. Deviations ========== PassLib's implementation of the Oracle11g hash may deviate from the official implementation in unknown ways, as there is no official documentation. There is only one known issue: * Unicode Policy Lack of testing (and test vectors) leaves it unclear as to how Oracle 11g handles passwords containing non-7bit ascii. In order to provide support for unicode strings, PassLib will encode unicode passwords using ``utf-8`` before running them through Oracle11. This behavior may be altered in the future, if further testing reveals another behavior is more in line with the official representation. .. rubric:: Footnotes .. [#] Description of Oracle10g and Oracle11g algorithms - ``_. passlib-1.5.3/docs/lib/passlib.hash.rst0000644000175000017500000001277011643466373021136 0ustar biscuitbiscuit00000000000000============================================== :mod:`passlib.hash` - Password Hashing Schemes ============================================== .. module:: passlib.hash :synopsis: all password hashes provided by PassLib Overview ======== The :mod:`!passlib.hash` module contains all the password hashes built into Passlib. Each object within this package implements a different password hashing scheme, but all have the same uniform interface. The hashes in this module can used in two ways: They can be imported and used directly, as in the following example:: >>> from passlib.hash import md5_crypt >>> md5_crypt.encrypt("password") '$1$IU54yC7Y$nI1wF8ltcRvaRHwMIjiJq1' More commonly, they can be referenced by name when constructing a custom :doc:`CryptContext ` object, as in the following example:: >>> from passlib.context import CryptContext >>> #note below that md5_crypt and des_crypt are both names of classes in passlib.hash >>> pwd_context = CryptContext(["md5_crypt", "des_crypt"]) >>> pwd_context.encrypt("password") '$1$2y72Yi12$o6Yu2OyjN.9FiK.9HJ7i5.' .. seealso:: * :ref:`password-hash-api` -- details the interface used by all password hashes in this module. * :doc:`Quickstart Guide
` -- for advice on choosing an appropriately secure hash for your new application. .. _mcf-hashes: Unix & "Modular Crypt" Hashes ============================= Aside from the "archaic" schemes below, most modern Unix flavors use password hashes which follow the :ref:`modular crypt format `, allowing them to be easily distinguished when used within the same file. The basic format :samp:`${scheme}${hash}` has also been adopted for use by other applications and password hash schemes. .. _archaic-unix-schemes: Archaic Unix Schemes -------------------- All of the following hashes are/were used by various Unix flavors to store user passwords; most are based on the DES block cipher, and predate the arrival of the modular crypt format. They should all be considered insecure at best, but may be useful when reading legacy password entries: .. toctree:: :maxdepth: 1 passlib.hash.des_crypt passlib.hash.bsdi_crypt passlib.hash.bigcrypt passlib.hash.crypt16 .. _standard-unix-hashes: Standard Unix Schemes --------------------- All these schemes are currently used by various Unix flavors to store user passwords. They all follow the modular crypt format. .. toctree:: :maxdepth: 1 passlib.hash.md5_crypt passlib.hash.bcrypt passlib.hash.sha1_crypt passlib.hash.sun_md5_crypt passlib.hash.sha256_crypt passlib.hash.sha512_crypt Other Modular Crypt Schemes --------------------------- While most of these schemes are not (commonly) used by any Unix flavor to store user passwords, they can be used compatibly along side other modular crypt format hashes. .. toctree:: :maxdepth: 1 passlib.hash.apr_md5_crypt passlib.hash.phpass passlib.hash.nthash passlib.hash.pbkdf2_digest passlib.hash.cta_pbkdf2_sha1 passlib.hash.dlitz_pbkdf2_sha1 Special note should be made of the fallback helper, which is not an actual hash scheme, but provides "disabled account" behavior found in many Linux & BSD password files: .. toctree:: :maxdepth: 1 passlib.hash.unix_fallback .. _ldap-hashes: LDAP / RFC2307 Hashes ===================== All of the following hashes use a variant of the password hash format used by LDAPv2. Originally specified in :rfc:`2307` and used by OpenLDAP [#openldap]_, the basic format ``{SCHEME}HASH`` has seen widespread adoption in a number of programs. .. _standard-ldap-hashes: Standard LDAP Schemes --------------------- .. toctree:: :hidden: passlib.hash.ldap_std The following schemes are explicitly defined by RFC 2307, and are supported by OpenLDAP. * :class:`passlib.hash.ldap_md5` - MD5 digest * :class:`passlib.hash.ldap_sha1` - SHA1 digest * :class:`passlib.hash.ldap_salted_md5` - salted MD5 digest * :class:`passlib.hash.ldap_salted_sha1` - salted SHA1 digest .. toctree:: :maxdepth: 1 passlib.hash.ldap_crypt * :class:`passlib.hash.ldap_plaintext` - LDAP-Aware Plaintext Handler Non-Standard LDAP Schemes ------------------------- None of the following schemes are actually used by LDAP, but follow the LDAP format: .. toctree:: :hidden: passlib.hash.ldap_other * :class:`passlib.hash.ldap_hex_md5` - Hex-encoded MD5 Digest * :class:`passlib.hash.ldap_hex_sha1` - Hex-encoded SHA1 Digest .. toctree:: :maxdepth: 1 passlib.hash.ldap_pbkdf2_digest passlib.hash.atlassian_pbkdf2_sha1 passlib.hash.fshp * :class:`passlib.hash.roundup_plaintext` - Roundup-specific LDAP Plaintext Handler .. _database-hashes: Database Hashes =============== The following schemes are used by various SQL databases to encode their own user accounts. These schemes have encoding and contextual requirements not seen outside those specific contexts: .. toctree:: :maxdepth: 1 passlib.hash.mysql323 passlib.hash.mysql41 passlib.hash.postgres_md5 passlib.hash.oracle10 passlib.hash.oracle11 .. _other-hashes: Other Hashes ============ The following schemes are used in various contexts, but have formats or uses which cannot be easily placed in one of the above categories: .. toctree:: :maxdepth: 1 passlib.hash.django_std passlib.hash.grub_pbkdf2_sha512 passlib.hash.hex_digests passlib.hash.plaintext .. rubric:: Footnotes .. [#openldap] OpenLDAP homepage - ``_. passlib-1.5.3/docs/lib/passlib.hash.sha512_crypt.rst0000644000175000017500000001073311643466373023356 0ustar biscuitbiscuit00000000000000=================================================================== :class:`passlib.hash.sha512_crypt` - SHA-512 Crypt =================================================================== .. currentmodule:: passlib.hash SHA-512 Crypt and SHA-256 Crypt were developed in 2008 by Ulrich Drepper [#f1]_ as a successor to :class:`~passlib.hash.md5_crypt`. They include fixes and advancements such as variable rounds, and use of NIST-approved cryptographic primitives. SHA-256 / SHA-512 Crypt are currently the default password hash for many systems (notably Linux), and have no known weaknesses. SHA-512 Crypt is one of the three hashes Passlib :ref:`recommends ` for new applications. Usage ===== This class can be used directly as follows:: >>> from passlib.hash import sha512_crypt as sc >>> #generate new salt, encrypt password >>> h = sc.encrypt("password") >>> h '$6$rounds=40000$xCsOXRqPPk5AGDFu$o5eyqxEoOSq0dLRFbPxEHp5Jc1vFVj47BNT.h9gmjSHXDS15mjIM.GSUaT5r6Z.Xa1Akrv4FAgKJE3EfbkJxs1' >>> #same, but with explict number of rounds >>> sc.encrypt("password", rounds=10000) '$6$rounds=10000$QWT8AlDMYRms7vSx$.1267Pg6Opn9CblFndtBJ2Q0AI0fcI2IX93zX3gi1Qse./j.VlKYX59NIUlbs0A66wCbfu/vra9wMv2uwTZAI.' >>> #check if hash is recognized >>> sc.identify(h) True >>> #check if some other hash is recognized >>> sc.identify('$1$3azHgidD$SrJPt7B.9rekpmwJwtON31') False >>> #verify correct password >>> sc.verify("password", h) True >>> sc.verify("secret", h) #verify incorrect password False Interface ========= .. autoclass:: sha512_crypt(checksum=None, salt=None, rounds=None, strict=False) Format & Algorithm ================== An example sha512-crypt hash (of the string ``password``) is: ``$6$rounds=40000$JvTuqzqw9bQ8iBl6$SxklIkW4gz00LvuOsKRCfNEllLciOqY/FSAwODHon45YTJEozmy.QAWiyVpuiq7XMTUMWbIWWEuQytdHkigcN/``. An sha512-crypt hash string has the format :samp:`$6$rounds={rounds}${salt}${checksum}`, where: * ``$6$`` is the prefix used to identify sha512-crypt hashes, following the :ref:`modular-crypt-format` * :samp:`{rounds}` is the decimal number of rounds to use (40000 in the example). * :samp:`{salt}` is 0-16 characters drawn from ``[./0-9A-Za-z]``, providing a 96-bit salt (``JvTuqzqw9bQ8iBl6`` in the example). * :samp:`{checksum}` is 86 characters drawn from the same set, encoding a 512-bit checksum. (``SxklIkW4gz00LvuOsKRCfNEllLciOqY/FSAwODHon45YTJEozmy.QAWiyVpuiq7XMTUMWbIWWEuQytdHkigcN/`` in the example). There is also an alternate format :samp:`$6${salt}${checksum}`, which can be used when the rounds parameter is equal to 5000 (see the ``implicit_rounds`` parameter above). The algorithm used by SHA512-Crypt is laid out in detail in the specification document linked to below [#f1]_. Deviations ========== This implementation of sha512-crypt differs from the specification, and other implementations, in a few ways: * Zero-Padded Rounds: The specification does not specify how to deal with zero-padding within the rounds portion of the hash. No existing examples or test vectors have zero padding, and allowing it would result in multiple encodings for the same configuration / hash. To prevent this situation, PassLib will throw an error if the rounds parameter in a hash has leading zeros. * Restricted salt string character set: The underlying algorithm can unambigously handle salt strings which contain any possible byte value besides ``\x00`` and ``$``. However, PassLib strictly limits salts to the :mod:`hash 64 ` character set, as nearly all implementations of sha512-crypt generate and expect salts containing those characters, but may have unexpected behaviors for other character values. * Unicode Policy: The underlying algorithm takes in a password specified as a series of non-null bytes, and does not specify what encoding should be used; though a ``us-ascii`` compatible encoding is implied by nearly all implementations of sha512-crypt as well as all known reference hashes. In order to provide support for unicode strings, PassLib will encode unicode passwords using ``utf-8`` before running them through sha512-crypt. If a different encoding is desired by an application, the password should be encoded before handing it to PassLib. .. rubric:: Footnotes .. [#f1] Ulrich Drepper's SHA-256/512-Crypt specification, reference implementation, and test vectors - `sha-crypt specification `_ passlib-1.5.3/docs/lib/passlib.utils.md4.rst0000644000175000017500000000173311643466373022033 0ustar biscuitbiscuit00000000000000======================================================== :mod:`passlib.utils.md4` - MD4 message digest algorithm ======================================================== .. module:: passlib.utils.md4 :synopsis: MD4 message digest algorithm .. warning:: This digest is considered **VERY INSECURE**, and not suitable for any new cryptographic activities. Trivial-cost real-world attacks exist for all password algorithms, stream ciphers, etc, that have been based on MD4. Do not use this hash or derived schemes unless you *really* have to. This module implements the MD4 hash algorithm in pure python, based on the `rfc 1320 `_ specification of MD4. .. autoclass:: md4 .. note:: If MD4 support is detected in :mod:`!hashlib`, the :class:`!md4` class in this module will be replaced by a function wrapping :mod:`!hashlib`'s implementation, which should be faster, but otherwise behave exactly the same. passlib-1.5.3/docs/lib/passlib.hash.md5_crypt.rst0000644000175000017500000001524711643466373023045 0ustar biscuitbiscuit00000000000000================================================================== :class:`passlib.hash.md5_crypt` - MD5 Crypt ================================================================== .. currentmodule:: passlib.hash This algorithm was developed for FreeBSD in 1994 by Poul-Henning Kamp, to replace the aging :class:`passlib.hash.des_crypt`. It has since been adopted by a wide variety of other Unix flavors, and is found in many other contexts as well. Due to it's origins, it's sometimes referred to as "FreeBSD MD5 Crypt". Security-wise it is considered to be steadily weakening (due to fixed cost), and most unix flavors have since replaced with with stronger schemes, such as :class:`~passlib.hash.sha512_crypt` and :class:`~passlib.hash.bcrypt`. Usage ===== PassLib provides an md5_crypt class, which can be can be used directly as follows:: >>> from passlib.hash import md5_crypt as mc >>> mc.encrypt("password") #generate new salt, encrypt password '$1$3azHgidD$SrJPt7B.9rekpmwJwtON31' >>> mc.identify('$1$3azHgidD$SrJPt7B.9rekpmwJwtON31') #check if hash is recognized True >>> mc.identify('JQMuyS6H.AGMo') #check if some other hash is recognized False >>> mc.verify("password", '$1$3azHgidD$SrJPt7B.9rekpmwJwtON31') #verify correct password True >>> mc.verify("secret", '$1$3azHgidD$SrJPt7B.9rekpmwJwtON31') #verify incorrect password False Interface ========= .. autoclass:: md5_crypt(checksum=None, salt=None, strict=False) Format ====== An example md5-crypt hash (of the string ``password``) is ``$1$5pZSV9va$azfrPr6af3Fc7dLblQXVa0``. An md5-crypt hash string has the format :samp:`$1${salt}${checksum}`, where: * ``$1$`` is the prefix used to identify md5-crypt hashes, following the :ref:`modular-crypt-format` * :samp:`{salt}` is 0-8 characters drawn from the regexp range ``[./0-9A-Za-z]``; providing a 48-bit salt (``5pZSV9va`` in the example). * :samp:`{checksum}` is 22 characters drawn from the same character set as the salt; encoding a 128-bit checksum (``azfrPr6af3Fc7dLblQXVa0`` in the example). .. _md5-crypt-algorithm: .. rst-class:: html-toggle Algorithm ========= The MD5-Crypt algorithm [#f1]_ calculates a checksum as follows: 1. A password string and salt string are provided. (The salt should not include the magic prefix, it should match the string referred to as :samp:`{salt}` in the format section, above). 2. If needed, the salt should be truncated to a maximum of 8 characters. .. 3. Start MD5 digest B. 4. Add the password to digest B. 5. Add the salt to digest B. 6. Add the password to digest B. 7. Finish MD5 digest B. .. 8. Start MD5 digest A. 9. Add the password to digest A. 10. Add the constant string ``$1$`` to digest A. (The Apache variant of MD5-Crypt uses ``$apr1$`` instead, this is the only change made by this variant). 11. Add the salt to digest A. 12. For each block of 16 bytes in the password string, add digest B to digest A. 13. For the remaining N bytes of the password string, add the first N bytes of digest B to digest A. 14. For each bit in the binary representation of the length of the password string; starting with the lowest value bit, up to and including the largest-valued bit that is set to ``1``: a. If the current bit is set 1, add the first character of the password to digest A. b. Otherwise, add a NULL character to digest A. (If the password is the empty string, step 14 is omitted entirely). 15. Finish MD5 digest A. .. 16. For 1000 rounds (round values 0..999 inclusive), perform the following steps: a. Start MD5 Digest C for the round. b. If the round is odd, add the password to digest C. c. If the round is even, add the previous round's result to digest C (for round 0, add digest A instead). d. If the round is not a multiple of 3, add the salt to digest C. e. If the round is not a multiple of 7, add the password to digest C. f. If the round is even, add the password to digest C. g. If the round is odd, add the previous round's result to digest C (for round 0, add digest A instead). h. Use the final value of MD5 digest C as the result for this round. 17. Transpose the 16 bytes of the final round's result in the following order: ``12,6,0,13,7,1,14,8,2,15,9,3,5,10,4,11``. 18. Encode the resulting 16 byte string into a 22 character :mod:`hash 64 `-encoded string (the 2 msb bits encoded by the last hash64 character are used as 0 padding). This results in the portion of the md5 crypt hash string referred to as :samp:`{checksum}` in the format section. Security Issues =============== MD5-Crypt has a couple of issues which have weakened it, though it is not yet considered broken: * It relies on the MD5 message digest, for which theoretical pre-image attacks exist [#f2]_. However, not only is this attack still only theoretical, but none of MD5's weaknesses have been show to affect MD5-Crypt's security. * The fixed number of rounds, combined with the availability of high-throughput MD5 implementations, means this algorithm is increasingly vulnerable to brute force attacks. It is this issue which has motivated it's replacement by new algorithms such as :class:`~passlib.hash.bcrypt` and :class:`~passlib.hash.sha512_crypt`. Deviations ========== PassLib's implementation of md5-crypt differs from the reference implementation (and others) in two ways: * Restricted salt string character set: The underlying algorithm can unambigously handle salt strings which contain any possible byte value besides ``\x00`` and ``$``. However, PassLib strictly limits salts to the :mod:`hash 64 ` character set, as nearly all implementations of md5-crypt generate and expect salts containing those characters, but may have unexpected behaviors for other character values. * Unicode Policy: The underlying algorithm takes in a password specified as a series of non-null bytes, and does not specify what encoding should be used; though a ``us-ascii`` compatible encoding is implied by nearly all implementations of md5-crypt as well as all known reference hashes. In order to provide support for unicode strings, PassLib will encode unicode passwords using ``utf-8`` before running them through md5-crypt. If a different encoding is desired by an application, the password should be encoded before handing it to PassLib. .. rubric:: Footnotes .. [#f1] The authoritative reference for MD5-Crypt is Poul-Henning Kamp's original FreeBSD implementation - ``_ .. [#f2] Security issues with MD5 - ``_. passlib-1.5.3/docs/lib/passlib.hash.sha1_crypt.rst0000644000175000017500000001016011643466373023201 0ustar biscuitbiscuit00000000000000=================================================================== :class:`passlib.hash.sha1_crypt` - SHA-1 Crypt =================================================================== .. currentmodule:: passlib.hash SHA1-Crypt is a hash algorithm introduced by NetBSD in 2004. It's based on a variation of the PBKDF1 algorithm, and supports a large salt and variable number of rounds. Usage ===== Supporting a variable sized salt and variable number of rounds, this scheme is used in exactly the same way as :doc:`sha512_crypt `. Functions ========= .. autoclass:: sha1_crypt(checksum=None, salt=None, rounds=None, strict=False) Format ====== An example hash (of ``password``) is ``$sha1$40000$jtNX3nZ2$hBNaIXkt4wBI2o5rsi8KejSjNqIq``. An sha1-crypt hash string has the format :samp:`$sha1${rounds}${salt}${checksum}`, where: * ``$sha1$`` is the prefix used to identify sha1-crypt hashes, following the :ref:`modular-crypt-format` * :samp:`{rounds}` is the decimal number of rounds to use (40000 in the example). * :samp:`{salt}` is 0-64 characters drawn from ``[./0-9A-Za-z]`` (``jtNX3nZ2`` in the example). * :samp:`{checksum}` is 28 characters drawn from the same set, encoding a 168-bit checksum. (``hBNaIXkt4wBI2o5rsi8KejSjNqIq/`` in the example). .. rst-class:: html-toggle Algorithm ========= The checksum is calculated using a modified version of PBKDF1 [#pbk]_, replacing it's use of the SHA1 message digest with HMAC-SHA1, (which does not suffer from the current vulnerabilities that SHA1 itself does, as well as providing some of the advancements made in PDKDF2). * first, the HMAC-SHA1 digest of :samp:`{salt}$sha1${rounds}` is generated, using the password as the HMAC-SHA1 key. * then, for :samp:`{rounds}-1` iterations, the previous HMAC-SHA1 digest is fed back through HMAC-SHA1, again using the password as the HMAC-SHA1 key. * the checksum is then rendered into hash-64 format using an ordering that roughly corresponds to big-endian encoding of 24-bit chunks (see :data:`passlib.hash.sha1_crypt._chk_offsets` for exact byte order). Deviations ========== This implementation of sha1-crypt differs from the NetBSD implementation in a few ways: * Default Rounds: The NetBSD implementation randomly varies the actual number of rounds when generating a new configuration string, in order to decrease predictability. This feature is provided by PassLib to *all* hashes, via the :class:`CryptContext` class, and so it omitted from this implementation. * Zero-Padded Rounds: The specification does not specify how to deal with zero-padding within the rounds portion of the hash. No existing examples or test vectors have zero padding, and allowing it would result in multiple encodings for the same configuration / hash. To prevent this situation, PassLib will throw an error if the rounds in a hash have leading zeros. * Restricted salt string character set: The underlying algorithm can unambigously handle salt strings which contain any possible byte value besides ``\x00`` and ``$``. However, PassLib strictly limits salts to the :mod:`hash 64 ` character set, as nearly all implementations of sha1-crypt generate and expect salts containing those characters. * Unicode Policy: The underlying algorithm takes in a password specified as a series of non-null bytes, and does not specify what encoding should be used; though a ``us-ascii`` compatible encoding is implied by nearly all known reference hashes. In order to provide support for unicode strings, PassLib will encode unicode passwords using ``utf-8`` before running them through sha1-crypt. If a different encoding is desired by an application, the password should be encoded before handing it to PassLib. .. rubric:: Footnotes .. [#desc] description of sha1-crypt algorithm - ``_ .. [#source] NetBSD implementation of SHA1-Crypt - ``_ .. [#pbk] rfc defining PBKDF1 & PBKDF2 - ``_ - passlib-1.5.3/docs/lib/passlib.hash.ldap_other.rst0000644000175000017500000000310711643466373023250 0ustar biscuitbiscuit00000000000000=============================================================== :samp:`passlib.hash.ldap_{other}` - Non-Standard RFC2307 Hashes =============================================================== .. currentmodule:: passlib.hash This section as a catch-all for a number of password hash formats supported by Passlib which use :rfc:`2307` style encoding, but are not part of any standard. .. seealso:: :ref:`ldap-hashes` for a full list of RFC 2307 style hashes. Hexidecimal Digests =================== All of the digests specified in RFC 2307 use base64 encoding. The following are non-standard versions which use hexdecimal encoding, as is found in some applications. .. class:: ldap_hex_md5 hexidecimal version of :class:`ldap_md5`, this is just the md5 digest of the password. an example hash (of ``password``) is ``{MD5}5f4dcc3b5aa765d61d8327deb882cf99``. .. class:: ldap_hex_sha1 hexidecimal version of :class:`ldap_sha1`, this is just the sha1 digest of the password. an example hash (of ``password``) is ``{SHA}5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8``. Other Hashes ============ .. class:: roundup_plaintext RFC 2307 specifies plaintext passwords should be stored without any identifying prefix. This class implements an alternate method used by the Roundup Issue Tracker [#roundup]_, which (when storing plaintext passwords) uses the identifying prefix ``{plaintext}``. an example hash (of ``password``) is ``{plaintext}password``. .. rubric:: Footnotes .. [#roundup] Roundup Issue Tracker homepage - ``_. passlib-1.5.3/docs/lib/passlib.context-usage.rst0000644000175000017500000002451511643466373023001 0ustar biscuitbiscuit00000000000000.. index:: CryptContext; usage examples .. _cryptcontext-examples: ==================================================== :mod:`passlib.context` - Usage Examples ==================================================== .. currentmodule:: passlib.context This section gives examples on how to use the :class:`CryptContext` object for a number of different use cases. .. seealso:: * :doc:`passlib.context-interface` * :doc:`passlib.context-options` Basic Usage =========== To start off with a simple example of how to create and use a CryptContext:: >>> from passlib.context import CryptContext >>> #create a new context that only understands Md5Crypt & DesCrypt: >>> myctx = CryptContext([ "md5_crypt", "des_crypt" ]) >>> #unless overidden, the first hash listed >>> #will be used as the default for encrypting >>> #(in this case, md5_crypt): >>> hash1 = myctx.encrypt("too many secrets") >>> hash1 '$1$nH3CrcVr$pyYzik1UYyiZ4Bvl1uCtb.' >>> #the scheme may be forced explicitly, >>> #though it must be one of the ones recognized by the context: >>> hash2 = myctx.encrypt("too many secrets", scheme="des_crypt") >>> hash2 'm9pvLj4.hWxJU' >>> #verification will autodetect the correct type of hash: >>> myctx.verify("too many secrets", hash1) True >>> myctx.verify("too many secrets", hash2) True >>> myctx.verify("too many socks", hash2) False >>> #you can also have it identify the algorithm in use: >>> myctx.identify(hash1) 'md5_crypt' >>> #or just return the handler instance directly: >>> myctx.identify(hash1, resolve=True) .. _using-predefined-contexts: Using Predefined CryptContexts ============================== Passlib contains a number of pre-made :class:`!CryptContext` instances, configured for various purposes (see :mod:`passlib.apps` and :mod:`passlib.hosts`). These can be used directly by importing them from passlib, such as the following example: >>> from passlib.apps import ldap_context as pwd_context >>> pwd_context.encrypt("somepass") '{SSHA}k4Ap0wYJWMrkgNhptlntsPGETBBwEoSH' However, applications which use the predefined contexts will frequently find they need to modify the context in some way, such as selecting a different default hash scheme. This is best done by importing the original context, and then making an application-specific copy; using the :meth:`CryptContext.replace` method to create a mutated copy of the original object:: >>> from passlib.apps import ldap_context >>> pwd_context = ldap_context.replace(default="ldap_md5_crypt") >>> pwd_context.encrypt("somepass") '{CRYPT}$1$Cw7t4sbP$dwRgCMc67mOwwus9m33z71' Examining a CryptContext Instance ================================= All configuration options for a :class:`!CryptContext` instance are stored in a :class:`!CryptPolicy` instance accessible through the :attr:`CryptContext.policy` attribute:: >>> from passlib.context import CryptContext >>> myctx = CryptContext([ "md5_crypt", "des_crypt" ], deprecated="des_crypt") >>> #get a list of schemes recognized in this context: >>> myctx.policy.schemes() [ 'md5-crypt', 'bcrypt' ] >>> #get the default handler class : >>> myctx.policy.get_handler() See the :class:`CryptPolicy` class for more details on it's interface. Full Integration Example ======================== The following is an extended example of how PassLib can be integrated into an existing application to provide runtime policy changes, deprecated hash migration, and other features. This is example uses a lot of different features, and many developers will want to pick and choose what they need from this example. The totality of this example is overkill for most simple applications. Policy Configuration File ------------------------- While it is possible to create a CryptContext instance manually, or to import an existing one, applications with advanced policy requirements may want to create a hash policy file (options show below are detailed in :ref:`cryptcontext-options`): .. code-block:: ini ; the options file uses the INI file format, ; and passlib will only read the section named "passlib", ; so it can be included along with other application configuration. [passlib] ;setup the context to support pbkdf2_sha1, along with legacy md5_crypt hashes: schemes = pbkdf2_sha1, md5_crypt ;flag md5_crypt as deprecated ; (existing md5_crypt hashes will be flagged as needs-updating) deprecated = md5_crypt ;set verify to always take at least 1/10th of a second min_verify_time = 0.1 ;set boundaries for pbkdf2 rounds parameter ; (pbkdf2 hashes outside this range will be flagged as needs-updating) pbkdf2_sha1.min_rounds = 10000 pbkdf2_sha1.max_rounds = 50000 ;set the default rounds to use when encrypting new passwords. ;the 'vary' field will cause each new hash to randomly vary ;from the default by the specified %. pbkdf2_sha1.default_rounds = 20000 pbkdf2_sha1.vary_rounds = 10%% ; NOTE the '%' above has to be doubled due to configparser interpolation ;applications can choose to treat certain user accounts differently, ;by assigning different types of account to a 'user category', ;and setting special policy options for that category. ;this create a category named 'admin', which will have a larger default rounds value. admin.pbkdf2_sha1.min_rounds = 40000 admin.pbkdf2_sha1.default_rounds = 50000 Initializing the CryptContext ----------------------------- Applications which choose to use a policy file will typically want to create the CryptContext at the module level, and then load the configuration once the application starts: 1. Within a common module in your application (eg ``myapp.model.security``):: # #create a crypt context that can be imported and used wherever is needed... #the instance will be configured later. # from passlib.context import CryptContext user_pwd_context = CryptContext() 2. Within some startup function within your application:: # #when the app starts, import the context from step 1 and #configure it... such as by loading a policy file (see above) # from myapp.model.security import user_pwd_context from passlib.context import CryptPolicy def myapp_startup(): # # ... other code ... # # vars: # policy_path - path to policy file defined in previous step # user_pwd_context.policy = CryptPolicy.from_path(policy_path) # #if you want to reconfigure the context without restarting the application, #simply repeat the above step at another point. # # # ... other code ... # .. _context-encrypting-passwords: Encrypting New Passwords ------------------------ When it comes time to create a new user's password, insert the following code in the correct function:: from myapp.model.security import user_pwd_context def handle_user_creation(): # # ... other code ... # # vars: # 'secret' containing the putative password # 'category' containing a category assigned to the user account # hash = user_pwd_context.encrypt(secret, category=category) #... perform appropriate actions to store hash... # # ... other code ... # .. note:: In the above code, the 'category' kwd can be omitted entirely, *OR* set to a string matching a user category specified in the policy file. In the latter case, any category-specific policy settings will be enforced. For this example, assume it's ``None`` for most users, and ``"admin"`` for special users. this namespace is entirely application chosen, it just has to match the policy file. See :ref:`user-categories` for more details. .. _context-verifying-passwords: Verifying Existing Passwords ---------------------------- Finally, when it comes time to check a users' password, insert the following code at the correct place:: from myapp.model.security import user_pwd_context def handle_user_login(): # # ... other code ... # # #vars: # 'hash' containing the specified user's hash, # 'secret' containing the putative password # 'category' containing a category assigned to the user account # #see note in "Encrypting New Passwords" about the category kwd # ok = user_pwd_context.verify(secret, hash, category=category) if not ok: #... password did not match. do mean things ... pass else: #... password matched ... #... do successful login actions ... pass .. _context-migrating-passwords: Verifying & Migrating Existing Passwords ---------------------------------------- The CryptContext object offers the ability to deprecate schemes, set lower strength bounds, and then flag any existing hashes which violate these limits. Applications which want to re-encrypt any deprecated hashes found in their database should use the following template instead of the one found in the previous step:: from myapp.model.security import user_pwd_context def handle_user_login(): # # ... other code ... # # #this example both checks the user's password AND upgrades deprecated hashes... #given the following variables: # #vars: # 'hash' containing the specified user's hash, # 'secret' containing the putative password # 'category' containing a category assigned to the user account # #see note in "Encrypting New Passwords" about the category kwd # ok, new_hash = user_pwd_context.verify_and_update(secret, hash, category=category) if not ok: #... password did not match. do mean things ... pass else: #... password matched ... if new_hash: # old hash was deprecated by policy. # ... replace hash w/ new_hash for user account ... pass #... do successful login actions ... passlib-1.5.3/docs/lib/passlib.context-interface.rst0000644000175000017500000000133511643466373023630 0ustar biscuitbiscuit00000000000000.. index:: CryptContext; interface .. _cryptcontext-interface: =============================================== :mod:`passlib.context` - Module Contents =============================================== .. currentmodule:: passlib.context This details all the constructors and methods provided by :class:`!CryptContext` and :class:`!CryptPolicy`. .. seealso:: * :doc:`passlib.context-usage` * :doc:`passlib.context-options` The Context Object ================== .. autoclass:: CryptContext(schemes=None, policy=, \*\*kwds) The Policy Object ================= .. autoclass:: CryptPolicy(\*\*kwds) Other Helpers ============= .. autoclass:: LazyCryptContext([schemes=None,] **kwds [, create_policy=None]) passlib-1.5.3/docs/_static/0000755000175000017500000000000011643754212016666 5ustar biscuitbiscuit00000000000000passlib-1.5.3/docs/_static/logo.png0000644000175000017500000000135311643466373020346 0ustar biscuitbiscuit00000000000000‰PNG  IHDRóÿasBIT|dˆ pHYs|4k¡tEXtSoftwarewww.inkscape.org›î<hIDAT8…“_HSaÆŸ³3wøÞ÷÷>ßûòQ„”ŠÐkê¸®š¹£¦i½˜,ö5unûƒ}”™Öpü©n)´›Gˆ|b}ÕíùÍ$‰±Ç ËÒ+³ÕÜC¡ŒK­‹\íÑNP•¬@©éªLšNn&䨡ÆÄÛ\õ7ä¼j{q6|ÏÈÛ.}´ô}@åGÆZýÃF¯µjœ^«eʈ„»¥RsòÉFx:rC“eﺂâŠÃY³ÝãˆÌ~ŸË§ƒŠ`MÐiÊ**ÊA¨rì Ȧ'a©KŸiä0¤ø„ÀSJgsÚ}:ƒÕ n¤7sÒBŠa’†”_ØH`¢¶ׯû© %ŒôSÕG\Î÷v·})ük,<há½ßñHܧÑÂUeBÇ–ˆÕ"ÁØ‚ouæ:ëÑ›»ùtt.øÿÙÕv™¬À,n:·1¢¢ÑBòxÞz•ˆ „ì)ècë"÷XA¸B¤Å³Å¯ƒìãý÷¥´wù*‘ÏM“òj™ÿh‰Žr±ø ÓïðL  €é—5ÇŒ§Ÿoh8™“¦Ï éµ¥Ìu10)BVÚ=ñ†ãkëÝ~¯wÏO 3šL¥‰ÇE!6¾‚sm½dE  ~â‘·Vjäfš‰($yCÀÝ\õÁÖnâW*°o¸Eff 06¼]ýó§¨Ûò4-]äÁ¡ÿÆNZou¹éhIEND®B`‚passlib-1.5.3/docs/_static/masthead.png0000644000175000017500000001423011643466373021172 0ustar biscuitbiscuit00000000000000‰PNG  IHDR´4#}êsBIT|dˆ pHYs × ×B(›xtEXtSoftwarewww.inkscape.org›î<IDATxœíyœe™Ç¿Õ×Ü3™ä˜&$ !HØåPY9 rB(X +"‚QaAÄ  Ân­h!9ŠC<@\T@®̘«&ÉÌ$™Ì=ÓÝUµ¼U™êšêžéI'á˜ßçSŸªzû½ß_=ïó>ïÑ’¬¨ô¡Ÿ„ŽvúЇ\¢Ð}øD!r$Y|ŠÑ+B?3C› IWŽÌ/‰™„!•eÒ„ÏUæç‡¿O˜Ëõ‹…ÂÚ[¬{]k<ŸÑÞŸT³cçžÚÕfŨ±‘H$vú3S´/ŽÂõáÓ‡¬ -E¸ª¢*š_P%—xÿõ-ëß«ßôþ5u[ÖîmÏ+Ècô©UE6L(.+ʃ0í­q‘ð;³ŸU{Å*u­i™55ÔwH¡ƒ†W 3ùp®}º6Öеû ]ˉ"ëHB!é²þ±0R˜Ý;ÚͶÖäâYϪ_{æR­¤nw˦ACÛóKû—qâÄÑ•”a¢±|LÓœ°dª62^HM¨#reAI¿+,‡Bѱ=IÛе $eEMø~Ëò÷jdEmʶŒŸºV œìmnº œkÐ(+ê®#§(0œ œœXÀ½À!·Uo¾Šú¤)aÚÛ­vl>˜½ZmZ>ë×»mBƒ!Liy™$¢STZÆð1£ ¶®[¿>Ú†4`XErÈð$Ó|`O[2™x½»D ]+C4Œû`:W,C¸ÝÀ:@–ÈJª%æŒÏ¿ó:ºf!ê+êó{;ð£CMÐеð¯Àë²¢6xÙ p_—+Á“µÊaÙö[ õÉ„M˜!rIQ("ݽxŠvͲéÚ¤'aKaÀsIa;<2é‚‹#¿xIxä)ó$)J¼½‰½» Bažë.]YQ€ˆ/ÙµŠ„éJæv`+Pí¼Î› ]ûZ¶eþ˜âeààkÀ?·d®îÎ>ÔÄ ]h—ßÕ†®M ðv)0xÊçž³Þ!k+Ǣ˴ÊXDZÊYƒ ó òÙWÛnïÝÓÚjÛ’4b¬\ÍÏGÔ™0Ñ!…¾Û„AŠ8ïkßüC[Sý¾MɾéŠeêîžæÁ‘ëã<Ϋ€«eEõJñÀdà1R%Ã`®_mù¤Âеcí>ç¯ÊŠªå0{ù§å²¢ÎHã7 t’ãô¬¬¨Ós‘¬%ôœUêN黼QÛÖÔ`ÀàRiô)rј U…±¼<„Ð y.ñn|ïÉD‰ö+}1‚´eé ­zétí=Ƀ¬¨q:¥´‹¿yÉìøÛ++ê*à3ÀžŸfß˶ìcì pÛã4ª}ï;Òyt‰7Oñ\e¢Wk9f.¿zA2aÍ^óö®¦uïîj¬1¨­n ­-_Õz·Ì6l«‰Ñ§W4ö³KÆœ*8ö†Eó¸gé íEmºVÔƒlÔøÞÒy”µ˜çsžoèZe ý1†¬¨-UÌ‹Ú'ó4°!yO÷jL{älÐëÅI³W¨º·+êk›¯ß¶qÏÓ[×ÜøþÖÆ 2ÛžwÛ6±ÌF,³ Ël¤½¥Ž6“X>Tަ¸°„óJ$íAü beò,+êŸïà3\™E‘?îðKéÆ\F.+êYQ§%²¢^ +jZ íà°¬å9¤Õv³W«M³WªOÏXrճÜÚÖÔšHfÏåÙJ6R³sorˇíM[×Q½u -¦ ƒdò€™OOÓNî&yƒôÄzñŠïýìž”ó¿Õá°êhKr¶|töjµI KûÚZ:èBfÉCæd#f²‘¶–zêŒöV+Á˜™ËT9‘äɺ´H!(éO$dqA–Y°{àÇ?0ªÈ2>|Ä‘ÓõÐ!¤·Z›éª7‡°­8–Ùˆé¨ñöf$‰=³W ƒ¾mògË´mˆÄˆ†ÂLÈeÞø’‡!>Eät=t<‘|­é@Ó‡…òmB ¹– Ól¤©~í-û(.k#–Ÿ ¦â™©Ú¶Í{á0æQhÛh'nZ¬ÍeÞã{'›ÀÎôlTVz¿–Û19–u²¢fÔûÓ„ƒ€}G»{?\0t­af­õ[®ºCN ‰D¦•”æyõæd¤vç?ìš››‰fl;2ì8{`Q?¤ŠQíÝÅ·$KPRTNÈLBKqƯ̂ⷹƒ“|ïÝÚе³€k“±@Ôеjàà;²¢Ö÷ Ž¡ÀÀ€áˆÊ1 ]Ûl^“5­љƾ˜ŒBL&Ù†®Õ!&‘6 lðY ¹€ó¡œœŠ˜¬É“õÌF!ºÖø`*0G{0tm ËŠº¾»ˆr¦r,¹L;/‰œbú»'¦W Æ7w"„Ž——Ãu÷¡kßï.¢œIèp,úHÕØ‹‘Äì`[s »þ¹.7­æ.¿zëoörõíES´ÓëªYQW͈â2BRˆP2N¼µ‰ˆ$ñ›3ÿÉ ¹Ê—7 Ÿø3ÞÎôøcKÀ¹²¢¾æ¸G‡€¯ã€+€…iâù<ð8B"¯¦z¥¨¡k—#¦Ë ]+•µÑ~°AÜ5ÀDYéÜácèÚ#Ào2ÄGçŸÜ8¬0tMBXŽbÀ¹¾Ÿ»í¹,+êvYQß—õIYQ/æÞr_gèÚué":d ½ðf-ª/>n|±«j4¨§¹¾¦õ@ƒõ³Laç¬T7‘ƒ…1. ]ûB-Ø(+jÂеã‹a~@'±¶ÊŠš ßðvkòû‘5ièÚ‡tšü‰äèÍ#=Né’ úqˆ•‹x׉§ ÿ pº|A<„¨»Bç=hµ]:¼™n,"+êS†®í^õ8ÿÄеçœà²„ÎÛR‹Jú••|Ъ±mÍÛͦi~ëëT´Y컈•e­†®Õÿ¤“Ì+€I²¢nKþ\:WïUï§ñçF.Mãg”ï=Ю.+j-0±:î ]+ö ¼œ†Pmޱ­—Á3’ß™å]îq*¾ä÷½ôJ­@ K÷U_,Èæ@ÝnÚZšöžµ5X¯<̘ŠÐgw#ôÊB"ÿ7ð/²¢N“•®*+:ÛMÝùPÖNo$u:þÛ†®M ò(+êN¿)БXÞõ*Š¡kè,ÂÚyÍçþÛI­Ï+‚䇤rØ­¡[Ê“_TÚƒbÛš·šÍDâ†\ëÁ=Ó¨_†ƒöÞ„¬¨=™AtÃÛˆ¥¥¹ÈK¡k‹9ŽSXnèÚ“sŸqU?ô¼ßgèÚ9À‰Ô>@jwFØf7eñá'tôŠÐK§i¶m½òø“ò;WÑ5RRžû™Ê‚~#_‰„Ù¶dºvÏÂ+µâÞ¤q´aèZÈеK ]{1w?b ·Œ®Öˆ´µ^VÔYÀ„^íÅàyC×îÏ>.+ê·{ÿêû¹?ð˜¡kË ]Ëïiž>Æð«±]¬½Ò¡¥PôGƒ‡ˆFó ñ®¢³ÌF¤P ƒ+“y僠Æà™·lšö½h;¿¼ì…ž­=p öi“÷ݺºæÿ@3ªéTC×NGØ¢OsœþŽX.•µÁе•ÀøLq¤µÊеç“2w!­.n3tÍ–5pÐ焘lèš+±õü< È7tí’,²$Õswjš¡k!ßäPÚ¶ê¦3úM“¯i2]×ûdMè¥S´1’™R1rLÔ]EWglJ6Õךƒ*¢y’Ô„m[Db0ô8 Ú[)ص™Úbì!ÍTs@Á¥,Ÿ¡s¿t%r໓®ÿ7xÖ‰+‰˜Ê}0ú¸£B,0tmb†ðÏÏ·ºö3Yévoå2à%àç8`9ñý¯Ï:à®ó–Ëùý}#ð¥4ñzÓ ùÞƒòåõ{0/ήþƒpêßKèW‚v–g.G,¶ b䘼p$oßÇöõ¶ÍŠ}{¬iýé )¶ ’–E\²xÉ—¯òü/y® wÿ€*Jêö|;ÃÝNã^ü†Îã)ÄÀ,§µÚе‹;h\IEr±ßÉškRþRûtª%Þº‚Î2¹(vâñÖ‡ë?…äžøæ"6&?B0Y½é¹;¥ýî.¼Ü“œüùór°m ];±@ÌÅ#qfGèÅÓµ3$)oòàá'„!Œ•l¤zóº`á¬åW_»ø2íÎú¨¯åK“W2о]4Û6wÏ^ñÜ… Òº_mгD猔‹":Iî'¯÷Ù½,»û| ©%¿qóæ!UIFªMC¬:û¦¯Œ.Zf9¯(Úç‰ëTDï0aOwá­«’JèýtîRöÚÜC%PG×z ê‰Ü¸nFì z†NB{ÛÄ+d#øåχKTïØ*äøõ··í®÷¤ù ð¢¡k’¿çÌŠÐÑpô‘cÇž\ E±¬6L³ÓìH€}Ñ’©Ú´Y+ÔgiK§hã÷íaÁþ&Ù6müÜ—§á‚ÈòÜÝgCù ]‚³¬ÒS îÝ}¶èZaÞgWgvq ðRùXÄj2\¾ø(Ò "½;hl„Å•¤¥ˆb5BjûI!Ñugõ;t’ÈOj?¡e„IÑ_^x¥ö­ˆÅ]/#V*úŒDê ?¡ýÄ.ñùäÅ} ¸çmÔ z'WФd¸Ç„^YQí Ú/;÷Wâ¥{Hí¶:€Gº•»Ö"“þÔÍžˆXãÿÐw $J9‚tßA,xrp?b y6ð-à5ÄÂ$wM÷ûˆc¼“,§"®— $õÙ¤Åv"ˆb·z´×±ÀJ'ÄG³…®õ9ÑS\ë+S-ð"âÃi¢s“q!¢^KKRϤSº'·zRIALùÇsGøÇBîrQ/ZªÌËN¹Kkŧ9e¬Flpø®é`ïÒ#B/žª]^\Úï‰ñ“/,¶ÌÌÄ^voÝÔŽ´ÇŠËš…ĶÁ¶À²¡qvýÞ{”ßô0­2üHGh—ÌNaG’á,;„9gb±øßÈQÉ?йbNßCÔû:UF·­¼ú¾û,e”І®Iûuè¦âþƒîwÆùEBÍØÏÚ·>h©­ß~á <¹j êÏyù””)ŽDaû:Ú>ÜÀ9×Ý‹»,KCÐ@0H‡öë{éôb?¡ýƒ«P7wZÞ4½i{ݼñ=÷¤Çò‡ B¶aýuï×W3Y9 ½Õ)D*a{šo¾º»ûÛÕò]ÝKèeR$…¥»‡;½ÈJ6bšM˜f#Ñ”sÇŠŸÒ:÷V—ÀiÞÍåíí|?¦¤#ÎK×ÝËÄW48 ªX¤ þ½$KWXïÝ·Ÿ¤éÈž‰,þtÓù÷¦™.>¿»7Þð™òáEwaݼ¸D ªë øõà ’Æ™)ïÙ|d¹tfCì ¿é­ŽY- D'Ž¥ðŽëQû—Ió‹ËìØ ™‚HTÚµnÔ×b××°wòU]ξè.ã6‚È çòÛDƒH•Ž`éºÿ B{ß»#p&Bgr;Z"XOÊГ߲I3¤k³î„GJ¸î¬6`ý}—Ì㉋&Ù‹nü 7470¯¤œÈ€¡äIaá©½ŽÆV’Ú-eŠ×Û…¸-ôBö••Mcõ¤ñs‘î‘D®$¦™tëÃFÚß²úöŽÞ é…7øñU—ñÄ•_â[õ\S6pA1±¶&â-âaÒëdéô!Wå°+Íé?Ý­ ,\aC—õböG2ÿ‡5­lÛ´[³éÍgÒü¯3äógð½X„+4pëÅóx2 7á'³w`hŽmS}øô Û™B©ÓÚç(.Ò»œï…_‰´÷‘¹‡ŠM}w³2.Ó` ÒK襾Ì}Ȳú¬,v–ø‘Ö<ÔGä>ä½úóz/²Ù7ÖGÞ>nü?Íÿëô#IEND®B`‚passlib-1.5.3/docs/_static/logo-64.png0000644000175000017500000001026711643466373020601 0ustar biscuitbiscuit00000000000000‰PNG  IHDR@@ªiqÞsBIT|dˆ pHYsttÞfxtEXtSoftwarewww.inkscape.org›î<4IDATxœÍ›yp]wuÇ?ç.ïiµöÕ²µx‰—@Šx‘d'„†C-ÊbË ¶dÚ™N”ÉÀôΆ¥P2$¶¤ eÚ[’8ILbÇv[ò"Ù–dmo»÷þNÿxzÒÓæ8Æ’83wÞ}O÷þ~çûý{¶ß•¨* %m»¥<KÂ Ž­¿WB™o:›e+Â×w©’ ‚ŠE—:Ux¨a»¾0_úÌû‘BãòÀ޼B;(,q¬l›p¦C4¢ŒôõÆýÑ!ß‹oÃWß»]G¯4f[›8n7•ç28»m›×¢×¼ÐÖ&N¸›mGV-[v‹JC€•<$ù©cß{ÏD8ulÈ7žOÝpÛNíIkïnYáX<(–l4V¶XåE5Ú.?Úüi=qµºÍ Íò¾kS¶™í2<|êð<åðó^<î<›ÁÖmÛ4hÛ-®p¿÷gådPXVàfdgÎÈ I028ÊåK—±H\}0çßÖß«Þ[é6ç´ï’:Ëæhõò°»¸&ƒÔª¾pá\œs]Q/ ¬œ¼Ö¬(pró3A,"#/ïï0úÍúSÜßY+ÏX¶ÔW¯¬tË«JQk2i`¡*œ?}Ž®co¨O¸ºå¶Oè¥+égÍ)zÀ²¸p*«3Ä&^hï÷OŽÇ£~«Qýû‘ˉ8Ø£ƒýqÀ"+'“ºÕå6ðO5|Ør㆕nÙÒ²iࣣq|/@Ħ²¦šwm­·ÝphYÈ“Ÿò \㜠ÂÊPØòIJÇÍþôñQxzÎò©¬ß¡Ÿmܡ߯ßa¶üòä‘‹ °›âòøÒÒ•VÖ¢ì1•mL œ?ÕËKû~Ÿxiß!>½Ÿ#‡ûñ˜G83›Ö­swwÖòÕ%@”êp–m§Wl†ž1ºkógµ?ýZ…–è¨2¶ã—ˆˆ–.)!E ØœxåDpúõSÑX4úÊfUîìë?ùÚó‡<(9y…T¯Zc¡üKÛn)ŸM?gNÑ'åÕÈp`” ‚íX"”N½P +-×ò-ÛqRæ]Q]æŒEÕq'"ÇÈ`„¾Þ>åC ;õ©Ôýû[äÅX4úÆÅs=”.©¡tI§ÁQsÐ:“rsÿXüÆ÷Œ1¤¬ ´27$ÂçÚwË]m{$§³Y¾(–²åC¡ «°¢jÜá&¢UÕ㳩7çÐØ¤?~pìðÅ`h Aj5äRµ¬œŠê’qðSÁN³±q¸aÝz7;/¿Î(ωG¿%ÖwJ*«k×lºÍµíðøõ‘áËžQ}}6ÝæÃ PÅ—.G©{õ`÷{Ë—R½¢\,;-‰™|Ú!“-!œ™Í;65:£ÃÃÄ#1r Kl'”1éšÞÓDG†B(¿›M¯y¯;šåSˆ|϶$;¯0×Ê+^d—V•bÙ³™<3£W /1Ì+ûžô¼DüW ;ô#³é3ï@²2T‡«pp×’åUNÕ²%\øH½9ªñh?Ç_zÞ ‚®¬ß®fÓeAH—ÎëÂ’KK×M!`Úª^xgàâiN¾ò{/ðƒ.D?\¿]ÿp¥ùçÍÌ.Zà†ÓÊãk4yD¼t†c‡‚ ¢|'è}«Ùç% Ì&m»%_Ç ym;¦xþ髞ö75˜` “‘eQQ[An~¶ ò-Û¢§³E~¶o—”̦Â`A €ʘqÕ'V~"NocàG0Á–¥´*‡Ú5ùöê›ó­Êe!Ëvø°esl«Ü9‹ '®ŽÎ`úª' ˘nòà‡'Á0Ê¢‚u7â.*$O•ÿêxTn˜ªÃ‚ vš\Ñä'¬ Ýä'À'- †‰G†‰Ž ˜ªIwQ¾ gâŠÃ¯žl•ìtÐ Šµ'°lמæìfr„&Ž1ÔD1A5ŒI~ö qé\$ lËÁ/]Œ³¨(9[E-îé£Ôf<|9¥Å‚XÀž=bw¶ÐòÉåï|§=£É§?ÀMþò¥N&2üÖ(a kÕçÛ=§axTÁvaQ!¤¤hÞó€C‹ÏÇ@þò†õë­‚’r4=û›–Øøc+AMl’¤ÎO¼2’HÄøeýý«ô¹:›¥5”ÉÇ–¬$¤Àèô¼ Ʀr˧õ<Ìó#ðÄw%œ—/¿ä}«nÞ`å•Ìjòˆ ˆ %è(^"‚ï%(®ÈÀqcæ%Ã1ÊÿM›PÙ›ˆñI%iYÉŸ­€[€Ça Ø¿G2óòäD¬[×ܲÑÎ)(ž^N¾| ¸ÔÓc§îAz1Zì%܂ŵ"ªIGç†ðq6-“&µØ ã«R…ÀO9Ÿºd^hÛ#9á¨üÚrìknÙlgç0Õäã‡ö—z?Ôïý{ÙlËJ®lA¡ Ý|¾£YºG›µjñ7ÀGòK“רB"žÔ'ãý9÷m»%?dËS¶íüÙÚõNfn>SMÞÊ‘ž †û/ÄŒê[š´c¶ñ=,y± ΓYºKúz ¿—@ 6@8/¯7;oŒàR7&2Ä™úZ=/<ß"E¾ÈïlÇ]½vS£“™½høÀ÷9r°Í½ÜQÑ÷\ÍÆhûn¹K„ÿ.YŒä—câ°pÃÉkSà#ƒp±>аCŸH5g<÷#) Âò¬ ×­ÙØèfd%[aé…Œïùyþi/2tyXŒÞ¶y§¾Úñ;šå~à_só1Å‹±,' &L>u>2çñMÀc MÚ”>ΜðÜ¥ÊÙë†3ªÖnÚâ†2²§yy?áñÚ'½Xd¸ßxzkÃgfo[Í&í»å.±ø¡X”äã„3!œ•äØóÀ‹ÃP~|å‘D÷ݶMGÒǸîìm•ZÙÎÌ*[³q‹ë†³¦åñ^<Æ«žòâ‘Ñ &Э÷èÉkïÉVÉÎV¾4¡Tˆ`4báe5|¡a§˜éþëJ@{‹¬´Döffç®Ù°ÅuBÓj÷xt˜×<í%bѳ~ [·Þ£Ý×kþÎV)5ÊQò°x×>£ÃWºçºÐÙ*7 Ò–™»(oõ-['fj.½Äkžõ‰ø›žÑ[§îý/„\—<`ß.Yg;òLv^Aöª›ÛÒáQƒ1#¼ñòó‰D"Þç¹ÚðVÛÖó%t1Ô±[6Y¶ìÍÉ/ÎY}ËÖiàU½ñr5¿4?„jEÈã¾?Zóë$Ô#°¯UnµUžÈ/. ­X_o[Vªµ*_£˜ñB&yÞßÓK×± *JóÙ,þúZßí¹^rÍìo•;Uåñ‚Ò gźM–Hx5Iàcµz²vŸ¨Þ†ú†é: Dy’L>²y›F¯+ª·!×D@G³Ü-"?/ª¨²—Ý´ÁIÕíI“× ÊÈàb‘ËäeM² "#2jè>†¯Êï%Áû¦¾+0_ò¶ èh–!ž¥Å£Ðußøœòn¿ž!ñjåmÐÑ"M¢è>Y€™C¼Óä- èh‘/U¯¾‰ÅËßA²5­cÀ'·¥ J]J«²øag³<£ñ3ú²/ÜâÅ9Ûu ?K®¾*x18{ÏV—Æù‹WôíÍò j×®§¬zIG—êÅ¥7('{ù¾ž(=ݪß«ßÁ?¤ŠÓÎV)Uå)ËbMeçßÀ3†ÔçηÊÛçBf%`«<¤Ê—êÞ±AJª–‘l[EÐ :ÞŒœ9Î'½üМ?‰QøyAÛ×lÓ$Ûc¡(#lÁ˜€öŒ8\¯FæxJf @¤£…ò¹å7m¶Š*kP5›AòóÜÉ..t_ÒŠš\)(5Ñd ;­1:çÞÄWÃÞD&JÕâ¯í‘Ð@”ï„ùâûÿNãóŒ{m:{öˆ]ãQQùôŠu VaÙÒ4“Ž[@÷ñ.úΨQ~!ðÑ¢ (©LŽ15ÌÅ¢pö¾þ¼wË=zqAÎ"“œàâ¨Õ*XŸºáæ[­Â²ê±<~dÒ\×ëojß¹c”MºM”{úÎcΟ¨™æBÉ0g;¬µlž{îGR¶0Pg–ÉQ@¸-3'œü¢Y·¡z‡Tá·MÉ7/ëwên»ûðΜ$0†ñ&eŠ'9¸(µÃÒÀ9«L"@sgtx°ïÕÎßx±Hߤ—:¯Z²DxOG ?}m„ê›ôExwdˆÑ3'ðÓ€^¼ˆ¢ÜW¿SÎ?ÌÙešÜ»K–8¶<í¸NͲ›jÜp8˜ìL‚Ñádúj”v<îN…¯­²:Pž …(*¯ÃµèïI‚WøBc“>¼ (¯ 3†ÁöǤÀä ,Y_»*ÏÉÈöQA5_ÙxÎÇóŽˆpGêM¬ç~,UÏ3¶CMæ"œ‘~¡©~‡þç¼£» ™5û•Ÿ‰Å×açä%O÷ò‰8œ=çûœ3>·§º»û‘BuyB•õ¯oÒ_Ìž·-W®«£–ï÷–-Eò‹¦„9À÷àÜI|/Æ †?¯ß©/ArÜÏ&sã§th>€\«\U9ÜÞ,_øzQ9VŒ9·4Oo ô¼I%®Âv謯¦þ©ÉUUƒMú U>Û׃éíB1à!Ùú/]Š-6™_›C}¯»\u? q§>*p÷P‰s§L0êL½]ÆîŸC}¯»¼í–X{«l´”_‡2É.«Áè=Eˆ2j”Û›ôМh:GrMMÑöYiÁÓ¶K™e#^Œ!1¼­ÝÝ?¹æ¶xÛn)w-ž(RŸw_Ëî,øÛâ -ÿºY¶CAT¤IEND®B`‚passlib-1.5.3/docs/_static/masthead.svg0000644000175000017500000002506611643466373021216 0ustar biscuitbiscuit00000000000000 image/svg+xml PassLib PassLib passlib-1.5.3/docs/_static/logo.ico0000644000175000017500000000217611643466373020340 0ustar biscuitbiscuit00000000000000 h(  °Ñn®Ìt±Ñ†Èãõ¸Öܢŀÿ¹Øœ ÓìûÀÝרËO³ÒŮΈ ­Ì2Èä×@äøÿ ¶Õ½¼ÚÌÐéÿ³ÓÀ£ÂªÑ'1ËäÑMçúÿ'Ýóÿ·Õ·¬ÍG¼ÚÚ°Ñ›ªÑ!-ÇáÎXéûÿ½ÛÄ ÈâãÏèü³Ò  ªÆ(ÅáÊYêüÿ;ßôý ²Ó’ ®Å%ÁÞÇUéûÿ&ÂÝĪªª¬Ì_°Î’ªÍ3¥Ã"ÁÞÂQéûÿ,ÅßɯÌ#³Ó›Äàñ½Ú仨ä¤ÈÄ ¿Ü¾OçûÿBÙîð*ÍæßÒëñ¼ÚͩʚÉâû¥ËD¦¿3ÕîíNéüÿVêüÿOçúÿ)ÝóÿÍèþµÓ»’¶'ÎèáDéýÿLéýÿSêüÿ[êüÿ²ÑĪªµÕHÍèÕ0äüÿ9èýÿAèýÿ@á÷ö1ËæÞEÚðø °Ñ= Ãáº澯 ²Ó¯.åýÿ,Úò÷&Ìæã ªËu:×îý±Ó\Ààª×ñêÅâÍÐëæ¶Õ’$Òëê+Òìõ¾Û°¬Ð+½ÝŸ Áà­¯ÒP¢Å ¨É/ÿÿÇÿ‰ÿÿÂàðÿøûüaþÿÿþü üþpasslib-1.5.3/docs/_static/logo.svg0000644000175000017500000002522311643466373020363 0ustar biscuitbiscuit00000000000000 image/svg+xml passlib-1.5.3/docs/_static/logo-128.png0000644000175000017500000002026711643466373020663 0ustar biscuitbiscuit00000000000000‰PNG  IHDR€€Ã>aËsBIT|dˆ pHYs$é$éP$çøtEXtSoftwarewww.inkscape.org›î< IDATxœíyœdWuß¿·¶Þ÷éîéée¦gß$fÕèI`$6Ì¢OP\‹ƒäÄŽ„‰?"JbÄ&, ÏÈ@6(`–FЋ$„4Úf_º§§×™Þ×Úoþ¸¯ªÞ«zU]ÕÛôT÷ïóy3¯ª_Ýíœ{Þ½çžEH)YÅÊ…çJ7`©aèÂ\ ljÍK]Àà¬æ——®\ —b%HC>à_ï~ ¨™å'¯?×üò7‹Ûº+‹‚fC¥ÀÇ?šçXÌ/€5¿|zÁ¶ŒP° `èb7ð°-ñ¥€²r•5*ªÜx}.¼^RB0 %#F†BD#iãòKà#š_ž]º^,> ’ ]Ü < ”¸ÝÐØâ¥¹Í‡¯Ø…zå[.aÿ,¥`l8LÏ…iFƒÖ¢Çj~ùýy¶¯hªÍ+ŠZƒti~ÌöÛ…FÁ1€¡ ?ðÍøçÚz7›wãõ:ÞF|lßKóÿ‰Ñ0§Ç™ Y«ù¢æ—ÿ!ÏvíÞ ¼ 8ø“@/ð$ð8ð/š_NçSO¾((0t¡O>!`ýfÍ|8Þaæ[ o»¤ ëÜÝçÇ­Õý•æ—ŸÊ¡M·ŸnC—¦¿3ë˜ÃïgEÁ0€¡‹5Àq `ãvM­ˆï@øŒÄ·\#ƒN¿v™h$¯ö>Í/¿¡=•À#ÀïY¿w¹]TT•R^U†ÛëÁíq#fBg‚LŽMœI{ Ì_þ‹æ—“s$|¸`m‹—M;Š™‹ÈŸí püh?2&A‰ì·i~y$¥-×¢ [âßUÕUÐÜÞ@UmB¸˜Ó3 _fàâ¡€NÐüòµ25…À†.Öç€R_‘`ßMå¸\óù¶‹´ßöMrúµ„D>\_¼ºØ ¼”—øØríz*ªËU}Yo« A,&¹Ô=@Ϲ.BÁÄd¸GóËGbì\ QÈ2Àb®ø[Ú‹p¹R|"#ñ»A< âO@Ü â³ ~B&Ÿs©K¸÷kšªhl©Š×½øOÐ;|“øµ U\{㶉o©Ë¼\.kÛZØsó Ô55Æë+¾aèâß.ÄÀŠxÐÜÁ¡7W"\)ÄOŸ]a‰xă¢©7ZžìÛp#ˆ/ƒ¸>“䈄c}¶ƒp(jV®¾| fM;ömJ„:Ï(»pê4•µu”UV%Êñ—Ò¼e+]'ƒÚ|ørÞ½Ë÷ˉӹé©öYŸF€¶ü‹×:Ïfu•U”™’êÖÖ D†3cCcŒ\Ž| ¸=…ø h~ùÀÈXŒžóç±J‰ ¡eƒ¹åà£ù÷­0à$˜žˆ¤Ìú4‚Ì¿x¡e›ÑnŸ‡-×¶Ó´a-ë·µ‘*ò­×ø°í,áãš_¦mA­0•='&FFˆ>þJóx‹¨i\ü:C[óíÝUϦîÀèpˆX²¬Uömü`®eËþÝ׃¸3ãJÞ\èÕ6Ö²a{n'å;N'ÔøAàù›ñ"@(  ¦µ¡¦¡Éúìu¹ö-Ž«žL| ‘ È6cA<,û7o˜­@ÙM)¸¾.o*!å,ºüL—åÝ?¦ù¥óò?+RʂҊjë×ä[b¡0€ŽÒŒÑß=MF1¬TÂU ž‘ý[oÏT˜ì¿æ-¨ýÿ®L³>óº 3”V$v †.ZsìÛa_Q1Þ¢Òd=æN£¤¼ÊúlÞ¯€BØ ùe·¡‹'€Û&FC\¾É¢Œ?ùy=ˆÈþOƒ8œ1 b¿ùÎG6‘?׫¼ªŠKÝ}ñfß |2[¿ ]ü° ¢¦Î©ê¿y P$(eH óÔ(‘°$ñ­×- þ;¸×àz\ïX(‘ŸÊ€ukñø¼ñöÞgèâŽL1tñ6à!—ËEóæŽý0¥ãȺ¨tBAHÍ/Oºxøt8åÄËì:°—{~„ËŸð¤g–áñùØ|ÍnN¾t4þÐw ]|ø"ðÊ6p?p'pw¼o­[wSZYíXW8h³˜ÈwÜ Iü7Ô@21äÔ+Héô¾^,â;Ô•RFMC-›7[Û|;ÊâxŽ`!~cÛFš6nÍXרðekY¿ÎwÀ Š4¿œÞtŒ NóƯ{˜™ “•H‹$òñÓËoݲ•݇o45{6$ÞèE%¥ì8x3í»÷f­gìr¯õ÷yû.\õÇÁN0t±¥Oo.AËÆ5´´¯A¤‹,¢ÈŸõ’024Èôø8©)¢Ñ(e•Õ”WÕRQ»árg©Kœç•_þ‹Óürw¾cU `è¢øð–øw¥åElÚÕLEµýˆu~"?•øäü¬£9Zm2 2L牣 \8ïÞÇ4¿üz¾ãT° `èB täŸò¶¼ª”êº ªê*¨¨®@¸ãoÂyŠü<¤ÆÜ‰FÊ0ãCœúÍsq[„`Û\l šâ0tÑ |xwêß\nÕå4¶5P×w^$‘ŸiÖg,ÃZ—DÆÂH!05Æñž%NÐûÍ/¿;—±Y ‡¡‹÷~”‡°Í’Ãív³ÿ–½¸Ü©ïÝ|g>Ÿ¯È—2L`jœS/½@pf&Þô/h~yß\ÇdE1@†.<À”·ÎÇP~z¸u?¯Åo‰|d„ÉÑ!Ný ‘P¦åŸ÷h~™ðTÉ+’¬0tñ8ðáÜðöÃ,G‘/e˜‘þnÎ{ƒX4oúO€÷k~™0-š F84x}ó™ù8>»"?83É…¯3zÙ¦ðyøÃ¹(Ú±Ê ÈâDz…Dþèå>ξzÔ:ë%ð€æ—ÿu¡:¿Êq(ò’TŒ^)‘Jˆ|d˜þÎóVâÇþC›€B¹ç}dEA©‚ó…¡‹ ”A¥E¤êòs?óÏ8ë32©z–1“øö«¾¹·'ÅæUÅ7ò£ŒG/ºxØÐEí\Ç`E3æì‡W@„KÐnzn/#'‘TD…± ùˆª5ì¾q7[÷n¢uëZꛫ(*± írà€ã†.lnè¹b¥¿ à+²0Àùñ{)ÃS÷‚0%enŠKK@zX»¾„™©cƒ3Œ†e¼ºøGàù“X•&¼¾"®´È·ÎþX\*È’±¤„(*ŽRß,Ø´ÛEÝZ’¼¥‚Q¼fèb{®°Ê&¼EEóùÎgÿùŠ|;3¤þ-!„ˆRß¶Cqi¢O À“†.Öç2« `Â[TÄüE~6É!íežáÎÒÁÎS^âRýv°·Oº¨›mVÀ„×WÌrùöߪ «7~ßԥɎͨSЬXe¡ìõ–›È„ƒLŒÎ0Ø;ÍP_ɱ(áP’àû=Àºvð%úw·¡‹7g€{`è vy}>ößz ó_å[Wûv]~¦U¾”éÏ„ƒAzÏO0>â¬é­¨†úVðzã5‘à LO@O2žéIT #ÇÂV¤0tQ„Šâµ  ´¢‚…ùñY?w‘?ri‚3¯Œf$>ÀÄ(tƒÑËéÄGBi9T$UCÛwe*kÅé ]”?Þjñ×¾sÖwæYó÷–™¯1%@ØülŸí2–.&GtŸµî½| xÉü| ÊÂé:ƒ‹àñYÞû2ù*¨©‡‰„#2EiÓ°¢^f<¡oð³ëà!ŠË’^DsùdÔBÌH^"_Ê0‘pˆs¯M%èñiàó©¢ÛÌwp/ð×n¬ßno’øqFè9 ÅOQ ÙIA´b$€¡‹*Ôúa€¢’v:DQIÒ@tî'x©3ÜüKa˽ŒÙ3Ô;c%þW5¿üK§~˜Æc袸/Ñ!¨]«þnÏåÕ p£âÿ(µ¼±0÷ÃOc¿¸¬ŒÝ7ÜHQI9ó[åcYÁ;\y(v¦&§~Ó@.&^Žé ˜BÍú”­aQ‰íyG“ñ‚gCÀ3À^€Òòrv:Œ¯¸„ÙE~’øBÏ$f̺ “AbÑ ¡™)¦ÆÇLM뻯 L'¨÷z.ÂÍcàW‚3)âßüà-¶ýd—S9ý 0­`&(«¬dçCx|Jë7‘D&& ¦g¦ ‡„ƒAÂÁáPÈýK¨o® ±µÄa EJ°XõÙˆÌÛ.U$º¢¾/ˆ.6 ˆ¿ ¼ºšûáñúòZåGÂa:¿ÌÄðeÂÁ±X4½²Y %\î™ ¶Ñ…ÛI®ˆ%(XT5ï÷åØ?/f@_±³f0®*6áh8R¯C[P—*kkÙyà†YˆŸ.òÃÁÇŸšÁžN‚3S¹´Ú@éþ'ðmPĽ<í¨Ë(IF›«5tñ‰ºù ¦1KQò ÈÆvc"fp@ÁI3Z÷`-@Õš5lß{—Û“—Èœxáif¦¹åyÜôYþOÜk~™æžmZý.P6r9BMƒ¢Pꌭª3;êû‡ ]œÈ”¨ÊÐÅ`—p{ jMJyæ?!ûJâœSY¥0t±³w @MC#[¯ßïàd™]±œ™æø OœNuzx»æ—Csl×ÿîذ#yt›ªÅ½—ºm?}%AN "¡íEéÞ q½íÈÆ#0–4&>¬ùeZ`ª‚‘†.¢/VÔ­mbËž½ 2ßyæ¦&9þÂBÄôyømÍ/GçѼG0 ÿ‚RÜà"M…[]O²Ùæ•! ¦13ñe ¦Æ'Çü‡±0tq3ð&ñë›[زgŸ3ñ³èòg&Ç9öüSVâÿ•d>ÄGóË_™e˜†¾ ¤?¾hl…um'zi(*…u›¡ªÞ,òõ‹ŸNA$÷;™ƒ®z `èâ­(=w)@ckí»®§ˆD>¦ÆG9ñ막„!WžDEò\¨¬]@Í–ñaEÄš8ãO˜ŒPZ må03 Á„ÍÈwE%êwVæpz÷Ç¢êub"„Šœâˆ«z `èâ]¨Õv1@ÓúvÖ&|D>&G‡8ùâω„SæÇÀû:‡Ÿ¡‹ýÀ¯€b! y”T`—æÍl÷N§€1 —/$TÀ_ÖüòÞLmºj_¦ô0‰ß¼q³3ñg9¾¾Ì‰_±ÿ1ཋ‘ÀÑÌCü1P3·çœš©‰Ý )ÊDÎrŸ‰øÃ=6â¿JY—W¥0#i똑Â[¶l£eóv2ÏzÒÿ†`l°ŸS/ýÂê}ó(p—æ—ùk{òkÿ_`!Ly¬Yîx*Ië¬ÇYÉc>–x0†Á‹J-lb8¨ùeG¶¶\u `èâ#¨dŠ.€õÛwÑÔ¾™|Ô¹ èâÌQƒX,¡ƒý:ÊárήÖyöã.Tž€RPM®¨…šu´ ä$ò£Q˜Rgÿ=Uð¯4¿|a¶v\U `èâàK˜Óº}ç54®ßH¾Äêëàì+Ï#“´þ2ðÇšiÃÐÅ.T–±‰/…Ò”V@q9x}æzÖ„D­îƒS03Óã¶sP_wäêrÕ0€¡‹ûÏ wï¡¡e=³ÛýåýE,ýþ¼æ—YC¶.&Lþ]¨Ôsëžq¹ÀåQ„ŽEí¯ †PYË>—Ï+ìª`CŸB°éÚ½¬Y×J>³e ë4Ç^¶ý æ—,]O2Ãd„;÷·`I…3 ΢^%_›K°ˆeφ.þ ø3ár±eÏ~jבñ¥ŒÐßq‚ 'mcþ£æ—Ÿ]®ä Ce¨,dס¤ÂzT”³1óº <ü\óË ó©kÙ2€âí¯1Ó¦¸\.¶î=Du}Ü.‘¯L³{ΠûÌ1kñÿNóË/-QW–5–¥&Ð4|ü[Ì=³ËífÛ¾¨ªk ÷Y¯Â¬\<ó:½çNÇ‹–¨T-·´=Z¾Xv `è |óÄíñ°}ÿa*jÖ;ñ•±e×ÉWéïLDÒŒwk~ù­%íÐ2Dzbs!ômÔB×ËöåUfÇ<|î{η? Ü©ùå÷–°;W– ˜Þ:ßÇŒæéõ±ã ¦râäYKÊCý¶ll®ßËâ,ÀPi×DœøEÅì_Þ¡ùå©%„e„%eC¥q9´ƒJ†¸ý€†Û“=NošÈ…ˆÆÂ¸DÔQä§3Dòs4ãâeogbx—æ—ygÜ*,*³õT+*ëêÙ¾_ÃåNÒ ÙD~(8Í™£¯˜š¦¡µ‘¦ Ž"ßΊQ@&üðz:`2¹˜BÙþtIcaIÀ4|x Ó[§º~-[÷ÆåJͶ]䇓œ~éÓI»§ÚµÕ´l^¤ûÜ[0!Ýn¾ÿ"Œ &¾ ÖüòÑEŒe†EgC×ÿ‚é­SÛ¸Ž-×ßÙ['Ã*?0=É™£¯œIß½U֖кµ !"ék€¸¦ƒ9•{a¸?Q”îÓüò‹‹0ˋʆ.?%î­³®•Í×BØlö!›È—2ÂÌÔ8§_~p0a¹û,*@Âw1s–VxhÛV‚Û•|çCf#Jëýè \ºhkúç?[j±+ESºxjŸ¯¼uZ6°%ø‚Ù‚)NOŒpú¥W­Ä?‚Ú¾=hÀy€é‰Ç& ‡²SÌ ªê iƒ©xT¸ø¦¡r 4E*óõ‰{ë´mÊš5“bgz|”3¯³[ø1*ONÀR×Z”6q(#ÊÖ­à3½g²‰ÿøMü~fú:mÖµO°H>Ë . ]܆:Ø)hjßJûî}ä?7žâôÑ7¬Äÿ>жE€æ—ý¨¨_Ï„Cpá¤rŽÈJ|S"X¡¸ÖmRF˜&~Șâ½°  `èâ}¨T&EÍ›w°~Çòùq†˜#ILÅ*r–cfLÍ/Çwšu@×é¤wìlRÀêu39 IW"À‹s‘åcCwÿð´n»†Ö­N~z8ߦFÖ*«*¢zM…µOº¸=Sý¦˜~?ð0("öœƒña3=á~e½·¼{Òœ*ß§ùåñyͲƂ¬ ]|5ðÊ[gÇšÚ·‘FüD0ÅÜuù=ç.3Ô—PÛE?Òüòk³´çAÔ.€ú¨^£î39]^ê¶EÖœAyÖülî£ru`Þ `èâ^ào0§wûî}4¶¥¸j‘L1š—.àâ—ºm!nþ\óËŒ.Ïf»>ò"rÔ6Bm“ú[ªBèR7LŽ$¾šÞmî2 óbCŸ> „`㵨onÇQä§iér‹Ÿgˆá }¶_A¹seôå3tñT¬]@e­’qõC,—ºl‘4ÆPÑ@ž›ó \e˜3ºø ð(âoÞs˜º¦6œE~ê™}dÖã['†˜žó¶ü=àßh~rhb¼·¢ÜÈ+ʪ ±M‰ÿ Ê·ÎÄêdð%Ç‚ sbCŸÅŒRår¹ØrýÔ4Ƨ–“ÈOîïSOç²ÅÏuÒåOO@÷9Û^ýiT$´]–öîEé ŠË”ÒÇr"8€ ózÞƒq•#/0½u¾ÜÊacëÞ›¨®obv‘¶tzr¯ÜîXv†pÐå§¡û¬ò‰7ñ2Jt'×ðémߌ:“hOùSpëJµ È™Lo‡Q±çq»=l;ð&*k™]ä+bÆ ·£—®A„Kжµ–ÊZ¯£È‡ÌºüPzÎ@2¤çP¡ÜΓ¦Öð§(Ÿ;€Nñ3þ¦Ð‘˜Þ::ð!·ÇËöo¦¢¦žÜD~òûžs=\î¶•¿®½”šwöã[FˆDÔ^?˜ ã4€:'x%K_ªPA“šQ>‚3=»0+˜Þ:ßA%%Äãó±ãÀ[(‹{ë‹ÈW qñLC} +Œ0¦Ò ¾ÙÍš&ÉlÇ·©Z¼Xú:ÔÚÀÄ8jMðóü‡cå!+XrëÜÊacÇÁ[”ÃF"_Ê]§ºH,¹§QáSÛPá^ܠ¦5¶%ëÏ•¤T+z‹…Oå öØÇeÅ #˜Þ:c†%õ—°ãÐ-””U‘È—±Nö0z9±äž~GóËgÍzÞ:×/•kÝF{[¬*\²ÜöØ,|bÀ=š_~unC³2í,àÿb¿¨¤Œ]7¼Õ$>8fɰ%LHêø;w[‰?¼5N|Í/„ †0*#ÖÅÓf(’çø³ uë’©S̾ý¯#o¯X82€¹eº5þyÃÎ}•V’5K†CLŒŒ36”ˆZEíµÓ¬o5¿4€›Q[2¦'Õi^<Ôi¦ã[§ûêz•+Ç‚»ò•GÐüò,Ê€³¯>Çè`ã Ï–ÜØW,q¹f6nà3m›SÇ€Q‰ Î(I âx‚çt”+%Œ ÙÖøsš•l¯€ÛˆFœzñ— ötÌ*ò­ áñFißY†Ç›`‚w£2[×8U¨ùep*B7áb‚DìæÔYŸ²M„¡¤ŸO eåûíü†dea¶]€@öÜÿ®eË&ÛšÒVùNÛ¾øB0qáT”pRis ¥wïI¯5±ý>Ê"— Ön0ãê‚£ø½¤â㛈¿¯ùåwò•†\Aÿø"æÆ¿¡u-Í›š·}é;0Èá0tŸ±ÌfèB1ÁÉ uz€o¿JwßЦsâˆ7}d ÍãÍ/×H¬Pä£ þ ð÷˜G«5õU´nk@E¦(€°šv›Þ8 VöÝçlJ›!”[–cHS' T·Îž"e¸Ï¶õ ¿§ùå¬"'ä{t êhµ ¼º˜õÛkq¹¢nÚêì>õ=-¥ÒÜM$jÓ(Ó«Ÿd©÷O‡0%PU½Úî öª8¹–r~Wó˧rîÐ*ò?6tqêhµ  ¸ÌÆíe¸=Q›ÈŸM…{©K9d˜ˆ l#x™6‡`ª½EX×(Iò«¼:³Š9Ûl@ªmð Öoõà-Š`ù]…;ÜC}¶Gï×üò Yê}'J5]fùzu4kdìU¤c>Au(G@¥/kݒ̈™r­÷£ƒpÙž)ë!à“™Ü²ÌäPÿŒò5B)—ŽÎ©«˜·M`)J¨íÚºæJ=‹øOÝÆMŽ©ÃKSþøH¦DGf‰Û€Ÿj~Ù9ç¬bA¬‚Ý(C‘¨am«¹R‡œafB¥TKqËzÿ&mZ…Ì70Õ¿® êâ9íÿd×å‡Ðw^yö˜x¸m® W1;Ô9ÔÐÅÇQÙ7Ü ¤@C‹ú›#áIß&FBÐß¡ÔÀ&N F+Úrg±° ¾š_þ-*Îo”‚¦·Ãâk7‹.ÀヵUFl;P‘ÃW±Xpï`Í/ˆ²#åtÑsN‰u§<+#ÄÏÿ¥´9h‚yL¼Š…Ç¢ˆ0Ï÷o.‚rÕî9«Ä{šfÐrT¢¿ÿ¼-íé³À}‹ÑÎU,~ˆ˜f”Âh7¨thMíJ¼;‰ÿpP½ÿ-‹À#(õnÞ¹pV‘–"HT5ʶðM.·Jy^\fßp©ÓFü'P;«1}‹.ÞŒÇûvàAíóû;L«8ñg`À>óÿ åž½JüEÆRŠt¡ÜÈï‰WÛ¾¸dW}ðgÒ®baq%b ø‹D„m=ðð±¥JߺŠ+À†.ü¨ä Ö0l_î] ±ù–®d¾€ßFÖÏÒI(IDATÙý•i~yÿ,?YÅ"àJg ©ÚL“ðU\\ñœA«¸²øÿß礫&?æ¿IEND®B`‚passlib-1.5.3/docs/conf.py.orig0000644000175000017500000002135111643752230017476 0ustar biscuitbiscuit00000000000000# -*- coding: utf-8 -*- # # PassLib documentation build configuration file, created by # sphinx-quickstart on Mon Mar 2 14:12:06 2009. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os options = os.environ.get("PASSLIB_DOCS", "").split(",") #make sure passlib in sys.path doc_root = os.path.abspath(os.path.join(__file__,os.path.pardir)) source_root = os.path.abspath(os.path.join(doc_root,os.path.pardir)) sys.path.insert(0, source_root) # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) #building the docs requires the Cloud sphinx theme & extensions # https://bitbucket.org/ecollins/cloud_sptheme #which contains some sphinx extensions used by passlib import cloud_sptheme #hack to make autodoc generate documentation from the correct class... from passlib.utils import md4 md4.md4 = md4._builtin_md4 # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.todo', 'cloud_sptheme.ext.autodoc_sections', #add autdoc support for ReST sections in class/function docstrings 'cloud_sptheme.ext.index_styling', #adds extra ids & classes to genindex html, for additional styling 'cloud_sptheme.ext.relbar_toc', #inserts toc into right hand nav bar (ala old style python docs) ## 'cloud_sptheme.ext.issue_tracker', ] # only used for packages.python.org deployment try: import sphinxcontrib.googleanalytics except ImportError: pass else: extensions.append("sphinxcontrib.googleanalytics") # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. source_encoding = 'utf-8' # The master toctree document. master_doc = 'contents' index_doc = 'index' # General information about the project. project = u'PassLib' copyright = u'2008-2011, Assurance Technologies, LLC' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # version: The short X.Y version. # release: The full version, including alpha/beta/rc tags. from passlib import __version__ as release version = cloud_sptheme.get_version(release) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [ #disabling documentation of this until module is more mature. "lib/passlib.ext.django.rst" ] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. modindex_common_prefix = [ "passlib." ] # -- Options for all output --------------------------------------------------- todo_include_todos = "hide-todos" not in options keep_warnings = "hide-warnings" not in options issue_tracker_template = "gc:passlib" # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'cloud' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. if html_theme == 'cloud': html_theme_options = { "roottarget": index_doc } gaid = os.environ.get("PASSLIB_GA_ID") if gaid: html_theme_options['googleanalytics_id'] = gaid else: html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. html_theme_path = [cloud_sptheme.get_theme_dir()] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". html_title = project + " v" + release + " Documentation" # A shorter title for the navigation bar. Default is the same as html_title. html_short_title = project + " Documentation" # The name of an image file (relative to this directory) to place at the top # of the sidebar. html_logo = os.path.join("_static", "masthead.png") # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. html_favicon = "logo.ico" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = project + 'Doc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ (index_doc, project + '.tex', project + u' Documentation', u'Assurance Technologies, LLC', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (index_doc, project, project + u' Documentation', [u'Assurance Technologies, LLC'], 1) ] passlib-1.5.3/docs/make.py0000644000175000017500000000032111643466373016533 0ustar biscuitbiscuit00000000000000"Makefile for Sphinx documentation, adapted to python" import os from cloud_sptheme.make_helper import SphinxMaker if __name__ == "__main__": SphinxMaker.execute(root_dir=os.path.join(__file__,os.pardir)) passlib-1.5.3/MANIFEST.in0000644000175000017500000000020211643466373016050 0ustar biscuitbiscuit00000000000000recursive-include docs * include LICENSE README CHANGES passlib/*.cfg passlib/tests/*.cfg prune docs/_build prune *.komodoproject passlib-1.5.3/setup.cfg0000644000175000017500000000030611643754212016130 0ustar biscuitbiscuit00000000000000[upload] sign = true [upload_docs] upload_dir = build/sphinx/html [test] test_suite = nose.collector [nosetests] tests = passlib/tests [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 passlib-1.5.3/README0000644000175000017500000000244211643466373015202 0ustar biscuitbiscuit00000000000000.. -*- restructuredtext -*- ========================== The PassLib Python Library ========================== Welcome ======= Passlib is a password hashing library for Python 2 & 3, which provides cross-platform implementations of over 20 password hashing algorithms, as well as a framework for managing existing password hashes. It's designed to be useful for a wide range of tasks, from verifying a hash found in /etc/shadow, to providing full-strength password hashing for multi-user application. The latest documentation can be found online at ``_. Requirements ============ * Python 2.5 - 2.7 or Python 3 * PyBCrypt or BCryptor (optional; required only if bcrypt support is needed) * M2Crypto (optional; accelerates PBKDF2-based hashes) Installation ============ To install from source using ``setup.py``:: python setup.py install For more detailed installation & testing instructions, see "docs/install.rst" Online Resources ================ * Homepage - http://passlib.googlecode.com * Docs - http://packages.python.org/passlib * Discussion - http://groups.google.com/group/passlib-users * PyPI - http://pypi.python.org/pypi/passlib * Downloads - http://code.google.com/p/passlib/downloads * Source - http://code.google.com/p/passlib/source passlib-1.5.3/CHANGES0000644000175000017500000002503111643754032015304 0ustar biscuitbiscuit00000000000000.. -*- restructuredtext -*- =============== Release History =============== **1.5.3** (2011-10-08) ====================== Bugfix release -- fixes BCrypt padding/verification issue .. _bcrypt-padding-issue: This release fixes a single issue with Passlib's BCrypt support: Many BCrypt hashes generated by Passlib (<= 1.5.2) will not successfully verify under some of the other BCrypt implementations, such as OpenBSD's ``/etc/master.passwd``. *In detail:* BCrypt hashes contain 4 "padding" bits in the encoded salt, and Passlib (<= 1.5.2) generated salts in a manner which frequently set some of the padding bits to 1. While Passlib ignores these bits, many BCrypt implementations perform password verification in a way will reject *all* passwords, if any of the padding bits are set. Thus Passlib's BCrypt salt generation needed to be corrected to ensure compatibility, and a route provided to fix existing hashes already out in the wild [issue 25]. *Changes in this release:* .. currentmodule:: passlib.context * BCrypt hashes generated by Passlib now have all padding bits cleared. * Passlib will continue to accept BCrypt hashes that have padding bits set, but when it encounters them, it will issue a :exc:`UserWarning` recommending that the hash should be fixed (see below). * Applications which use :meth:`CryptContext.verify_and_update` will have any such hashes automatically re-encoded the next time the user logs in. *To fix existing hashes:* If you have BCrypt hashes which might have their padding bits set, you can import :class:`!passlib.hash.bcrypt`, and call ``clean_hash = bcrypt.normhash(hash)``. This function will clear the padding bits of any BCrypt hashes, and should leave all other strings alone. **1.5.2** (2011-09-19) ====================== Minor bugfix release -- mainly Django-related fixes Hashes .. currentmodule:: passlib.hash * *bugfix:* :class:`django_des_crypt` now accepts all :mod:`Hash64 ` characters in it's salts; previously it accepted only lower-case hexidecimal characters [issue 22]. * Additional unittests added for all standard :doc:`Django hashes
`. * :class:`django_des_crypt` now rejects hashes where salt and checksum containing mismatched salt characters. CryptContext .. currentmodule:: passlib.context * *bugfix:* fixed exception in :meth:`CryptPolicy.iter_config` that occurred when iterating over deprecation options. * Added documentation for the (mistakenly undocumented) :meth:`CryptContext.verify_and_update` method. **1.5.1** (2011-08-17) ====================== Minor bugfix release -- now compatible with Google App Engine. * *bugfix:* make ``passlib.hash.__loader__`` attribute writable - needed by Google App Engine (GAE) [issue 19]. * *bugfix:* provide fallback for loading ``passlib/default.cfg`` if :mod:`pkg_resources` is not present, such as for GAE [issue 19]. * *bugfix:* fixed error thrown by CryptContext.verify when issuing min_verify_time warning [issue 17]. * removed min_verify_time setting from custom_app_context, min_verify_time is too host & load dependant to be hardcoded [issue 17]. * under GAE, disable all unittests which require writing to filesystem. * more unittest coverage for :mod:`passlib.apps` and :mod:`passlib.hosts`. * improved version datestamps in build script. **1.5** (2011-07-11) ==================== *"20% more unicode than the leading breakfast cereal"* The main new feature in this release is that Passlib now supports Python 3 (via the 2to3 tool). Everything has been recoded to have better separation between unicode and bytes, and to use unicode internally where possible. When run under Python 2, Passlib 1.5 attempts to provide the same behavior as Passlib 1.4; but when run under Python 3, most functions will return unicode instead of ascii bytes. Besides this major change, there have been some other additions: Hashes * added support for Cryptacular's PBKDF2 format. * added support for the FSHP family of hashes. * added support for using BCryptor as BCrypt backend. * added support for all of Django's hash formats. CryptContext .. currentmodule:: passlib.context * interpolation deprecation: :meth:`CryptPolicy.from_path` and :meth:`CryptPolicy.from_string` now use :class:`!SafeConfigParser` instead of :class:`!ConfigParser`. This may cause some existing config files containing unescaped ``%`` to result in errors; Passlib 1.5 will demote these to warnings, but any extant config files should be updated, as the errors will be fatal in Passlib 1.6. * added encoding keyword to :class:`!CryptPolicy`'s :meth:`!.from_path()`, :meth:`!.from_string`, and :meth:`!.to_string` methods. * both classes in :mod:`passlib.apache` now support specifying an encoding for the username/realm. Documentation * Password Hash API expanded to include explicit :ref:`unicode vs bytes policy `. * Added quickstart guide to documentation. * Various minor improvements. Internals * Added more handler utility functions to reduce code duplication. * Expanded kdf helpers in :mod:`passlib.utils.pbkdf2`. * Removed deprecated parts of :mod:`passlib.utils.handlers`. * Various minor changes to :class:`passlib.utils.handlers.HasManyBackends`; main change is that multi-backend handlers now raise :exc:`~passlib.utils.MissingBackendError` if no backends are available. Other * Builtin tests now use :mod:`!unittest2` if available. * Setup script no longer requires distribute or setuptools. * added (undocumented, experimental) Django app for overriding Django's default hash format, see ``docs/lib/passlib.ext.django.rst`` for more. **1.4** (2011-05-04) ==================== This release contains a large number of changes, both large and small. It adds a number of PBKDF2-based schemes, better support for LDAP-format hashes, improved documentation, and faster load times. In detail... Hashes * added LDAP ``{CRYPT}`` support for all hashes known to be supported by OS crypt() * added 3 custom PBKDF2 schemes for general use, as well as 3 LDAP-compatible versions. * added support for Dwayne Litzenberger's PBKDF2 scheme. * added support for Grub2's PBKDF2 hash scheme. * added support for Atlassian's PBKDF2 password hash * added support for all hashes used by the Roundup Issue Tracker * bsdi_crypt, sha1_crypt now check for OS crypt() support * ``salt_size`` keyword added to encrypt() method of all the hashes which support variable-length salts. * security fix: disabled unix_fallback's "wildcard password" support unless explicitly enabled by user. CryptContext * host_context now dynamically detects which formats OS crypt() supports, instead of guessing based on sys.platform. * added predefined context for Roundup Issue Tracker database. * added CryptContext.verify_and_update() convience method, to make it easier to perform both operations at once. * *bugfix:* fixed NameError in category+min_verify_time border case * apps & hosts modules now use new :class:`LazyCryptContext` wrapper class - this should speed up initial import, and reduce memory by not loading uneeded hashes. Documentation * greatly expanded documentation on how to use CryptContexts. * roughly documented framework for writing & testing custom password handlers. * various minor improvements. Internals * added generate_password() convenience method * refactored framework for building hash handlers, using new mixin-based system. * deprecated old handler framework - will remove in 1.5 * deprecated list_to_bytes & bytes_to_list - not used, will remove in 1.5 Other * password hash api - as part of cleaning up optional attributes specification, renamed a number of them to reduce ambiguity: - renamed *{xxx}_salt_chars* attributes -> *xxx_salt_size* - renamed *salt_charset* -> *salt_chars* - old attributes still present, but deprecated - will remove in 1.5 * password hash api - tightened specifications for salt & rounds parameters, added support for hashes w/ no max salt size. * improved password hash api conformance tests * PyPy compatibility **1.3.1** (2011-03-28) ====================== Minor bugfix release. * bugfix: replaced "sys.maxsize" reference that was failing under py25 * bugfix: fixed default_rounds>max_rounds border case that could cause ValueError during CryptContext.encrypt() * minor documentation changes * added instructions for building html documentation from source **1.3** (2011-03-25) ==================== First public release. * documentation completed * 99% unittest coverage * some refactoring and lots of bugfixes * added support for a number of addtional password schemes: bigcrypt, crypt16, sun md5 crypt, nthash, lmhash, oracle10 & 11, phpass, sha1, generic hex digests, ldap digests. **1.2** (2011-01-06) ==================== .. note:: For this and all previous versions, PassLib did not exist independantly, but as a subpackage of *BPS*, a private & unreleased toolkit library. * many bugfixes * global registry added * transitional release for applications using BPS library. * first truly functional release since splitting from BPS library (see below). **1.0** (2009-12-11) ==================== * CryptContext & CryptHandler framework * added support for: des-crypt, bcrypt (via pybcrypt), postgres, mysql * added unit tests **0.5** (2008-05-10) ==================== * initial production version * consolidated from code scattered across multiple applications * MD5-Crypt, SHA256-Crypt, SHA512-Crypt support passlib-1.5.3/passlib/0000755000175000017500000000000011643754212015745 5ustar biscuitbiscuit00000000000000passlib-1.5.3/passlib/registry.py0000644000175000017500000003557511643466373020216 0ustar biscuitbiscuit00000000000000"""passlib.registry - registry for password hash handlers""" #========================================================= #imports #========================================================= #core import inspect import re import logging; log = logging.getLogger(__name__) from warnings import warn #site #libs from passlib.utils import Undef, is_crypt_handler #pkg #local __all__ = [ "register_crypt_handler_path", "register_crypt_handler", "get_crypt_handler", "list_crypt_handlers", ] #========================================================= #registry proxy object #========================================================= class PasslibRegistryProxy(object): """proxy module passlib.hash this module is in fact an object which lazy-loads the requested password hash algorithm from wherever it has been stored. it acts as a thin wrapper around :func:`passlib.registry.get_crypt_handler`. """ __name__ = "passlib.hash" __package__ = None def __getattr__(self, attr): if attr.startswith("_"): raise AttributeError("missing attribute: %r" % (attr,)) handler = get_crypt_handler(attr, None) if handler: return handler else: raise AttributeError("unknown password hash: %r" % (attr,)) def __setattr__(self, attr, value): if attr.startswith("_"): #NOTE: this is required for GAE, # since it tries to set passlib.hash.__loader__ object.__setattr__(self, attr, value) else: register_crypt_handler(value, name=attr) def __repr__(self): return "" def __dir__(self): #add in handlers that will be lazy-loaded, #otherwise this is std dir implementation attrs = set(dir(self.__class__)) attrs.update(self.__dict__) attrs.update(_handler_locations) return sorted(attrs) #========================================================= #eoc #========================================================= #singleton instance - available publicallly as 'passlib.hash' _proxy = PasslibRegistryProxy() #========================================================== #internal registry state #========================================================== #: dict mapping name -> handler for all loaded handlers. uses proxy's dict so they stay in sync. _handlers = _proxy.__dict__ #: dict mapping name -> (module path, attribute) for lazy-loading of handlers _handler_locations = { #NOTE: this is a hardcoded list of the handlers built into passlib, #applications should call register_crypt_handler_location() to add their own "apr_md5_crypt": ("passlib.handlers.md5_crypt", "apr_md5_crypt"), "atlassian_pbkdf2_sha1": ("passlib.handlers.pbkdf2", "atlassian_pbkdf2_sha1"), "bcrypt": ("passlib.handlers.bcrypt", "bcrypt"), "bigcrypt": ("passlib.handlers.des_crypt", "bigcrypt"), "bsdi_crypt": ("passlib.handlers.des_crypt", "bsdi_crypt"), "cta_pbkdf2_sha1": ("passlib.handlers.pbkdf2", "cta_pbkdf2_sha1"), "crypt16": ("passlib.handlers.des_crypt", "crypt16"), "des_crypt": ("passlib.handlers.des_crypt", "des_crypt"), "django_salted_sha1": ("passlib.handlers.django", "django_salted_sha1"), "django_salted_md5":("passlib.handlers.django", "django_salted_md5"), "django_des_crypt": ("passlib.handlers.django", "django_des_crypt"), "django_disabled": ("passlib.handlers.django", "django_disabled"), "dlitz_pbkdf2_sha1":("passlib.handlers.pbkdf2", "dlitz_pbkdf2_sha1"), "fshp": ("passlib.handlers.fshp", "fshp"), "grub_pbkdf2_sha512": ("passlib.handlers.pbkdf2", "grub_pbkdf2_sha512"), "hex_md4": ("passlib.handlers.digests", "hex_md4"), "hex_md5": ("passlib.handlers.digests", "hex_md5"), "hex_sha1": ("passlib.handlers.digests", "hex_sha1"), "hex_sha256": ("passlib.handlers.digests", "hex_sha256"), "hex_sha512": ("passlib.handlers.digests", "hex_sha512"), "ldap_plaintext": ("passlib.handlers.ldap_digests","ldap_plaintext"), "ldap_md5": ("passlib.handlers.ldap_digests","ldap_md5"), "ldap_sha1": ("passlib.handlers.ldap_digests","ldap_sha1"), "ldap_hex_md5": ("passlib.handlers.roundup", "ldap_hex_md5"), "ldap_hex_sha1": ("passlib.handlers.roundup", "ldap_hex_sha1"), "ldap_salted_md5": ("passlib.handlers.ldap_digests","ldap_salted_md5"), "ldap_salted_sha1": ("passlib.handlers.ldap_digests","ldap_salted_sha1"), "ldap_des_crypt": ("passlib.handlers.ldap_digests","ldap_des_crypt"), "ldap_bsdi_crypt": ("passlib.handlers.ldap_digests","ldap_bsdi_crypt"), "ldap_md5_crypt": ("passlib.handlers.ldap_digests","ldap_md5_crypt"), "ldap_bcrypt": ("passlib.handlers.ldap_digests","ldap_bcrypt"), "ldap_sha1_crypt": ("passlib.handlers.ldap_digests","ldap_sha1_crypt"), "ldap_sha256_crypt":("passlib.handlers.ldap_digests","ldap_sha256_crypt"), "ldap_sha512_crypt":("passlib.handlers.ldap_digests","ldap_sha512_crypt"), "ldap_pbkdf2_sha1": ("passlib.handlers.pbkdf2", "ldap_pbkdf2_sha1"), "ldap_pbkdf2_sha256": ("passlib.handlers.pbkdf2", "ldap_pbkdf2_sha256"), "ldap_pbkdf2_sha512": ("passlib.handlers.pbkdf2", "ldap_pbkdf2_sha512"), "md5_crypt": ("passlib.handlers.md5_crypt", "md5_crypt"), "mysql323": ("passlib.handlers.mysql", "mysql323"), "mysql41": ("passlib.handlers.mysql", "mysql41"), "nthash": ("passlib.handlers.nthash", "nthash"), "oracle10": ("passlib.handlers.oracle", "oracle10"), "oracle11": ("passlib.handlers.oracle", "oracle11"), "pbkdf2_sha1": ("passlib.handlers.pbkdf2", "pbkdf2_sha1"), "pbkdf2_sha256": ("passlib.handlers.pbkdf2", "pbkdf2_sha256"), "pbkdf2_sha512": ("passlib.handlers.pbkdf2", "pbkdf2_sha512"), "phpass": ("passlib.handlers.phpass", "phpass"), "plaintext": ("passlib.handlers.misc", "plaintext"), "postgres_md5": ("passlib.handlers.postgres", "postgres_md5"), "roundup_plaintext":("passlib.handlers.roundup", "roundup_plaintext"), "sha1_crypt": ("passlib.handlers.sha1_crypt", "sha1_crypt"), "sha256_crypt": ("passlib.handlers.sha2_crypt", "sha256_crypt"), "sha512_crypt": ("passlib.handlers.sha2_crypt", "sha512_crypt"), "sun_md5_crypt": ("passlib.handlers.sun_md5_crypt","sun_md5_crypt"), "unix_fallback": ("passlib.handlers.misc", "unix_fallback"), } #: master regexp for detecting valid handler names _name_re = re.compile("^[a-z][_a-z0-9]{2,}$") #: names which aren't allowed for various reasons (mainly keyword conflicts in CryptContext) _forbidden_names = frozenset(["policy", "context", "all", "default", "none"]) #========================================================== #registry frontend functions #========================================================== def register_crypt_handler_path(name, path): """register location to lazy-load handler when requested. custom hashes may be registered via :func:`register_crypt_handler`, or they may be registered by this function, which will delay actually importing and loading the handler until a call to :func:`get_crypt_handler` is made for the specified name. :arg name: name of handler :arg path: module import path the specified module path should contain a password hash handler called :samp:`{name}`, or the path may contain a colon, specifying the module and module attribute to use. for example, the following would cause ``get_handler("myhash")`` to look for a class named ``myhash`` within the ``myapp.helpers`` module:: >>> from passlib.registry import registry_crypt_handler_path >>> registry_crypt_handler_path("myhash", "myapp.helpers") ...while this form would cause ``get_handler("myhash")`` to look for a class name ``MyHash`` within the ``myapp.helpers`` module:: >>> from passlib.registry import registry_crypt_handler_path >>> registry_crypt_handler_path("myhash", "myapp.helpers:MyHash") """ global _handler_locations if ':' in path: modname, modattr = path.split(":") else: modname, modattr = path, name _handler_locations[name] = (modname, modattr) def register_crypt_handler(handler, force=False, name=None): """register password hash handler. this method immediately registers a handler with the internal passlib registry, so that it will be returned by :func:`get_crypt_handler` when requested. :arg handler: the password hash handler to register :param force: force override of existing handler (defaults to False) :param name: [internal kwd] if specified, ensures ``handler.name`` matches this value, or raises :exc:`ValueError`. :raises TypeError: if the specified object does not appear to be a valid handler. :raises ValueError: if the specified object's name (or other required attributes) contain invalid values. :raises KeyError: if a (different) handler was already registered with the same name, and ``force=True`` was not specified. """ global _handlers, _name_re #validate handler if not is_crypt_handler(handler): raise TypeError("object does not appear to be a crypt handler: %r" % (handler,)) assert handler, "crypt handlers must be boolean True: %r" % (handler,) #if name specified, make sure it matched #(this is mainly used as a check to help __setattr__) if name: if name != handler.name: raise ValueError("handlers must be stored only under their own name") else: name = handler.name #validate name if not name: raise ValueError("name is null: %r" % (name,)) if name.lower() != name: raise ValueError("name must be lower-case: %r" % (name,)) if not _name_re.match(name): raise ValueError("invalid characters in name (must be 3+ characters, begin with a-z, and contain only underscore, a-z, 0-9): %r" % (name,)) if '__' in name: raise ValueError("name may not contain double-underscores: %r" % (name,)) if name in _forbidden_names: raise ValueError("that name is not allowed: %r" % (name,)) #check for existing handler other = _handlers.get(name) if other: if other is handler: return #already registered if force: log.warning("overriding previous handler registered to name %r: %r", name, other) else: raise KeyError("a handler has already registered for the name %r: %r (use force=True to override)" % (name, other)) #register handler in dict _handlers[name] = handler log.info("registered crypt handler %r: %r", name, handler) def get_crypt_handler(name, default=Undef): """return handler for specified password hash scheme. this method looks up a handler for the specified scheme. if the handler is not already loaded, it checks if the location is known, and loads it first. :arg name: name of handler to return :param default: optional default value to return if no handler with specified name is found. :raises KeyError: if no handler matching that name is found, and no default specified, a KeyError will be raised. :returns: handler attached to name, or default value (if specified). """ global _handlers, _handler_locations #check if handler loaded handler = _handlers.get(name) if handler: return handler #normalize name (and if changed, check dict again) alt = name.replace("-","_").lower() if alt != name: warn("handler names should be lower-case, and use underscores instead of hyphens: %r => %r" % (name, alt)) name = alt #check if handler loaded handler = _handlers.get(name) if handler: return handler #check if lazy load mapping has been specified for this driver route = _handler_locations.get(name) if route: modname, modattr = route #try to load the module - any import errors indicate runtime config, # either missing packages, or bad path provided to register_crypt_handler_path() mod = __import__(modname, None, None, ['dummy'], 0) #first check if importing module triggered register_crypt_handler(), #(though this is discouraged due to it's magical implicitness) handler = _handlers.get(name) if handler: #XXX: issue deprecation warning here? assert is_crypt_handler(handler), "unexpected object: name=%r object=%r" % (name, handler) return handler #then get real handler & register it handler = getattr(mod, modattr) register_crypt_handler(handler, name=name) return handler #fail! if default is Undef: raise KeyError("no crypt handler found for algorithm: %r" % (name,)) else: return default def list_crypt_handlers(loaded_only=False): """return sorted list of all known crypt handler names. :param loaded_only: if ``True``, only returns names of handlers which have actually been loaded. :returns: list of names of all known handlers """ global _handlers, _handler_locations names = set(_handlers) if not loaded_only: names.update(_handler_locations) return sorted(names) #NOTE: these two functions mainly exist just for the unittests... def has_crypt_handler(name, loaded_only=False): """check if handler name is known. this is only useful for two cases: * quickly checking if handler has already been loaded * checking if handler exists, without actually loading it :arg name: name of handler :param loaded_only: if ``True``, returns False if handler exists but hasn't been loaded """ global _handlers, _handler_locations return (name in _handlers) or (not loaded_only and name in _handler_locations) def _unload_handler_name(name, locations=True): """unloads a handler from the registry. .. warning:: this is an internal function, used only by the unittests. if loaded handler is found with specified name, it's removed. if path to lazy load handler is found, its' removed. missing names are a noop. :arg name: name of handler to unload :param locations: if False, won't purge registered handler locations (default True) """ global _handlers, _handler_locations if name in _handlers: del _handlers[name] if locations and name in _handler_locations: del _handler_locations[name] #========================================================= # eof #========================================================= passlib-1.5.3/passlib/win32.py0000644000175000017500000000421511643466373017273 0ustar biscuitbiscuit00000000000000"""passlib.win32 - MS Windows support the LMHASH and NTHASH algorithms are used in various windows related contexts, but generally not in a manner compatible with how passlib is structured. in particular, they have no identifying marks, both being 32 bytes of binary data. thus, they can't be easily identified in a context with other hashes, so a CryptHandler hasn't been defined for them. this module provided two functions to aid in any use-cases which exist. .. warning:: these functions should not be used for new code unless an existing system requires them, they are both known broken, and are beyond insecure on their own. .. autofunction:: raw_lmhash .. autofunction:: raw_nthash See also :mod:`passlib.hash.nthash`. """ #========================================================= #imports #========================================================= #core from binascii import hexlify #site #pkg from passlib.utils import to_native_str, b from passlib.utils.des import des_encrypt_block from passlib.hash import nthash #local __all__ = [ "nthash", "raw_lmhash", "raw_nthash", ] #========================================================= #helpers #========================================================= LM_MAGIC = b("KGS!@#$%") raw_nthash = nthash.raw_nthash def raw_lmhash(secret, encoding="ascii", hex=False): "encode password using des-based LMHASH algorithm; returns string of raw bytes, or unicode hex" #NOTE: various references say LMHASH uses the OEM codepage of the host # for it's encoding. until a clear reference is found, # as well as a path for getting the encoding, # letting this default to "ascii" to prevent incorrect hashes # from being made w/o user explicitly choosing an encoding. if isinstance(secret, unicode): secret = secret.encode(encoding) ns = secret.upper()[:14] + b("\x00") * (14-len(secret)) out = des_encrypt_block(ns[:7], LM_MAGIC) + des_encrypt_block(ns[7:], LM_MAGIC) return hexlify(out).decode("ascii") if hex else out #========================================================= #eoc #========================================================= passlib-1.5.3/passlib/hosts.py0000644000175000017500000000601211643466373017466 0ustar biscuitbiscuit00000000000000"""passlib.hosts""" #========================================================= #imports #========================================================= #core import sys from warnings import warn #pkg from passlib.context import LazyCryptContext from passlib.registry import get_crypt_handler from passlib.utils import os_crypt, unix_crypt_schemes #local __all__ = [ "linux_context", "linux2_context", "openbsd_context", "netbsd_context", "freebsd_context", "host_context", ] #========================================================= #linux support #========================================================= #known platform names - linux2 linux_context = linux2_context = LazyCryptContext( schemes = [ "sha512_crypt", "sha256_crypt", "md5_crypt", "des_crypt", "unix_fallback" ], deprecated = [ "des_crypt" ], ) #========================================================= #bsd support #========================================================= #known platform names - # freebsd2 # freebsd3 # freebsd4 # freebsd5 # freebsd6 # freebsd7 # # netbsd1 #referencing source via -http://fxr.googlebit.com # freebsd 6,7,8 - des, md5, bcrypt, nthash # netbsd - des, ext, md5, bcrypt, sha1 # openbsd - des, ext, md5, bcrypt freebsd_context = LazyCryptContext([ "bcrypt", "md5_crypt", "nthash", "des_crypt", "unix_fallback" ]) openbsd_context = LazyCryptContext([ "bcrypt", "md5_crypt", "bsdi_crypt", "des_crypt", "unix_fallback" ]) netbsd_context = LazyCryptContext([ "bcrypt", "sha1_crypt", "md5_crypt", "bsdi_crypt", "des_crypt", "unix_fallback" ]) #========================================================= #current host #========================================================= if os_crypt: #NOTE: this is basically mimicing the output of os crypt(), #except that it uses passlib's (usually stronger) defaults settings, #and can be introspected and used much more flexibly. def _iter_os_crypt_schemes(): "helper which iterates over supported os_crypt schemes" found = False for name in unix_crypt_schemes: handler = get_crypt_handler(name) if handler.has_backend("os_crypt"): found = True yield name if found: #only offer fallback if there's another scheme in front, #as this can't actually hash any passwords yield "unix_fallback" else: #no idea what OS this could happen on, but just in case... warn("crypt.crypt() function is present, but doesn't support any formats known to passlib!") host_context = LazyCryptContext(_iter_os_crypt_schemes()) #========================================================= #other platforms #========================================================= #known platform strings - #aix3 #aix4 #atheos #beos5 #darwin #generic #hp-ux11 #irix5 #irix6 #mac #next3 #os2emx #riscos #sunos5 #unixware7 #========================================================= #eof #========================================================= passlib-1.5.3/passlib/default.cfg0000644000175000017500000000155611643466373020071 0ustar biscuitbiscuit00000000000000[passlib] # # this is the PassLib default policy configuration, used by CryptContext # objects which don't have an explicit base policy specified. # the goal of this default configuration is not to set any preferred schemes, # but provide sane defaults (eg rounds) for all the supported algorithms. # #TODO: need to generate min rounds for specific cpu speed & verify time limitations all.vary_rounds = 10%% bsdi_crypt.default_rounds = 30000 bcrypt.default_rounds = 10 sha1_crypt.default_rounds = 30000 sun_md5_crypt.default_rounds = 30000 sha256_crypt.default_rounds = 30000 sha512_crypt.default_rounds = 30000 ldap_bsdi_crypt.default_rounds = 30000 ldap_bcrypt.default_rounds = 10 ldap_sha1_crypt.default_rounds = 30000 ldap_sun_md5_crypt.default_rounds = 30000 ldap_sha256_crypt.default_rounds = 30000 ldap_sha512_crypt.default_rounds = 30000 phpass.default_rounds = 10 passlib-1.5.3/passlib/hash.py0000644000175000017500000000166411643466373017261 0ustar biscuitbiscuit00000000000000"""passlib.hash stub NOTE: this module does not actually contain any hashes. this file is a stub which is replaced by a proxy object, which lazy-loads hashes as requested. the actually implementations of hashes (at least, those built into passlib) are stored in the passlib.handlers subpackage. """ #NOTE: could support 'non-lazy' version which just imports # all schemes known to list_crypt_handlers() #========================================================= #import special proxy object as 'passlib.hash' module #========================================================= #import proxy object, and replace this module with it. #this should cause any import commands to return that object, #not this module from passlib.registry import _proxy import sys sys.modules['passlib.hash'] = _proxy del sys, _proxy #========================================================= #eoc #========================================================= passlib-1.5.3/passlib/_setup/0000755000175000017500000000000011643754212017244 5ustar biscuitbiscuit00000000000000passlib-1.5.3/passlib/_setup/stamp.py0000644000175000017500000000366611643466373020765 0ustar biscuitbiscuit00000000000000"update version string during build" #========================================================= # imports #========================================================= from __future__ import with_statement #core import os import re import time from distutils.dist import Distribution #pkg #local __all__ = [ "stamp_source", "stamp_distutils_output", ] #========================================================= # helpers #========================================================= def get_command_class(opts, name): return opts['cmdclass'].get(name) or Distribution().get_command_class(name) def stamp_source(base_dir, version, dry_run=False): "update version string in passlib dist" path = os.path.join(base_dir, "passlib", "__init__.py") with open(path) as fh: input = fh.read() output = re.sub('(?m)^__version__\s*=.*$', '__version__ = ' + repr(version), input) assert output != input, "failed to match" if not dry_run: os.unlink(path) # sdist likes to use hardlinks with open(path, "w") as fh: fh.write(output) def stamp_distutils_output(opts, version): # subclass buildpy to update version string in source _build_py = get_command_class(opts, "build_py") class build_py(_build_py): def build_packages(self): _build_py.build_packages(self) stamp_source(self.build_lib, version, self.dry_run) opts['cmdclass']['build_py'] = build_py # subclass sdist to do same thing _sdist = get_command_class(opts, "sdist") class sdist(_sdist): def make_release_tree(self, base_dir, files): _sdist.make_release_tree(self, base_dir, files) stamp_source(base_dir, version, self.dry_run) opts['cmdclass']['sdist'] = sdist #========================================================= # eof #========================================================= passlib-1.5.3/passlib/_setup/cond2to3.py0000644000175000017500000001170011643466373021260 0ustar biscuitbiscuit00000000000000"""passlib.setup.cond2to3 - moneypatches 2to3 to provide conditional macros, ala SQLAlchemy""" #========================================================= #imports #========================================================= #core from lib2to3.refactor import RefactoringTool import re #site #local __all__ = [ "patch2to3", ] #========================================================= #macro preprocessor #========================================================= py3k_start_re = re.compile(r"^(\s*)# Py3K #", re.I) py3k_stop_re = re.compile(r"^(\s*)# end Py3K #", re.I) py2k_start_re = re.compile(r"^(\s*)# Py2K #", re.I) py2k_stop_re = re.compile(r"^(\s*)# end Py2K #", re.I) bare_comment_re = re.compile(r"^(\s*)#(.*)") bare_re = re.compile(r"^(\s*)(.*)") def preprocess(data, name): #TODO: add flag so this can also function in reverse, for 3to2 changed = False lines = data.split("\n") state = 0 #0: parsing normally, looking for start-p3k or start-py2k #1: in Py3K block - removing comment chars until end-py3k #2: in Py2K block - adding comment chars until end-py2k idx = 0 indent = '' while idx < len(lines): line = lines[idx] #hack to detect ''"abc" strings - using this as py25-compat way to indicate bytes. #should really turn into a proper fixer. #also, this check is really weak, and might fail in some cases if '\'\'".*"' in line: line = lines[idx] = line.replace("''", "b") changed = True #check for py3k start marker m = py3k_start_re.match(line) if m: if state in (0,2): ident = m.group(1) state = 1 idx += 1 continue #states 1 this is an error... raise SyntaxError("unexpected py3k-start marker on line %d of %r: %r" % (idx, name, line)) #check for py3k stop marker if py3k_stop_re.match(line): if state == 1: state = 0 idx += 1 continue #states 0,2 this is an error... raise SyntaxError("unexpected py3k-stop marker on line %d of %r: %r" % (idx, name, line)) #check for py2k start marker m = py2k_start_re.match(line) if m: if state in (0,1): ident = m.group(1) state = 2 idx += 1 continue #states 2 this is an error... raise SyntaxError("unexpected py2k-start marker on line %d of %r: %r" % (idx, name, line)) #check for py2k end marker if py2k_stop_re.match(line): if state == 2: state = 0 idx += 1 continue #states 0,1 this is an error... raise SyntaxError("unexpected py2k-stop marker on line %d of %r: %r" % (idx, name, line)) #state 0 - leave non-marker lines alone if state == 0: idx += 1 continue #state 1 - uncomment comment lines, throw error on bare lines if state == 1: m = bare_comment_re.match(line) if not m: raise SyntaxError("unexpected non-comment in py3k block on line %d of %r: %r" % (idx,name, line)) pad, content = m.group(1,2) lines[idx] = pad + content changed = True idx += 1 continue #state 2 - comment out all lines if state == 2: m = bare_re.match(line) if not m: raise RuntimeError("unexpected failure to parse line %d of %r: %r" % (idx, name, line)) pad, content = m.group(1,2) if pad.startswith(ident): #try to put comments on same level content = pad[len(ident):] + content pad = ident lines[idx] = "%s#%s" % (pad,content) changed = True idx += 1 continue #should never get here raise AssertionError("invalid state: %r" % (state,)) if changed: return "\n".join(lines) else: return data orig_rs = RefactoringTool.refactor_string def refactor_string(self, data, name): "replacement for RefactoringTool.refactor_string which honors conditional includes" newdata = preprocess(data, name) tree = orig_rs(self, newdata, name) if tree and newdata != data: tree.was_changed = True return tree #========================================================= #main #========================================================= def patch2to3(): "frontend to patch preprocessor into lib2to3" RefactoringTool.refactor_string = refactor_string #helper for development purposes - runs 2to3 w/ patch if __name__ == "__main__": import sys from lib2to3.main import main patch2to3() sys.exit(main("lib2to3.fixes")) #========================================================= #eof #========================================================= passlib-1.5.3/passlib/_setup/__init__.py0000644000175000017500000000025711643466373021371 0ustar biscuitbiscuit00000000000000"""passlib.setup - helpers used by passlib's setup.py script note that unlike the rest of passlib, the code in this package must work *unaltered* under both python 2 & 3 """ passlib-1.5.3/passlib/_setup/docdist.py0000644000175000017500000000572711643466373021272 0ustar biscuitbiscuit00000000000000"custom command to build doc.zip file" #========================================================= #imports #========================================================= #core import os from distutils import dir_util from distutils.cmd import Command from distutils.errors import * from distutils.spawn import spawn #local __all__ = [ "docdist" ] #========================================================= #command #========================================================= class docdist(Command): description = "create zip file containing standalone html docs" user_options = [ ('build-dir=', None, 'Build directory'), ('dist-dir=', 'd', "directory to put the source distribution archive(s) in " "[default: dist]"), ('format=', 'f', "archive format to create (tar, ztar, gztar, zip)"), ('sign', 's', 'sign files using gpg'), ('identity=', 'i', 'GPG identity used to sign files'), ] def initialize_options(self): self.build_dir = None self.dist_dir = None self.format = None self.keep_temp = False self.sign = False self.identity = None def finalize_options(self): if self.identity and not self.sign: raise DistutilsOptionError( "Must use --sign for --identity to have meaning" ) if self.build_dir is None: cmd = self.get_finalized_command('build') self.build_dir = os.path.join(cmd.build_base, 'docdist') if not self.dist_dir: self.dist_dir = "dist" if not self.format: self.format = "zip" def run(self): # call build sphinx to build docs self.run_command("build_sphinx") cmd = self.get_finalized_command("build_sphinx") source_dir = cmd.builder_target_dir # copy to directory with appropriate name dist = self.distribution arc_name = "%s-docs-%s" % (dist.get_name(), dist.get_version()) tmp_dir = os.path.join(self.build_dir, arc_name) if os.path.exists(tmp_dir): dir_util.remove_tree(tmp_dir, dry_run=self.dry_run) self.copy_tree(source_dir, tmp_dir, preserve_symlinks=True) # make archive from dir arc_base = os.path.join(self.dist_dir, arc_name) self.arc_filename = self.make_archive(arc_base, self.format, self.build_dir) # Sign if requested if self.sign: gpg_args = ["gpg", "--detach-sign", "-a", self.arc_filename] if self.identity: gpg_args[2:2] = ["--local-user", self.identity] spawn(gpg_args, dry_run=self.dry_run) #cleanup if not self.keep_temp: dir_util.remove_tree(tmp_dir, dry_run=self.dry_run) #========================================================= #eof #========================================================= passlib-1.5.3/passlib/context.py0000644000175000017500000013505211643754032020011 0ustar biscuitbiscuit00000000000000"""passlib.context - CryptContext implementation""" #========================================================= #imports #========================================================= from __future__ import with_statement from passlib.utils import py32_lang #core from cStringIO import StringIO # Py2k # #note: importing ConfigParser to handle passlib 1.4 / earlier files from ConfigParser import SafeConfigParser,ConfigParser,InterpolationSyntaxError # Py3k # #if py32_lang: # #Py3.2 removed old ConfigParser, put SafeConfigParser in it's place # from ConfigParser import ConfigParser as SafeConfigParser #else: # from ConfigParser import SafeConfigParser # end Py3k # import inspect import re import hashlib from math import log as logb import logging; log = logging.getLogger(__name__) import time import os from warnings import warn #site try: from pkg_resources import resource_string except ImportError: #not available eg: under GAE resource_string = None #libs from passlib.registry import get_crypt_handler, _unload_handler_name from passlib.utils import to_bytes, to_unicode, bytes, Undef, \ is_crypt_handler, splitcomma, rng #pkg #local __all__ = [ 'CryptPolicy', 'CryptContext', ] #========================================================= #crypt policy #========================================================= #-------------------------------------------------------- #constants controlling parsing of special kwds #-------------------------------------------------------- #: CryptContext kwds which aren't allowed to have category specifiers _forbidden_category_context_options = frozenset([ "schemes", ]) #NOTE: forbidding 'schemes' because it would really complicate the behavior # of CryptContext.identify & CryptContext.lookup. # most useful behaviors here can be had by overriding deprecated # and default, anyways. #: hash settings which aren't allowed to be set via policy _forbidden_hash_options = frozenset([ "salt" ]) #NOTE: doing this for security purposes, why would you ever want a fixed salt? #: CryptContext kwds which should be parsed into comma separated list of strings _context_comma_options = frozenset([ "schemes", "deprecated" ]) #-------------------------------------------------------- #parsing helpers #-------------------------------------------------------- def _parse_policy_key(key): "helper to normalize & parse policy keys; returns ``(category, name, option)``" orig = key if '.' not in key and '__' in key: #lets user specifiy programmatically (since python doesn't allow '.') key = key.replace("__", ".") parts = key.split(".") if len(parts) == 1: cat = None name = "context" opt, = parts elif len(parts) == 2: cat = None name, opt = parts elif len(parts) == 3: cat, name, opt = parts else: raise KeyError("keys must have 0..2 separators: %r" % (orig,)) if cat == "default": cat = None assert name assert opt return cat, name, opt def _parse_policy_value(cat, name, opt, value): "helper to parse policy values" #FIXME: kinda primitive to parse things this way :| if name == "context": if opt in _context_comma_options: if isinstance(value, str): return splitcomma(value) elif opt == "min_verify_time": return float(value) return value else: #try to coerce everything to int try: return int(value) except ValueError: return value def parse_policy_items(source): "helper to parse CryptPolicy options" # py2k # if hasattr(source, "iteritems"): source = source.iteritems() # py3k # #if hasattr(source, "items"): # source = source.items() # end py3k # for key, value in source: cat, name, opt = _parse_policy_key(key) if name == "context": if cat and opt in _forbidden_category_context_options: raise KeyError("%r context option is not allowed per-category" % (opt,)) else: if opt in _forbidden_hash_options: raise KeyError("%r handler option is not allowed to be set via a policy object" % (opt,)) value = _parse_policy_value(cat, name, opt, value) yield cat, name, opt, value # Py2k # def _is_legacy_parse_error(err): "helper for parsing config files" #NOTE: passlib 1.4 and earlier used ConfigParser, # when they should have been using SafeConfigParser # (which passlib 1.5+ switched to) # this has no real security effects re: passlib, # but some 1.4 config files that have "vary_rounds = 10%" # may throw an error under SafeConfigParser, # and should read "vary_rounds = 10%%" # # passlib 1.6 and on will only use SafeConfigParser, # but passlib 1.5 tries to detect the above 10% error, # issue a warning, and retry w/ ConfigParser, # for backward compat. # # this function's purpose is to encapsulate that # backward-compat behavior. value = err.args[0] #'%' must be followed by '%' or '(', found: '%' if value == "'%' must be followed by '%' or '(', found: '%'": return True return False # end Py2k # #-------------------------------------------------------- #policy class proper #-------------------------------------------------------- class CryptPolicy(object): """stores configuration options for a CryptContext object. The CryptPolicy class constructor accepts a dictionary of keywords, which can include all the options listed in the :ref:`list of crypt context options `. Constructors ============ In addition to passing in keywords directly, CryptPolicy objects can be constructed by the following methods: .. automethod:: from_path .. automethod:: from_string .. automethod:: from_source .. automethod:: from_sources .. automethod:: replace Introspection ============= .. automethod:: has_schemes .. automethod:: schemes .. automethod:: iter_handlers .. automethod:: get_handler .. automethod:: get_options .. automethod:: handler_is_deprecated .. automethod:: get_min_verify_time Exporting ========= .. automethod:: iter_config .. automethod:: to_dict .. automethod:: to_file .. automethod:: to_string .. note:: Instances of CryptPolicy should be treated as immutable. Use the :meth:`replace` method to mutate existing instances. """ #========================================================= #class methods #========================================================= @classmethod def from_path(cls, path, section="passlib", encoding="utf-8"): """create new policy from specified section of an ini file. :arg path: path to ini file :param section: option name of section to read from. :arg encoding: optional encoding (defaults to utf-8) :raises EnvironmentError: if the file cannot be read :returns: new CryptPolicy instance. """ #NOTE: we want config parser object to have native strings as keys. # so we parse as bytes under py2, and unicode under py3. # # encoding issues are handled under py2 via to_bytes(), # which ensures everything is utf-8 internally. # Py2k # if encoding == "utf-8": #we want utf-8 anyways, so just load file in raw mode. with open(path, "rb") as stream: return cls._from_stream(stream, section, path) else: #kinda hacked - load whole file, transcode, and parse. with open(path, "rb") as stream: source = stream.read() source = source.decode(encoding).encode("utf-8") return cls._from_stream(StringIO(source), section, path) # Py3k # #with open(path, "r", encoding=encoding) as stream: # return cls._from_stream(stream, section, path) # end Py3k # @classmethod def from_string(cls, source, section="passlib", encoding="utf-8"): """create new policy from specified section of an ini-formatted string. :arg source: bytes/unicode string containing ini-formatted content. :param section: option name of section to read from. :arg encoding: optional encoding if source is bytes (defaults to utf-8) :returns: new CryptPolicy instance. """ #NOTE: we want config parser object to have native strings as keys. # so we parse as bytes under py2, and unicode under py3. # to handle encoding issues under py2, we use # "to_bytes()" to transcode to utf-8 as needed. # Py2k # source = to_bytes(source, "utf-8", source_encoding=encoding, errname="source") # Py3k # #source = to_unicode(source, encoding, errname="source") # end Py3k # return cls._from_stream(StringIO(source), section, "") @classmethod def _from_stream(cls, stream, section, filename=None): "helper for from_string / from_path" # Py2k # pos = stream.tell() # end Py2k # p = SafeConfigParser() if py32_lang: # Py3.2 deprecated readfp p.read_file(stream, filename or "") else: p.readfp(stream, filename or "") # Py2k # try: items = p.items(section) except InterpolationSyntaxError, err: if not _is_legacy_parse_error(err): raise #support for deprecated 1.4 behavior, will be removed in 1.6 if filename: warn("from_path(): the file %r contains an unescaped '%%', this will be fatal in passlib 1.6" % (filename,), stacklevel=3) else: warn("from_string(): the provided string contains an unescaped '%', this will be fatal in passlib 1.6", stacklevel=3) p = ConfigParser() stream.seek(pos) p.readfp(stream) items = p.items(section) # py3k # #items = p.items(section) # end py3k # return cls(**dict(items)) @classmethod def from_source(cls, source): """create new policy from input. :arg source: source may be a dict, CryptPolicy instance, filepath, or raw string. the exact type will be autodetected, and the appropriate constructor called. :raises TypeError: if source cannot be identified. :returns: new CryptPolicy instance. """ if isinstance(source, cls): #NOTE: can just return source unchanged, #since we're treating CryptPolicy objects as read-only return source elif isinstance(source, dict): return cls(**source) elif isinstance(source, (bytes,unicode)): #FIXME: this autodetection makes me uncomfortable... if any(c in source for c in "\n\r\t") or not source.strip(" \t./\;:"): #none of these chars should be in filepaths, but should be in config string return cls.from_string(source) else: #other strings should be filepath return cls.from_path(source) else: raise TypeError("source must be CryptPolicy, dict, config string, or file path: %r" % (type(source),)) @classmethod def from_sources(cls, sources): """create new policy from list of existing policy objects. this method takes multiple sources and composites them on top of eachother, returning a single resulting CryptPolicy instance. this allows default policies to be specified, and then overridden on a per-context basis. :arg sources: list of sources to build policy from, elements may be any type accepted by :meth:`from_source`. :returns: new CryptPolicy instance """ #check for no sources - should we return blank policy in that case? if len(sources) == 0: #XXX: er, would returning an empty policy be the right thing here? raise ValueError("no sources specified") #check if only one source if len(sources) == 1: return cls.from_source(sources[0]) #else, build up list of kwds by parsing each source kwds = {} for source in sources: policy = cls.from_source(source) kwds.update(policy.iter_config(resolve=True)) #build new policy return cls(**kwds) def replace(self, *args, **kwds): """return copy of policy, with specified options replaced by new values. this is essentially a convience wrapper around :meth:`from_sources`, except that it always inserts the current policy as the first element in the list; this allows easily making minor changes from an existing policy object. :param \*args: optional list of sources as accepted by :meth:`from_sources`. :param \*\*kwds: optional specific options to override in the new policy. :returns: new CryptPolicy instance """ sources = [ self ] if args: sources.extend(args) if kwds: sources.append(kwds) return CryptPolicy.from_sources(sources) #========================================================= #instance attrs #========================================================= #NOTE: all category dictionaries below will have a minimum of 'None' as a key #:list of all handlers, in order they will be checked when identifying (reverse of order specified) _handlers = None #list of password hash handlers instances. #:dict mapping category -> default handler for that category _default = None #:dict mapping category -> set of handler names which are deprecated for that category _deprecated = None #:dict mapping category -> min verify time _min_verify_time = None #:dict mapping category -> dict mapping hash name -> dict of options for that hash # if a category is specified, particular hash names will be mapped ONLY if that category # has options which differ from the default options. _options = None #:dict mapping (handler name, category) -> dict derived from options. # this is used to cache results of the get_option() method _cache = None #========================================================= #init #========================================================= def __init__(self, **kwds): self._from_dict(kwds) #========================================================= #internal init helpers #========================================================= def _from_dict(self, kwds): "configure policy from constructor keywords" # #init cache & options # context_options = {} options = self._options = {None:{"context":context_options}} self._cache = {} # #normalize & sort keywords # for cat, name, opt, value in parse_policy_items(kwds): copts = options.get(cat) if copts is None: copts = options[cat] = {} config = copts.get(name) if config is None: copts[name] = {opt:value} else: config[opt] = value # #parse list of schemes, and resolve to handlers. # schemes = context_options.get("schemes") or [] handlers = self._handlers = [] handler_names = set() for scheme in schemes: #resolve & validate handler if is_crypt_handler(scheme): handler = scheme else: handler = get_crypt_handler(scheme) name = handler.name if not name: raise TypeError("handler lacks name: %r" % (handler,)) #check name hasn't been re-used if name in handler_names: #XXX: should this just be a warning ? raise KeyError("multiple handlers with same name: %r" % (name,)) #add to handler list handlers.append(handler) handler_names.add(name) # #build _deprecated & _default maps # dmap = self._deprecated = {} fmap = self._default = {} mvmap = self._min_verify_time = {} for cat, config in options.iteritems(): kwds = config.pop("context", None) if not kwds: continue #list of deprecated schemes deps = kwds.get("deprecated") or [] if deps: if handlers: for scheme in deps: if scheme not in handler_names: raise KeyError("known scheme in deprecated list: %r" % (scheme,)) dmap[cat] = frozenset(deps) #default scheme fb = kwds.get("default") if fb: if handlers: if hasattr(fb, "name"): fb = fb.name if fb not in handler_names: raise KeyError("unknown scheme set as default: %r" % (fb,)) fmap[cat] = self.get_handler(fb, required=True) else: fmap[cat] = fb #min verify time value = kwds.get("min_verify_time") if value: mvmap[cat] = value #XXX: error or warning if unknown key found in kwds? #NOTE: for dmap/fmap/mvmap - # if no cat=None value is specified, each has it's own defaults, # (handlers[0] for fmap, set() for dmap, 0 for mvmap) # but we don't store those in dict since it would complicate policy merge operation #========================================================= #public interface (used by CryptContext) #========================================================= def has_schemes(self): "check if policy supported *any* schemes; returns True/False" return len(self._handlers) > 0 def iter_handlers(self): "iterate through handlers for all schemes in policy" return iter(self._handlers) def schemes(self, resolve=False): "return list of supported schemes; if resolve=True, returns list of handlers instead" if resolve: return list(self._handlers) else: return [h.name for h in self._handlers] def get_handler(self, name=None, category=None, required=False): """given the name of a scheme, return handler which manages it. :arg name: name of scheme, or ``None`` :param category: optional user category :param required: if ``True``, raises KeyError if name not found, instead of returning ``None``. if name is not specified, attempts to return default handler. if returning default, and category is specified, returns category-specific default if set. :returns: handler attached to specified name or None """ if name: for handler in self._handlers: if handler.name == name: return handler else: fmap = self._default if category in fmap: return fmap[category] elif category and None in fmap: return fmap[None] else: handlers = self._handlers if handlers: return handlers[0] raise KeyError("no crypt algorithms supported") if required: raise KeyError("no crypt algorithm by that name: %r" % (name,)) return None def get_options(self, name, category=None): """return dict of options for specified scheme :arg name: name of scheme, or handler instance itself :param category: optional user category whose options should be returned :returns: dict of options for CryptContext internals which are relevant to this name/category combination. """ if hasattr(name, "name"): name = name.name cache = self._cache key = (name, category) try: return cache[key] except KeyError: pass #TODO: pre-calculate or at least cache some of this. options = self._options #start with default values kwds = options[None].get("all") if kwds is None: kwds = {} else: kwds = kwds.copy() #mix in category default values if category and category in options: tmp = options[category].get("all") if tmp: kwds.update(tmp) #mix in hash-specific options tmp = options[None].get(name) if tmp: kwds.update(tmp) #mix in category hash-specific options if category and category in options: tmp = options[category].get(name) if tmp: kwds.update(tmp) cache[key] = kwds return kwds def handler_is_deprecated(self, name, category=None): "check if scheme is marked as deprecated according to this policy; returns True/False" if hasattr(name, "name"): name = name.name dmap = self._deprecated if category in dmap: return name in dmap[category] elif category and None in dmap: return name in dmap[None] else: return False def get_min_verify_time(self, category=None): "return minimal time that verify() should take, according to this policy" mvmap = self._min_verify_time if category in mvmap: return mvmap[category] elif category and None in mvmap: return mvmap[None] else: return 0 #========================================================= #serialization #========================================================= def iter_config(self, ini=False, resolve=False): """iterate through key/value pairs of policy configuration :param ini: If ``True``, returns data formatted for insertion into INI file. Keys use ``.`` separator instead of ``__``; list of handlers returned as comma-separated strings. :param resolve: If ``True``, returns handler objects instead of handler names where appropriate. Ignored if ``ini=True``. :returns: iterator which yeilds (key,value) pairs. """ # #prepare formatting functions # if ini: fmt1 = "%s.%s.%s" fmt2 = "%s.%s" def encode_handler(h): return h.name def encode_hlist(hl): return ", ".join(h.name for h in hl) def encode_nlist(hl): return ", ".join(name for name in hl) else: fmt1 = "%s__%s__%s" fmt2 = "%s__%s" encode_nlist = list if resolve: def encode_handler(h): return h encode_hlist = list else: def encode_handler(h): return h.name def encode_hlist(hl): return [ h.name for h in hl ] def format_key(cat, name, opt): if cat: return fmt1 % (cat, name or "context", opt) if name: return fmt2 % (name, opt) return opt # #run through contents of internal configuration # value = self._handlers if value: yield format_key(None, None, "schemes"), encode_hlist(value) for cat, value in self._deprecated.iteritems(): yield format_key(cat, None, "deprecated"), encode_nlist(value) for cat, value in self._default.iteritems(): yield format_key(cat, None, "default"), encode_handler(value) for cat, value in self._min_verify_time.iteritems(): yield format_key(cat, None, "min_verify_time"), value for cat, copts in self._options.iteritems(): for name in sorted(copts): config = copts[name] for opt in sorted(config): value = config[opt] yield format_key(cat, name, opt), value def to_dict(self, resolve=False): "return policy as dictionary of keywords" return dict(self.iter_config(resolve=resolve)) def _escape_ini_pair(self, k, v): if isinstance(v, str): v = v.replace("%", "%%") #escape any percent signs. elif isinstance(v, (int, long)): v = str(v) return k,v def _write_to_parser(self, parser, section): "helper for to_string / to_file" parser.add_section(section) for k,v in self.iter_config(ini=True): k,v = self._escape_ini_pair(k,v) parser.set(section, k,v) #XXX: rename as "to_stream" or "write_to_stream" ? def to_file(self, stream, section="passlib"): "serialize to INI format and write to specified stream" p = SafeConfigParser() self._write_to_parser(p, section) p.write(stream) def to_string(self, section="passlib", encoding=None): "render to INI string; inverse of from_string() constructor" buf = StringIO() self.to_file(buf, section) out = buf.getvalue() # Py2k # out = out.decode("utf-8") # end Py2k # if encoding: out = out.encode(encoding) return out ##def to_path(self, path, section="passlib", update=False): ## "write to INI file" ## p = ConfigParser() ## if update and os.path.exists(path): ## if not p.read([path]): ## raise EnvironmentError("failed to read existing file") ## p.remove_section(section) ## self._write_to_parser(p, section) ## fh = file(path, "w") ## p.write(fh) ## fh.close() #========================================================= #eoc #========================================================= #========================================================= #load default policy from default.cfg #========================================================= def _load_default_policy(): "helper to try to load default policy from file" #if pkg_resources available, try to read out of egg (common case) if resource_string: try: return CryptPolicy.from_string(resource_string("passlib", "default.cfg")) except IOError: log.warn("error reading passlib/default.cfg, is passlib installed correctly?") pass #failing that, see if we can read it from package dir path = os.path.abspath(os.path.join(os.path.dirname(__file__), "default.cfg")) if os.path.exists(path): with open(path, "rb") as fh: return CryptPolicy.from_string(fh.read()) #give up - this is not desirable at all, could use another fallback. log.error("can't find passlib/default.cfg, is passlib installed correctly?") return CryptPolicy() default_policy = _load_default_policy() #========================================================= # #========================================================= class CryptContext(object): """Helper for encrypting passwords using different algorithms. :param policy: optionally override the default policy CryptContext starts with before options are added. If not specified, the new instance will inherit a set of default options (such as rounds, etc) from the passlib default policy (importable as :data:`passlib.context.default_policy`). If explicitly set to ``None``, the new instance will not inherit from the default policy, and will contain only the configuration specified by any additional keywords. Alternately, a custom CryptPolicy instance can be passed in, which allows loading the policy from a configuration file, combining multiple policies together, and other features. :param kwds: ``schemes`` and all other keywords are passed to the CryptPolicy constructor, or to :meth:`CryptPolicy.replace`, if a policy has also been specified. .. automethod:: replace Configuration ============= .. attribute:: policy This exposes the :class:`CryptPolicy` instance which contains the configuration used by this context object. This attribute may be written to (replacing it with another CryptPolicy instance), in order to reconfigure a CryptContext while an application is running. However, this should only be done for context instances created by the application, and NOT for context instances provided by PassLib. Main Interface ============== .. automethod:: identify .. automethod:: encrypt .. automethod:: verify Migration Helpers ================= .. automethod:: hash_needs_update .. automethod:: verify_and_update """ #=================================================================== #instance attrs #=================================================================== policy = None #policy object governing context #=================================================================== #init #=================================================================== def __init__(self, schemes=None, policy=default_policy, **kwds): #XXX: add a name for the contexts, to help out repr? if schemes: kwds['schemes'] = schemes if not policy: policy = CryptPolicy(**kwds) elif kwds: policy = policy.replace(**kwds) self.policy = policy def __repr__(self): #XXX: *could* have proper repr(), but would have to render policy object options, and it'd be *really* long names = [ handler.name for handler in self.policy.iter_handlers() ] return "" % (id(self), names) #XXX: make an update() method that just updates policy? def replace(self, **kwds): """return mutated CryptContext instance this function operates much like :meth:`datetime.replace()` - it returns a new CryptContext instance whose configuration is exactly the same as the original, with the exception of any keywords specificed taking precedence over the original settings. this is identical to the operation ``CryptContext(policy=self.policy.replace(**kwds))``, see :meth:`CryptPolicy.replace` for more details. """ return CryptContext(policy=self.policy.replace(**kwds)) #=================================================================== #policy adaptation #=================================================================== def _prepare_rounds(self, handler, opts, settings): "helper for prepare_default_settings" mn = opts.get("min_rounds") mx = opts.get("max_rounds") rounds = settings.get("rounds") if rounds is None: df = opts.get("default_rounds") or mx or mn if df is not None: vr = opts.get("vary_rounds") if vr: if isinstance(vr, str): rc = getattr(handler, "rounds_cost", "linear") vr = int(vr.rstrip("%")) #NOTE: deliberately strip >1 %, #in case an interpolation-escaped %% #makes it through to here. assert 0 <= vr < 100 if rc == "log2": #let % variance scale the number of actual rounds, not the logarithmic value df = 2**df vr = int(df*vr/100) lower = int(logb(df-vr,2)+.5) #err on the side of strength - round up upper = int(logb(df+vr,2)) else: assert rc == "linear" vr = int(df*vr/100) lower = df-vr upper = df+vr else: lower = df-vr upper = df+vr if lower < 1: lower = 1 if mn and lower < mn: lower = mn if mx and upper > mx: upper = mx if lower > upper: #NOTE: this mainly happens when default_rounds>max_rounds, which shouldn't usually happen rounds = upper warn("vary default rounds: lower bound > upper bound, using upper bound (%d > %d)" % (lower, upper)) else: rounds = rng.randint(lower, upper) else: rounds = df if rounds is not None: if mx and rounds > mx: rounds = mx if mn and rounds < mn: #give mn predence if mn > mx rounds = mn settings['rounds'] = rounds def _prepare_settings(self, handler, category=None, **settings): "normalize settings for handler according to context configuration" opts = self.policy.get_options(handler, category) if not opts: return settings #load in default values for any settings for k in handler.setting_kwds: if k not in settings and k in opts: settings[k] = opts[k] #handle rounds if 'rounds' in handler.setting_kwds: self._prepare_rounds(handler, opts, settings) #done return settings def hash_needs_update(self, hash, category=None): """check if hash is allowed by current policy, or if secret should be re-encrypted. the core of CryptContext's support for hash migration: this function takes in a hash string, and checks the scheme, number of rounds, and other properties against the current policy; and returns True if the hash is using a deprecated scheme, or is otherwise outside of the bounds specified by the policy. if so, the password should be re-encrypted using ``ctx.encrypt(passwd)``. :arg hash: existing hash string :param category: optional user category :returns: True/False """ handler = self.identify(hash, resolve=True, required=True) policy = self.policy #check if handler has been deprecated if policy.handler_is_deprecated(handler, category): return True #get options, and call compliance helper (check things such as rounds, etc) opts = policy.get_options(handler, category) #XXX: could check if handler provides it's own helper, eg getattr(handler, "hash_needs_update", None), #and call that instead of the following default behavior if hasattr(handler, "_hash_needs_update"): #NOTE: hacking this in for the sake of bcrypt & issue 25, # will formalize (and possibly change) interface later. if handler._hash_needs_update(hash, **opts): return True if opts: #check if we can parse hash to check it's rounds parameter if ('min_rounds' in opts or 'max_rounds' in opts) and \ 'rounds' in handler.setting_kwds and hasattr(handler, "from_string"): info = handler.from_string(hash) rounds = getattr(info, "rounds", None) #should generally work, but just in case if rounds is not None: min_rounds = opts.get("min_rounds") if min_rounds is not None and rounds < min_rounds: return True max_rounds = opts.get("max_rounds") if max_rounds is not None and rounds > max_rounds: return True return False #=================================================================== #password hash api proxy methods #=================================================================== def genconfig(self, scheme=None, category=None, **settings): """Call genconfig() for specified handler This wraps the genconfig() method of the appropriate handler (using the default if none other is specified). See the :ref:`password-hash-api` for details. The main different between this and calling a handlers' genhash method directly is that this method will add in any policy-specific options relevant for the particular hash. """ handler = self.policy.get_handler(scheme, category, required=True) settings = self._prepare_settings(handler, category, **settings) return handler.genconfig(**settings) def genhash(self, secret, config, scheme=None, category=None, **context): """Call genhash() for specified handler. This wraps the genconfig() method of the appropriate handler (using the default if none other is specified). See the :ref:`password-hash-api` for details. """ #NOTE: this doesn't use category in any way, but accepts it for consistency if scheme: handler = self.policy.get_handler(scheme, required=True) else: handler = self.identify(config, resolve=True, required=True) #XXX: could insert normalization to preferred unicode encoding here return handler.genhash(secret, config, **context) def identify(self, hash, category=None, resolve=False, required=False): """Attempt to identify which algorithm hash belongs to w/in this context. :arg hash: The hash string to test. :param resolve: If ``True``, returns the handler itself, instead of the name of the handler. All registered algorithms will be checked in from last to first, and whichever one claims the hash first will be returned. :returns: The handler which first identifies the hash, or ``None`` if none of the algorithms identify the hash. """ #NOTE: this doesn't use category in any way, but accepts it for consistency if hash is None: if required: raise ValueError("no hash specified") return None handler = None for handler in self.policy.iter_handlers(): if handler.identify(hash): if resolve: return handler else: return handler.name if required: if handler is None: raise KeyError("no crypt algorithms supported") raise ValueError("hash could not be identified") return None def encrypt(self, secret, scheme=None, category=None, **kwds): """encrypt secret, returning resulting hash. :arg secret: String containing the secret to encrypt :param scheme: Optionally specify the name of the algorithm to use. If no algorithm is specified, an attempt is made to guess from the hash string. If no hash string is specified, the last algorithm in the list is used. :param \*\*kwds: All other keyword options are passed to the algorithm's encrypt method. The two most common ones are "keep_salt" and "rounds". :returns: The secret as encoded by the specified algorithm and options. """ handler = self.policy.get_handler(scheme, category, required=True) kwds = self._prepare_settings(handler, category, **kwds) #XXX: could insert normalization to preferred unicode encoding here return handler.encrypt(secret, **kwds) def verify(self, secret, hash, scheme=None, category=None, **context): """verify secret against specified hash. This identifies the scheme used by the hash (within this context), and verifies that the specified password matches. If the policy specified a min_verify_time, this method will always take at least that amount of time (so as to not reveal legacy entries which use a weak hash scheme). :arg secret: the secret to verify :arg hash: hash string to compare to :param scheme: optional force context to use specfic scheme (must be listed in context) :param category: optional user category, if used by the application. defaults to ``None``. :param \*\*context: all additional keywords are passed to the appropriate handler, and should match it's :attr:`context keywords `. :returns: True/False """ #quick checks if hash is None: return False mvt = self.policy.get_min_verify_time(category) if mvt: start = time.time() #locate handler if scheme: handler = self.policy.get_handler(scheme, required=True) else: handler = self.identify(hash, resolve=True, required=True) #strip context kwds if scheme doesn't use them ##for k in context.keys(): ## if k not in handler.context_kwds: ## del context[k] #XXX: could insert normalization to preferred unicode encoding here #use handler to verify secret result = handler.verify(secret, hash, **context) if mvt: #delta some amount of time if verify took less than mvt seconds end = time.time() delta = mvt + start - end if delta > 0: time.sleep(delta) elif delta < 0: #warn app they aren't being protected against timing attacks... warn("CryptContext: verify exceeded min_verify_time: scheme=%r min_verify_time=%r elapsed=%r" % (handler.name, mvt, end-start)) return result def verify_and_update(self, secret, hash, scheme=None, category=None, **kwds): """verify secret and check if hash needs upgrading, in a single call. This is a convience method for a common situation in most applications: When a user logs in, they must :meth:`verify` if the password matches; if successful, check if the hash algorithm has been deprecated (:meth:`hash_needs_update`); and if so, re-:meth:`encrypt` the secret. This method takes care of calling all of these 3 methods, returning a simple tuple for the application to use. :arg secret: the secret to verify :arg hash: hash string to compare to :param scheme: optional force context to use specfic scheme (must be listed in context) :param category: optional user category, if used by the application. defaults to ``None``. :param \*\*context: all additional keywords are passed to the appropriate handler, and should match it's :attr:`context keywords `. :returns: The tuple ``(verified, new_hash)``, where one of the following cases is true: * ``(False, None)`` indicates the secret failed to verify. * ``(True, None)`` indicates the secret verified correctly, and the hash does not need upgrading. * ``(True, str)`` indicates the secret verified correctly, but the existing hash has been deprecated, and should be replaced by the :class:`str` returned as ``new_hash``. .. seealso:: :ref:`context-migrating-passwords` for a usage example. """ ok = self.verify(secret, hash, scheme=scheme, category=category, **kwds) if not ok: return False, None if self.hash_needs_update(hash, category=category): return True, self.encrypt(secret, category=category, **kwds) else: return True, None #========================================================= #eoc #========================================================= class LazyCryptContext(CryptContext): """CryptContext subclass which doesn't load handlers until needed. This is a subclass of CryptContext which takes in a set of arguments exactly like CryptContext, but won't load any handlers (or even parse it's arguments) until the first time one of it's methods is accessed. :arg schemes: the first positional argument can be a list of schemes, or omitted, just like CryptContext. :param create_policy: if a callable is passed in via this keyword, it will be invoked at lazy-load time with the following signature: ``create_policy(**kwds) -> CryptPolicy``; where ``kwds`` is all the additional kwds passed to LazyCryptContext. It should return a CryptPolicy instance, which will then be used by the CryptContext. :param kwds: All additional keywords are passed to CryptPolicy; or to the create_policy function if provided. This is mainly used internally by modules such as :mod:`passlib.apps`, which define a large number of contexts, but only a few of them will be needed at any one time. Use of this class saves the memory needed to import the specified handlers until the context instance is actually accessed. As well, it allows constructing a context at *module-init* time, but using :func:`!create_policy()` to provide dynamic configuration at *application-run* time. """ _lazy_kwds = None def __init__(self, schemes=None, **kwds): if schemes is not None: kwds['schemes'] = schemes self._lazy_kwds = kwds def _lazy_init(self): kwds = self._lazy_kwds del self._lazy_kwds if 'create_policy' in kwds: create_policy = kwds.pop("create_policy") kwds = dict(policy=create_policy(**kwds)) super(LazyCryptContext, self).__init__(**kwds) #NOTE: 'policy' property calls _lazy_init the first time it's accessed, # and relies on CryptContext.__init__ to replace it with an actual instance. # it should then have no more effect from then on. class _PolicyProperty(object): def __get__(self, obj, cls): if obj is None: return self obj._lazy_init() assert isinstance(obj.policy, CryptPolicy) return obj.policy policy = _PolicyProperty() #========================================================= # eof #========================================================= passlib-1.5.3/passlib/utils/0000755000175000017500000000000011643754212017105 5ustar biscuitbiscuit00000000000000passlib-1.5.3/passlib/utils/md4.py0000644000175000017500000001675411643466373020170 0ustar biscuitbiscuit00000000000000""" helper implementing insecure and obsolete md4 algorithm. used for NTHASH format, which is also insecure and broken, since it's just md4(password) implementated based on rfc at http://www.faqs.org/rfcs/rfc1320.html """ #========================================================================= #imports #========================================================================= #core from binascii import hexlify import struct from warnings import warn #site from passlib.utils import b, bytes, to_native_str #local __all__ = [ "md4" ] #========================================================================= #utils #========================================================================= def F(x,y,z): return (x&y) | ((~x) & z) def G(x,y,z): return (x&y) | (x&z) | (y&z) ##def H(x,y,z): ## return x ^ y ^ z MASK_32 = 2**32-1 #========================================================================= #main class #========================================================================= class md4(object): """pep-247 compatible implementation of MD4 hash algorithm .. attribute:: digest_size size of md4 digest in bytes (16 bytes) .. method:: update update digest by appending additional content .. method:: copy create clone of digest object, including current state .. method:: digest return bytes representing md4 digest of current content .. method:: hexdigest return hexdecimal version of digest """ #FIXME: make this follow hash object PEP better. #FIXME: this isn't threadsafe #XXX: should we monkeypatch ourselves into hashlib for general use? probably wouldn't be nice. name = "md4" digest_size = digestsize = 16 _count = 0 #number of 64-byte blocks processed so far (not including _buf) _state = None #list of [a,b,c,d] 32 bit ints used as internal register _buf = None #data processed in 64 byte blocks, this holds leftover from last update def __init__(self, content=None): self._count = 0 self._state = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476] self._buf = b('') if content: self.update(content) #round 1 table - [abcd k s] _round1 = [ [0,1,2,3, 0,3], [3,0,1,2, 1,7], [2,3,0,1, 2,11], [1,2,3,0, 3,19], [0,1,2,3, 4,3], [3,0,1,2, 5,7], [2,3,0,1, 6,11], [1,2,3,0, 7,19], [0,1,2,3, 8,3], [3,0,1,2, 9,7], [2,3,0,1, 10,11], [1,2,3,0, 11,19], [0,1,2,3, 12,3], [3,0,1,2, 13,7], [2,3,0,1, 14,11], [1,2,3,0, 15,19], ] #round 2 table - [abcd k s] _round2 = [ [0,1,2,3, 0,3], [3,0,1,2, 4,5], [2,3,0,1, 8,9], [1,2,3,0, 12,13], [0,1,2,3, 1,3], [3,0,1,2, 5,5], [2,3,0,1, 9,9], [1,2,3,0, 13,13], [0,1,2,3, 2,3], [3,0,1,2, 6,5], [2,3,0,1, 10,9], [1,2,3,0, 14,13], [0,1,2,3, 3,3], [3,0,1,2, 7,5], [2,3,0,1, 11,9], [1,2,3,0, 15,13], ] #round 3 table - [abcd k s] _round3 = [ [0,1,2,3, 0,3], [3,0,1,2, 8,9], [2,3,0,1, 4,11], [1,2,3,0, 12,15], [0,1,2,3, 2,3], [3,0,1,2, 10,9], [2,3,0,1, 6,11], [1,2,3,0, 14,15], [0,1,2,3, 1,3], [3,0,1,2, 9,9], [2,3,0,1, 5,11], [1,2,3,0, 13,15], [0,1,2,3, 3,3], [3,0,1,2, 11,9], [2,3,0,1, 7,11], [1,2,3,0, 15,15], ] def _process(self, block): "process 64 byte block" #unpack block into 16 32-bit ints X = struct.unpack("<16I", block) #clone state orig = self._state state = list(orig) #round 1 - F function - (x&y)|(~x & z) for a,b,c,d,k,s in self._round1: t = (state[a] + F(state[b],state[c],state[d]) + X[k]) & MASK_32 state[a] = ((t<>(32-s)) #round 2 - G function for a,b,c,d,k,s in self._round2: t = (state[a] + G(state[b],state[c],state[d]) + X[k] + 0x5a827999) & MASK_32 state[a] = ((t<>(32-s)) #round 3 - H function - x ^ y ^ z for a,b,c,d,k,s in self._round3: t = (state[a] + (state[b] ^ state[c] ^ state[d]) + X[k] + 0x6ed9eba1) & MASK_32 state[a] = ((t<>(32-s)) #add back into original state for i in xrange(4): orig[i] = (orig[i]+state[i]) & MASK_32 def update(self, content): if not isinstance(content, bytes): raise TypeError("expected bytes") buf = self._buf if buf: content = buf + content idx = 0 end = len(content) while True: next = idx + 64 if next <= end: self._process(content[idx:next]) self._count += 1 idx = next else: self._buf = content[idx:] return def copy(self): other = _builtin_md4() other._count = self._count other._state = list(self._state) other._buf = self._buf return other def digest(self): #NOTE: backing up state so we can restore it after _process is called, #in case object is updated again (this is only attr altered by this method) orig = list(self._state) #final block: buf + 0x80, # then 0x00 padding until congruent w/ 56 mod 64 bytes # then last 8 bytes = msg length in bits buf = self._buf msglen = self._count*512 + len(buf)*8 block = buf + b('\x80') + b('\x00') * ((119-len(buf)) % 64) + \ struct.pack("<2I", msglen & MASK_32, (msglen>>32) & MASK_32) if len(block) == 128: self._process(block[:64]) self._process(block[64:]) else: assert len(block) == 64 self._process(block) #render digest & restore un-finalized state out = struct.pack("<4I", *self._state) self._state = orig return out def hexdigest(self): return to_native_str(hexlify(self.digest()), "latin-1") #========================================================================= #eoc #========================================================================= #keep ref around for unittest, 'md4' usually replaced by ssl wrapper, below. _builtin_md4 = md4 #========================================================================= #check if hashlib provides accelarated md4 #========================================================================= from passlib.utils import pypy_vm import hashlib def _has_native_md4(): try: h = hashlib.new("md4") except ValueError: #not supported return False result = h.hexdigest() if result == '31d6cfe0d16ae931b73c59d7e0c089c0': return True if pypy_vm and result == '': #as of 1.5, pypy md4 just returns null! #since this is expected, don't bother w/ warning. return False #anything else should alert user warn("native md4 support disabled, incorrect value returned") return False if _has_native_md4(): #overwrite md4 class w/ hashlib wrapper def md4(content=None): "wrapper for hashlib.new('md4')" return hashlib.new('md4', content or b('')) else: del hashlib #========================================================================= #eof #========================================================================= passlib-1.5.3/passlib/utils/des.py0000644000175000017500000013277011643466373020254 0ustar biscuitbiscuit00000000000000""" History ======= These routines (which have since been drastically modified for python) are based on a Java implementation of the des-crypt algorithm, found at ``_. The copyright & license for that source is as follows:: UnixCrypt.java 0.9 96/11/25 Copyright (c) 1996 Aki Yoshida. All rights reserved. Permission to use, copy, modify and distribute this software for non-commercial or commercial purposes and without fee is hereby granted provided that this copyright notice appears in all copies. --- Unix crypt(3C) utility @version 0.9, 11/25/96 @author Aki Yoshida --- modified April 2001 by Iris Van den Broeke, Daniel Deville --- Unix Crypt. Implements the one way cryptography used by Unix systems for simple password protection. @version $Id: UnixCrypt2.txt,v 1.1.1.1 2005/09/13 22:20:13 christos Exp $ @author Greg Wilkins (gregw) netbsd des-crypt implementation, which has some nice notes on how this all works - http://fxr.googlebit.com/source/lib/libcrypt/crypt.c?v=NETBSD-CURRENT """ #TODO: could use an accelerated C version of this module to speed up lmhash, des-crypt, and ext-des-crypt #========================================================= #imports #========================================================= #pkg from passlib.utils import bytes_to_int, int_to_bytes, bytes, bord, bjoin_ints #local __all__ = [ "expand_des_key", "des_encrypt_block", "mdes_encrypt_int_block", ] #========================================================= #precalculated iteration ranges & constants #========================================================= R8 = range(8) RR8 = range(7, -1, -1) RR4 = range(3, -1, -1) RR12_1 = range(11, 1, -1) RR9_1 = range(9,-1,-1) RR6_S2 = range(6, -1, -2) RR14_S2 = range(14, -1, -2) R16_S2 = range(0, 16, 2) INT_24_MAX = 0xffffff INT_64_MAX = 0xffffffff INT_64_MAX = 0xffffffffffffffff #========================================================= # static tables for des #========================================================= PCXROT = IE3264 = SPE = CF6464 = None #placeholders filled in by load_tables def load_tables(): "delay loading tables until they are actually needed" global PCXROT, IE3264, SPE, CF6464 #--------------------------------------------------- # Initial key schedule permutation # PC1ROT - bit reverse, then PC1, then Rotate, then PC2 #--------------------------------------------------- #NOTE: this was reordered from original table to make perm3264 logic simpler PC1ROT=( ( 0x0000000000000000, 0x0000000000000000, 0x0000000000002000, 0x0000000000002000, 0x0000000000000020, 0x0000000000000020, 0x0000000000002020, 0x0000000000002020, 0x0000000000000400, 0x0000000000000400, 0x0000000000002400, 0x0000000000002400, 0x0000000000000420, 0x0000000000000420, 0x0000000000002420, 0x0000000000002420, ), ( 0x0000000000000000, 0x2000000000000000, 0x0000000400000000, 0x2000000400000000, 0x0000800000000000, 0x2000800000000000, 0x0000800400000000, 0x2000800400000000, 0x0008000000000000, 0x2008000000000000, 0x0008000400000000, 0x2008000400000000, 0x0008800000000000, 0x2008800000000000, 0x0008800400000000, 0x2008800400000000, ), ( 0x0000000000000000, 0x0000000000000000, 0x0000000000000040, 0x0000000000000040, 0x0000000020000000, 0x0000000020000000, 0x0000000020000040, 0x0000000020000040, 0x0000000000200000, 0x0000000000200000, 0x0000000000200040, 0x0000000000200040, 0x0000000020200000, 0x0000000020200000, 0x0000000020200040, 0x0000000020200040, ), ( 0x0000000000000000, 0x0002000000000000, 0x0800000000000000, 0x0802000000000000, 0x0100000000000000, 0x0102000000000000, 0x0900000000000000, 0x0902000000000000, 0x4000000000000000, 0x4002000000000000, 0x4800000000000000, 0x4802000000000000, 0x4100000000000000, 0x4102000000000000, 0x4900000000000000, 0x4902000000000000, ), ( 0x0000000000000000, 0x0000000000000000, 0x0000000000040000, 0x0000000000040000, 0x0000020000000000, 0x0000020000000000, 0x0000020000040000, 0x0000020000040000, 0x0000000000000004, 0x0000000000000004, 0x0000000000040004, 0x0000000000040004, 0x0000020000000004, 0x0000020000000004, 0x0000020000040004, 0x0000020000040004, ), ( 0x0000000000000000, 0x0000400000000000, 0x0200000000000000, 0x0200400000000000, 0x0080000000000000, 0x0080400000000000, 0x0280000000000000, 0x0280400000000000, 0x0000008000000000, 0x0000408000000000, 0x0200008000000000, 0x0200408000000000, 0x0080008000000000, 0x0080408000000000, 0x0280008000000000, 0x0280408000000000, ), ( 0x0000000000000000, 0x0000000000000000, 0x0000000010000000, 0x0000000010000000, 0x0000000000001000, 0x0000000000001000, 0x0000000010001000, 0x0000000010001000, 0x0000000040000000, 0x0000000040000000, 0x0000000050000000, 0x0000000050000000, 0x0000000040001000, 0x0000000040001000, 0x0000000050001000, 0x0000000050001000, ), ( 0x0000000000000000, 0x0000001000000000, 0x0000080000000000, 0x0000081000000000, 0x1000000000000000, 0x1000001000000000, 0x1000080000000000, 0x1000081000000000, 0x0004000000000000, 0x0004001000000000, 0x0004080000000000, 0x0004081000000000, 0x1004000000000000, 0x1004001000000000, 0x1004080000000000, 0x1004081000000000, ), ( 0x0000000000000000, 0x0000000000000000, 0x0000000000000080, 0x0000000000000080, 0x0000000000080000, 0x0000000000080000, 0x0000000000080080, 0x0000000000080080, 0x0000000000800000, 0x0000000000800000, 0x0000000000800080, 0x0000000000800080, 0x0000000000880000, 0x0000000000880000, 0x0000000000880080, 0x0000000000880080, ), ( 0x0000000000000000, 0x0000000008000000, 0x0000002000000000, 0x0000002008000000, 0x0000100000000000, 0x0000100008000000, 0x0000102000000000, 0x0000102008000000, 0x0000200000000000, 0x0000200008000000, 0x0000202000000000, 0x0000202008000000, 0x0000300000000000, 0x0000300008000000, 0x0000302000000000, 0x0000302008000000, ), ( 0x0000000000000000, 0x0000000000000000, 0x0000000000400000, 0x0000000000400000, 0x0000000004000000, 0x0000000004000000, 0x0000000004400000, 0x0000000004400000, 0x0000000000000800, 0x0000000000000800, 0x0000000000400800, 0x0000000000400800, 0x0000000004000800, 0x0000000004000800, 0x0000000004400800, 0x0000000004400800, ), ( 0x0000000000000000, 0x0000000000008000, 0x0040000000000000, 0x0040000000008000, 0x0000004000000000, 0x0000004000008000, 0x0040004000000000, 0x0040004000008000, 0x8000000000000000, 0x8000000000008000, 0x8040000000000000, 0x8040000000008000, 0x8000004000000000, 0x8000004000008000, 0x8040004000000000, 0x8040004000008000, ), ( 0x0000000000000000, 0x0000000000000000, 0x0000000000004000, 0x0000000000004000, 0x0000000000000008, 0x0000000000000008, 0x0000000000004008, 0x0000000000004008, 0x0000000000000010, 0x0000000000000010, 0x0000000000004010, 0x0000000000004010, 0x0000000000000018, 0x0000000000000018, 0x0000000000004018, 0x0000000000004018, ), ( 0x0000000000000000, 0x0000000200000000, 0x0001000000000000, 0x0001000200000000, 0x0400000000000000, 0x0400000200000000, 0x0401000000000000, 0x0401000200000000, 0x0020000000000000, 0x0020000200000000, 0x0021000000000000, 0x0021000200000000, 0x0420000000000000, 0x0420000200000000, 0x0421000000000000, 0x0421000200000000, ), ( 0x0000000000000000, 0x0000000000000000, 0x0000010000000000, 0x0000010000000000, 0x0000000100000000, 0x0000000100000000, 0x0000010100000000, 0x0000010100000000, 0x0000000000100000, 0x0000000000100000, 0x0000010000100000, 0x0000010000100000, 0x0000000100100000, 0x0000000100100000, 0x0000010100100000, 0x0000010100100000, ), ( 0x0000000000000000, 0x0000000080000000, 0x0000040000000000, 0x0000040080000000, 0x0010000000000000, 0x0010000080000000, 0x0010040000000000, 0x0010040080000000, 0x0000000800000000, 0x0000000880000000, 0x0000040800000000, 0x0000040880000000, 0x0010000800000000, 0x0010000880000000, 0x0010040800000000, 0x0010040880000000, ), ) #--------------------------------------------------- # Subsequent key schedule rotation permutations # PC2ROT - PC2 inverse, then Rotate, then PC2 #--------------------------------------------------- #NOTE: this was reordered from original table to make perm3264 logic simpler PC2ROTA=( ( 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000200000, 0x0000000000200000, 0x0000000000200000, 0x0000000000200000, 0x0000000004000000, 0x0000000004000000, 0x0000000004000000, 0x0000000004000000, 0x0000000004200000, 0x0000000004200000, 0x0000000004200000, 0x0000000004200000, ), ( 0x0000000000000000, 0x0000000000000800, 0x0000010000000000, 0x0000010000000800, 0x0000000000002000, 0x0000000000002800, 0x0000010000002000, 0x0000010000002800, 0x0000000010000000, 0x0000000010000800, 0x0000010010000000, 0x0000010010000800, 0x0000000010002000, 0x0000000010002800, 0x0000010010002000, 0x0000010010002800, ), ( 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000100000000, 0x0000000100000000, 0x0000000100000000, 0x0000000100000000, 0x0000000000800000, 0x0000000000800000, 0x0000000000800000, 0x0000000000800000, 0x0000000100800000, 0x0000000100800000, 0x0000000100800000, 0x0000000100800000, ), ( 0x0000000000000000, 0x0000020000000000, 0x0000000080000000, 0x0000020080000000, 0x0000000000400000, 0x0000020000400000, 0x0000000080400000, 0x0000020080400000, 0x0000000008000000, 0x0000020008000000, 0x0000000088000000, 0x0000020088000000, 0x0000000008400000, 0x0000020008400000, 0x0000000088400000, 0x0000020088400000, ), ( 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000040, 0x0000000000000040, 0x0000000000000040, 0x0000000000000040, 0x0000000000001000, 0x0000000000001000, 0x0000000000001000, 0x0000000000001000, 0x0000000000001040, 0x0000000000001040, 0x0000000000001040, 0x0000000000001040, ), ( 0x0000000000000000, 0x0000000000000010, 0x0000000000000400, 0x0000000000000410, 0x0000000000000080, 0x0000000000000090, 0x0000000000000480, 0x0000000000000490, 0x0000000040000000, 0x0000000040000010, 0x0000000040000400, 0x0000000040000410, 0x0000000040000080, 0x0000000040000090, 0x0000000040000480, 0x0000000040000490, ), ( 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000080000, 0x0000000000080000, 0x0000000000080000, 0x0000000000080000, 0x0000000000100000, 0x0000000000100000, 0x0000000000100000, 0x0000000000100000, 0x0000000000180000, 0x0000000000180000, 0x0000000000180000, 0x0000000000180000, ), ( 0x0000000000000000, 0x0000000000040000, 0x0000000000000020, 0x0000000000040020, 0x0000000000000004, 0x0000000000040004, 0x0000000000000024, 0x0000000000040024, 0x0000000200000000, 0x0000000200040000, 0x0000000200000020, 0x0000000200040020, 0x0000000200000004, 0x0000000200040004, 0x0000000200000024, 0x0000000200040024, ), ( 0x0000000000000000, 0x0000000000000008, 0x0000000000008000, 0x0000000000008008, 0x0010000000000000, 0x0010000000000008, 0x0010000000008000, 0x0010000000008008, 0x0020000000000000, 0x0020000000000008, 0x0020000000008000, 0x0020000000008008, 0x0030000000000000, 0x0030000000000008, 0x0030000000008000, 0x0030000000008008, ), ( 0x0000000000000000, 0x0000400000000000, 0x0000080000000000, 0x0000480000000000, 0x0000100000000000, 0x0000500000000000, 0x0000180000000000, 0x0000580000000000, 0x4000000000000000, 0x4000400000000000, 0x4000080000000000, 0x4000480000000000, 0x4000100000000000, 0x4000500000000000, 0x4000180000000000, 0x4000580000000000, ), ( 0x0000000000000000, 0x0000000000004000, 0x0000000020000000, 0x0000000020004000, 0x0001000000000000, 0x0001000000004000, 0x0001000020000000, 0x0001000020004000, 0x0200000000000000, 0x0200000000004000, 0x0200000020000000, 0x0200000020004000, 0x0201000000000000, 0x0201000000004000, 0x0201000020000000, 0x0201000020004000, ), ( 0x0000000000000000, 0x1000000000000000, 0x0004000000000000, 0x1004000000000000, 0x0002000000000000, 0x1002000000000000, 0x0006000000000000, 0x1006000000000000, 0x0000000800000000, 0x1000000800000000, 0x0004000800000000, 0x1004000800000000, 0x0002000800000000, 0x1002000800000000, 0x0006000800000000, 0x1006000800000000, ), ( 0x0000000000000000, 0x0040000000000000, 0x2000000000000000, 0x2040000000000000, 0x0000008000000000, 0x0040008000000000, 0x2000008000000000, 0x2040008000000000, 0x0000001000000000, 0x0040001000000000, 0x2000001000000000, 0x2040001000000000, 0x0000009000000000, 0x0040009000000000, 0x2000009000000000, 0x2040009000000000, ), ( 0x0000000000000000, 0x0400000000000000, 0x8000000000000000, 0x8400000000000000, 0x0000002000000000, 0x0400002000000000, 0x8000002000000000, 0x8400002000000000, 0x0100000000000000, 0x0500000000000000, 0x8100000000000000, 0x8500000000000000, 0x0100002000000000, 0x0500002000000000, 0x8100002000000000, 0x8500002000000000, ), ( 0x0000000000000000, 0x0000800000000000, 0x0800000000000000, 0x0800800000000000, 0x0000004000000000, 0x0000804000000000, 0x0800004000000000, 0x0800804000000000, 0x0000000400000000, 0x0000800400000000, 0x0800000400000000, 0x0800800400000000, 0x0000004400000000, 0x0000804400000000, 0x0800004400000000, 0x0800804400000000, ), ( 0x0000000000000000, 0x0080000000000000, 0x0000040000000000, 0x0080040000000000, 0x0008000000000000, 0x0088000000000000, 0x0008040000000000, 0x0088040000000000, 0x0000200000000000, 0x0080200000000000, 0x0000240000000000, 0x0080240000000000, 0x0008200000000000, 0x0088200000000000, 0x0008240000000000, 0x0088240000000000, ), ) #NOTE: this was reordered from original table to make perm3264 logic simpler PC2ROTB=( ( 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000400, 0x0000000000000400, 0x0000000000000400, 0x0000000000000400, 0x0000000000080000, 0x0000000000080000, 0x0000000000080000, 0x0000000000080000, 0x0000000000080400, 0x0000000000080400, 0x0000000000080400, 0x0000000000080400, ), ( 0x0000000000000000, 0x0000000000800000, 0x0000000000004000, 0x0000000000804000, 0x0000000080000000, 0x0000000080800000, 0x0000000080004000, 0x0000000080804000, 0x0000000000040000, 0x0000000000840000, 0x0000000000044000, 0x0000000000844000, 0x0000000080040000, 0x0000000080840000, 0x0000000080044000, 0x0000000080844000, ), ( 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000008, 0x0000000000000008, 0x0000000000000008, 0x0000000000000008, 0x0000000040000000, 0x0000000040000000, 0x0000000040000000, 0x0000000040000000, 0x0000000040000008, 0x0000000040000008, 0x0000000040000008, 0x0000000040000008, ), ( 0x0000000000000000, 0x0000000020000000, 0x0000000200000000, 0x0000000220000000, 0x0000000000000080, 0x0000000020000080, 0x0000000200000080, 0x0000000220000080, 0x0000000000100000, 0x0000000020100000, 0x0000000200100000, 0x0000000220100000, 0x0000000000100080, 0x0000000020100080, 0x0000000200100080, 0x0000000220100080, ), ( 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000002000, 0x0000000000002000, 0x0000000000002000, 0x0000000000002000, 0x0000020000000000, 0x0000020000000000, 0x0000020000000000, 0x0000020000000000, 0x0000020000002000, 0x0000020000002000, 0x0000020000002000, 0x0000020000002000, ), ( 0x0000000000000000, 0x0000000000000800, 0x0000000100000000, 0x0000000100000800, 0x0000000010000000, 0x0000000010000800, 0x0000000110000000, 0x0000000110000800, 0x0000000000000004, 0x0000000000000804, 0x0000000100000004, 0x0000000100000804, 0x0000000010000004, 0x0000000010000804, 0x0000000110000004, 0x0000000110000804, ), ( 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000001000, 0x0000000000001000, 0x0000000000001000, 0x0000000000001000, 0x0000000000000010, 0x0000000000000010, 0x0000000000000010, 0x0000000000000010, 0x0000000000001010, 0x0000000000001010, 0x0000000000001010, 0x0000000000001010, ), ( 0x0000000000000000, 0x0000000000000040, 0x0000010000000000, 0x0000010000000040, 0x0000000000200000, 0x0000000000200040, 0x0000010000200000, 0x0000010000200040, 0x0000000000008000, 0x0000000000008040, 0x0000010000008000, 0x0000010000008040, 0x0000000000208000, 0x0000000000208040, 0x0000010000208000, 0x0000010000208040, ), ( 0x0000000000000000, 0x0000000004000000, 0x0000000008000000, 0x000000000c000000, 0x0400000000000000, 0x0400000004000000, 0x0400000008000000, 0x040000000c000000, 0x8000000000000000, 0x8000000004000000, 0x8000000008000000, 0x800000000c000000, 0x8400000000000000, 0x8400000004000000, 0x8400000008000000, 0x840000000c000000, ), ( 0x0000000000000000, 0x0002000000000000, 0x0200000000000000, 0x0202000000000000, 0x1000000000000000, 0x1002000000000000, 0x1200000000000000, 0x1202000000000000, 0x0008000000000000, 0x000a000000000000, 0x0208000000000000, 0x020a000000000000, 0x1008000000000000, 0x100a000000000000, 0x1208000000000000, 0x120a000000000000, ), ( 0x0000000000000000, 0x0000000000400000, 0x0000000000000020, 0x0000000000400020, 0x0040000000000000, 0x0040000000400000, 0x0040000000000020, 0x0040000000400020, 0x0800000000000000, 0x0800000000400000, 0x0800000000000020, 0x0800000000400020, 0x0840000000000000, 0x0840000000400000, 0x0840000000000020, 0x0840000000400020, ), ( 0x0000000000000000, 0x0080000000000000, 0x0000008000000000, 0x0080008000000000, 0x2000000000000000, 0x2080000000000000, 0x2000008000000000, 0x2080008000000000, 0x0020000000000000, 0x00a0000000000000, 0x0020008000000000, 0x00a0008000000000, 0x2020000000000000, 0x20a0000000000000, 0x2020008000000000, 0x20a0008000000000, ), ( 0x0000000000000000, 0x0000002000000000, 0x0000040000000000, 0x0000042000000000, 0x4000000000000000, 0x4000002000000000, 0x4000040000000000, 0x4000042000000000, 0x0000400000000000, 0x0000402000000000, 0x0000440000000000, 0x0000442000000000, 0x4000400000000000, 0x4000402000000000, 0x4000440000000000, 0x4000442000000000, ), ( 0x0000000000000000, 0x0000004000000000, 0x0000200000000000, 0x0000204000000000, 0x0000080000000000, 0x0000084000000000, 0x0000280000000000, 0x0000284000000000, 0x0000800000000000, 0x0000804000000000, 0x0000a00000000000, 0x0000a04000000000, 0x0000880000000000, 0x0000884000000000, 0x0000a80000000000, 0x0000a84000000000, ), ( 0x0000000000000000, 0x0000000800000000, 0x0000000400000000, 0x0000000c00000000, 0x0000100000000000, 0x0000100800000000, 0x0000100400000000, 0x0000100c00000000, 0x0010000000000000, 0x0010000800000000, 0x0010000400000000, 0x0010000c00000000, 0x0010100000000000, 0x0010100800000000, 0x0010100400000000, 0x0010100c00000000, ), ( 0x0000000000000000, 0x0100000000000000, 0x0001000000000000, 0x0101000000000000, 0x0000001000000000, 0x0100001000000000, 0x0001001000000000, 0x0101001000000000, 0x0004000000000000, 0x0104000000000000, 0x0005000000000000, 0x0105000000000000, 0x0004001000000000, 0x0104001000000000, 0x0005001000000000, 0x0105001000000000, ), ) #--------------------------------------------------- #PCXROT - PC1ROT, PC2ROTA, PC2ROTB listed in order # of the PC1 rotation schedule, as used by des_setkey #--------------------------------------------------- ##ROTATES = (1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1) ##PCXROT = ( ## PC1ROT, PC2ROTA, PC2ROTB, PC2ROTB, ## PC2ROTB, PC2ROTB, PC2ROTB, PC2ROTB, ## PC2ROTA, PC2ROTB, PC2ROTB, PC2ROTB, ## PC2ROTB, PC2ROTB, PC2ROTB, PC2ROTA, ## ) #NOTE: modified PCXROT to contain entrys broken into pairs, # to help generate them in format best used by encoder. PCXROT = ( (PC1ROT, PC2ROTA), (PC2ROTB, PC2ROTB), (PC2ROTB, PC2ROTB), (PC2ROTB, PC2ROTB), (PC2ROTA, PC2ROTB), (PC2ROTB, PC2ROTB), (PC2ROTB, PC2ROTB), (PC2ROTB, PC2ROTA), ) #--------------------------------------------------- # Bit reverse, intial permupation, expantion # Initial permutation/expansion table #--------------------------------------------------- #NOTE: this was reordered from original table to make perm3264 logic simpler IE3264=( ( 0x0000000000000000, 0x0000000000800800, 0x0000000000008008, 0x0000000000808808, 0x0000008008000000, 0x0000008008800800, 0x0000008008008008, 0x0000008008808808, 0x0000000080080000, 0x0000000080880800, 0x0000000080088008, 0x0000000080888808, 0x0000008088080000, 0x0000008088880800, 0x0000008088088008, 0x0000008088888808, ), ( 0x0000000000000000, 0x0080080000000000, 0x0000800800000000, 0x0080880800000000, 0x0800000000000080, 0x0880080000000080, 0x0800800800000080, 0x0880880800000080, 0x8008000000000000, 0x8088080000000000, 0x8008800800000000, 0x8088880800000000, 0x8808000000000080, 0x8888080000000080, 0x8808800800000080, 0x8888880800000080, ), ( 0x0000000000000000, 0x0000000000001000, 0x0000000000000010, 0x0000000000001010, 0x0000000010000000, 0x0000000010001000, 0x0000000010000010, 0x0000000010001010, 0x0000000000100000, 0x0000000000101000, 0x0000000000100010, 0x0000000000101010, 0x0000000010100000, 0x0000000010101000, 0x0000000010100010, 0x0000000010101010, ), ( 0x0000000000000000, 0x0000100000000000, 0x0000001000000000, 0x0000101000000000, 0x1000000000000000, 0x1000100000000000, 0x1000001000000000, 0x1000101000000000, 0x0010000000000000, 0x0010100000000000, 0x0010001000000000, 0x0010101000000000, 0x1010000000000000, 0x1010100000000000, 0x1010001000000000, 0x1010101000000000, ), ( 0x0000000000000000, 0x0000000000002000, 0x0000000000000020, 0x0000000000002020, 0x0000000020000000, 0x0000000020002000, 0x0000000020000020, 0x0000000020002020, 0x0000000000200000, 0x0000000000202000, 0x0000000000200020, 0x0000000000202020, 0x0000000020200000, 0x0000000020202000, 0x0000000020200020, 0x0000000020202020, ), ( 0x0000000000000000, 0x0000200000000000, 0x0000002000000000, 0x0000202000000000, 0x2000000000000000, 0x2000200000000000, 0x2000002000000000, 0x2000202000000000, 0x0020000000000000, 0x0020200000000000, 0x0020002000000000, 0x0020202000000000, 0x2020000000000000, 0x2020200000000000, 0x2020002000000000, 0x2020202000000000, ), ( 0x0000000000000000, 0x0000000000004004, 0x0400000000000040, 0x0400000000004044, 0x0000000040040000, 0x0000000040044004, 0x0400000040040040, 0x0400000040044044, 0x0000000000400400, 0x0000000000404404, 0x0400000000400440, 0x0400000000404444, 0x0000000040440400, 0x0000000040444404, 0x0400000040440440, 0x0400000040444444, ), ( 0x0000000000000000, 0x0000400400000000, 0x0000004004000000, 0x0000404404000000, 0x4004000000000000, 0x4004400400000000, 0x4004004004000000, 0x4004404404000000, 0x0040040000000000, 0x0040440400000000, 0x0040044004000000, 0x0040444404000000, 0x4044040000000000, 0x4044440400000000, 0x4044044004000000, 0x4044444404000000, ), ) #--------------------------------------------------- # Table that combines the S, P, and E operations. #--------------------------------------------------- SPE=( ( 0x0080088008200000, 0x0000008008000000, 0x0000000000200020, 0x0080088008200020, 0x0000000000200000, 0x0080088008000020, 0x0000008008000020, 0x0000000000200020, 0x0080088008000020, 0x0080088008200000, 0x0000008008200000, 0x0080080000000020, 0x0080080000200020, 0x0000000000200000, 0x0000000000000000, 0x0000008008000020, 0x0000008008000000, 0x0000000000000020, 0x0080080000200000, 0x0080088008000000, 0x0080088008200020, 0x0000008008200000, 0x0080080000000020, 0x0080080000200000, 0x0000000000000020, 0x0080080000000000, 0x0080088008000000, 0x0000008008200020, 0x0080080000000000, 0x0080080000200020, 0x0000008008200020, 0x0000000000000000, 0x0000000000000000, 0x0080088008200020, 0x0080080000200000, 0x0000008008000020, 0x0080088008200000, 0x0000008008000000, 0x0080080000000020, 0x0080080000200000, 0x0000008008200020, 0x0080080000000000, 0x0080088008000000, 0x0000000000200020, 0x0080088008000020, 0x0000000000000020, 0x0000000000200020, 0x0000008008200000, 0x0080088008200020, 0x0080088008000000, 0x0000008008200000, 0x0080080000200020, 0x0000000000200000, 0x0080080000000020, 0x0000008008000020, 0x0000000000000000, 0x0000008008000000, 0x0000000000200000, 0x0080080000200020, 0x0080088008200000, 0x0000000000000020, 0x0000008008200020, 0x0080080000000000, 0x0080088008000020, ), ( 0x1000800810004004, 0x0000000000000000, 0x0000800810000000, 0x0000000010004004, 0x1000000000004004, 0x1000800800000000, 0x0000800800004004, 0x0000800810000000, 0x0000800800000000, 0x1000000010004004, 0x1000000000000000, 0x0000800800004004, 0x1000000010000000, 0x0000800810004004, 0x0000000010004004, 0x1000000000000000, 0x0000000010000000, 0x1000800800004004, 0x1000000010004004, 0x0000800800000000, 0x1000800810000000, 0x0000000000004004, 0x0000000000000000, 0x1000000010000000, 0x1000800800004004, 0x1000800810000000, 0x0000800810004004, 0x1000000000004004, 0x0000000000004004, 0x0000000010000000, 0x1000800800000000, 0x1000800810004004, 0x1000000010000000, 0x0000800810004004, 0x0000800800004004, 0x1000800810000000, 0x1000800810004004, 0x1000000010000000, 0x1000000000004004, 0x0000000000000000, 0x0000000000004004, 0x1000800800000000, 0x0000000010000000, 0x1000000010004004, 0x0000800800000000, 0x0000000000004004, 0x1000800810000000, 0x1000800800004004, 0x0000800810004004, 0x0000800800000000, 0x0000000000000000, 0x1000000000004004, 0x1000000000000000, 0x1000800810004004, 0x0000800810000000, 0x0000000010004004, 0x1000000010004004, 0x0000000010000000, 0x1000800800000000, 0x0000800800004004, 0x1000800800004004, 0x1000000000000000, 0x0000000010004004, 0x0000800810000000, ), ( 0x0000000000400410, 0x0010004004400400, 0x0010000000000000, 0x0010000000400410, 0x0000004004000010, 0x0000000000400400, 0x0010000000400410, 0x0010004004000000, 0x0010000000400400, 0x0000004004000000, 0x0000004004400400, 0x0000000000000010, 0x0010004004400410, 0x0010000000000010, 0x0000000000000010, 0x0000004004400410, 0x0000000000000000, 0x0000004004000010, 0x0010004004400400, 0x0010000000000000, 0x0010000000000010, 0x0010004004400410, 0x0000004004000000, 0x0000000000400410, 0x0000004004400410, 0x0010000000400400, 0x0010004004000010, 0x0000004004400400, 0x0010004004000000, 0x0000000000000000, 0x0000000000400400, 0x0010004004000010, 0x0010004004400400, 0x0010000000000000, 0x0000000000000010, 0x0000004004000000, 0x0010000000000010, 0x0000004004000010, 0x0000004004400400, 0x0010000000400410, 0x0000000000000000, 0x0010004004400400, 0x0010004004000000, 0x0000004004400410, 0x0000004004000010, 0x0000000000400400, 0x0010004004400410, 0x0000000000000010, 0x0010004004000010, 0x0000000000400410, 0x0000000000400400, 0x0010004004400410, 0x0000004004000000, 0x0010000000400400, 0x0010000000400410, 0x0010004004000000, 0x0010000000400400, 0x0000000000000000, 0x0000004004400410, 0x0010000000000010, 0x0000000000400410, 0x0010004004000010, 0x0010000000000000, 0x0000004004400400, ), ( 0x0800100040040080, 0x0000100000001000, 0x0800000000000080, 0x0800100040041080, 0x0000000000000000, 0x0000000040041000, 0x0800100000001080, 0x0800000040040080, 0x0000100040041000, 0x0800000000001080, 0x0000000000001000, 0x0800100000000080, 0x0800000000001080, 0x0800100040040080, 0x0000000040040000, 0x0000000000001000, 0x0800000040041080, 0x0000100040040000, 0x0000100000000000, 0x0800000000000080, 0x0000100040040000, 0x0800100000001080, 0x0000000040041000, 0x0000100000000000, 0x0800100000000080, 0x0000000000000000, 0x0800000040040080, 0x0000100040041000, 0x0000100000001000, 0x0800000040041080, 0x0800100040041080, 0x0000000040040000, 0x0800000040041080, 0x0800100000000080, 0x0000000040040000, 0x0800000000001080, 0x0000100040040000, 0x0000100000001000, 0x0800000000000080, 0x0000000040041000, 0x0800100000001080, 0x0000000000000000, 0x0000100000000000, 0x0800000040040080, 0x0000000000000000, 0x0800000040041080, 0x0000100040041000, 0x0000100000000000, 0x0000000000001000, 0x0800100040041080, 0x0800100040040080, 0x0000000040040000, 0x0800100040041080, 0x0800000000000080, 0x0000100000001000, 0x0800100040040080, 0x0800000040040080, 0x0000100040040000, 0x0000000040041000, 0x0800100000001080, 0x0800100000000080, 0x0000000000001000, 0x0800000000001080, 0x0000100040041000, ), ( 0x0000000000800800, 0x0000001000000000, 0x0040040000000000, 0x2040041000800800, 0x2000001000800800, 0x0040040000800800, 0x2040041000000000, 0x0000001000800800, 0x0000001000000000, 0x2000000000000000, 0x2000000000800800, 0x0040041000000000, 0x2040040000800800, 0x2000001000800800, 0x0040041000800800, 0x0000000000000000, 0x0040041000000000, 0x0000000000800800, 0x2000001000000000, 0x2040040000000000, 0x0040040000800800, 0x2040041000000000, 0x0000000000000000, 0x2000000000800800, 0x2000000000000000, 0x2040040000800800, 0x2040041000800800, 0x2000001000000000, 0x0000001000800800, 0x0040040000000000, 0x2040040000000000, 0x0040041000800800, 0x0040041000800800, 0x2040040000800800, 0x2000001000000000, 0x0000001000800800, 0x0000001000000000, 0x2000000000000000, 0x2000000000800800, 0x0040040000800800, 0x0000000000800800, 0x0040041000000000, 0x2040041000800800, 0x0000000000000000, 0x2040041000000000, 0x0000000000800800, 0x0040040000000000, 0x2000001000000000, 0x2040040000800800, 0x0040040000000000, 0x0000000000000000, 0x2040041000800800, 0x2000001000800800, 0x0040041000800800, 0x2040040000000000, 0x0000001000000000, 0x0040041000000000, 0x2000001000800800, 0x0040040000800800, 0x2040040000000000, 0x2000000000000000, 0x2040041000000000, 0x0000001000800800, 0x2000000000800800, ), ( 0x4004000000008008, 0x4004000020000000, 0x0000000000000000, 0x0000200020008008, 0x4004000020000000, 0x0000200000000000, 0x4004200000008008, 0x0000000020000000, 0x4004200000000000, 0x4004200020008008, 0x0000200020000000, 0x0000000000008008, 0x0000200000008008, 0x4004000000008008, 0x0000000020008008, 0x4004200020000000, 0x0000000020000000, 0x4004200000008008, 0x4004000020008008, 0x0000000000000000, 0x0000200000000000, 0x4004000000000000, 0x0000200020008008, 0x4004000020008008, 0x4004200020008008, 0x0000000020008008, 0x0000000000008008, 0x4004200000000000, 0x4004000000000000, 0x0000200020000000, 0x4004200020000000, 0x0000200000008008, 0x4004200000000000, 0x0000000000008008, 0x0000200000008008, 0x4004200020000000, 0x0000200020008008, 0x4004000020000000, 0x0000000000000000, 0x0000200000008008, 0x0000000000008008, 0x0000200000000000, 0x4004000020008008, 0x0000000020000000, 0x4004000020000000, 0x4004200020008008, 0x0000200020000000, 0x4004000000000000, 0x4004200020008008, 0x0000200020000000, 0x0000000020000000, 0x4004200000008008, 0x4004000000008008, 0x0000000020008008, 0x4004200020000000, 0x0000000000000000, 0x0000200000000000, 0x4004000000008008, 0x4004200000008008, 0x0000200020008008, 0x0000000020008008, 0x4004200000000000, 0x4004000000000000, 0x4004000020008008, ), ( 0x0000400400000000, 0x0020000000000000, 0x0020000000100000, 0x0400000000100040, 0x0420400400100040, 0x0400400400000040, 0x0020400400000000, 0x0000000000000000, 0x0000000000100000, 0x0420000000100040, 0x0420000000000040, 0x0000400400100000, 0x0400000000000040, 0x0020400400100000, 0x0000400400100000, 0x0420000000000040, 0x0420000000100040, 0x0000400400000000, 0x0400400400000040, 0x0420400400100040, 0x0000000000000000, 0x0020000000100000, 0x0400000000100040, 0x0020400400000000, 0x0400400400100040, 0x0420400400000040, 0x0020400400100000, 0x0400000000000040, 0x0420400400000040, 0x0400400400100040, 0x0020000000000000, 0x0000000000100000, 0x0420400400000040, 0x0000400400100000, 0x0400400400100040, 0x0420000000000040, 0x0000400400000000, 0x0020000000000000, 0x0000000000100000, 0x0400400400100040, 0x0420000000100040, 0x0420400400000040, 0x0020400400000000, 0x0000000000000000, 0x0020000000000000, 0x0400000000100040, 0x0400000000000040, 0x0020000000100000, 0x0000000000000000, 0x0420000000100040, 0x0020000000100000, 0x0020400400000000, 0x0420000000000040, 0x0000400400000000, 0x0420400400100040, 0x0000000000100000, 0x0020400400100000, 0x0400000000000040, 0x0400400400000040, 0x0420400400100040, 0x0400000000100040, 0x0020400400100000, 0x0000400400100000, 0x0400400400000040, ), ( 0x8008000080082000, 0x0000002080082000, 0x8008002000000000, 0x0000000000000000, 0x0000002000002000, 0x8008000080080000, 0x0000000080082000, 0x8008002080082000, 0x8008000000000000, 0x0000000000002000, 0x0000002080080000, 0x8008002000000000, 0x8008002080080000, 0x8008002000002000, 0x8008000000002000, 0x0000000080082000, 0x0000002000000000, 0x8008002080080000, 0x8008000080080000, 0x0000002000002000, 0x8008002080082000, 0x8008000000002000, 0x0000000000000000, 0x0000002080080000, 0x0000000000002000, 0x0000000080080000, 0x8008002000002000, 0x8008000080082000, 0x0000000080080000, 0x0000002000000000, 0x0000002080082000, 0x8008000000000000, 0x0000000080080000, 0x0000002000000000, 0x8008000000002000, 0x8008002080082000, 0x8008002000000000, 0x0000000000002000, 0x0000000000000000, 0x0000002080080000, 0x8008000080082000, 0x8008002000002000, 0x0000002000002000, 0x8008000080080000, 0x0000002080082000, 0x8008000000000000, 0x8008000080080000, 0x0000002000002000, 0x8008002080082000, 0x0000000080080000, 0x0000000080082000, 0x8008000000002000, 0x0000002080080000, 0x8008002000000000, 0x8008002000002000, 0x0000000080082000, 0x8008000000000000, 0x0000002080082000, 0x8008002080080000, 0x0000000000000000, 0x0000000000002000, 0x8008000080082000, 0x0000002000000000, 0x8008002080080000, ), ) #--------------------------------------------------- # compressed/interleaved => final permutation table # Compression, final permutation, bit reverse #--------------------------------------------------- #NOTE: this was reordered from original table to make perm6464 logic simpler CF6464=( ( 0x0000000000000000, 0x0000002000000000, 0x0000200000000000, 0x0000202000000000, 0x0020000000000000, 0x0020002000000000, 0x0020200000000000, 0x0020202000000000, 0x2000000000000000, 0x2000002000000000, 0x2000200000000000, 0x2000202000000000, 0x2020000000000000, 0x2020002000000000, 0x2020200000000000, 0x2020202000000000, ), ( 0x0000000000000000, 0x0000000200000000, 0x0000020000000000, 0x0000020200000000, 0x0002000000000000, 0x0002000200000000, 0x0002020000000000, 0x0002020200000000, 0x0200000000000000, 0x0200000200000000, 0x0200020000000000, 0x0200020200000000, 0x0202000000000000, 0x0202000200000000, 0x0202020000000000, 0x0202020200000000, ), ( 0x0000000000000000, 0x0000000000000020, 0x0000000000002000, 0x0000000000002020, 0x0000000000200000, 0x0000000000200020, 0x0000000000202000, 0x0000000000202020, 0x0000000020000000, 0x0000000020000020, 0x0000000020002000, 0x0000000020002020, 0x0000000020200000, 0x0000000020200020, 0x0000000020202000, 0x0000000020202020, ), ( 0x0000000000000000, 0x0000000000000002, 0x0000000000000200, 0x0000000000000202, 0x0000000000020000, 0x0000000000020002, 0x0000000000020200, 0x0000000000020202, 0x0000000002000000, 0x0000000002000002, 0x0000000002000200, 0x0000000002000202, 0x0000000002020000, 0x0000000002020002, 0x0000000002020200, 0x0000000002020202, ), ( 0x0000000000000000, 0x0000008000000000, 0x0000800000000000, 0x0000808000000000, 0x0080000000000000, 0x0080008000000000, 0x0080800000000000, 0x0080808000000000, 0x8000000000000000, 0x8000008000000000, 0x8000800000000000, 0x8000808000000000, 0x8080000000000000, 0x8080008000000000, 0x8080800000000000, 0x8080808000000000, ), ( 0x0000000000000000, 0x0000000800000000, 0x0000080000000000, 0x0000080800000000, 0x0008000000000000, 0x0008000800000000, 0x0008080000000000, 0x0008080800000000, 0x0800000000000000, 0x0800000800000000, 0x0800080000000000, 0x0800080800000000, 0x0808000000000000, 0x0808000800000000, 0x0808080000000000, 0x0808080800000000, ), ( 0x0000000000000000, 0x0000000000000080, 0x0000000000008000, 0x0000000000008080, 0x0000000000800000, 0x0000000000800080, 0x0000000000808000, 0x0000000000808080, 0x0000000080000000, 0x0000000080000080, 0x0000000080008000, 0x0000000080008080, 0x0000000080800000, 0x0000000080800080, 0x0000000080808000, 0x0000000080808080, ), ( 0x0000000000000000, 0x0000000000000008, 0x0000000000000800, 0x0000000000000808, 0x0000000000080000, 0x0000000000080008, 0x0000000000080800, 0x0000000000080808, 0x0000000008000000, 0x0000000008000008, 0x0000000008000800, 0x0000000008000808, 0x0000000008080000, 0x0000000008080008, 0x0000000008080800, 0x0000000008080808, ), ( 0x0000000000000000, 0x0000001000000000, 0x0000100000000000, 0x0000101000000000, 0x0010000000000000, 0x0010001000000000, 0x0010100000000000, 0x0010101000000000, 0x1000000000000000, 0x1000001000000000, 0x1000100000000000, 0x1000101000000000, 0x1010000000000000, 0x1010001000000000, 0x1010100000000000, 0x1010101000000000, ), ( 0x0000000000000000, 0x0000000100000000, 0x0000010000000000, 0x0000010100000000, 0x0001000000000000, 0x0001000100000000, 0x0001010000000000, 0x0001010100000000, 0x0100000000000000, 0x0100000100000000, 0x0100010000000000, 0x0100010100000000, 0x0101000000000000, 0x0101000100000000, 0x0101010000000000, 0x0101010100000000, ), ( 0x0000000000000000, 0x0000000000000010, 0x0000000000001000, 0x0000000000001010, 0x0000000000100000, 0x0000000000100010, 0x0000000000101000, 0x0000000000101010, 0x0000000010000000, 0x0000000010000010, 0x0000000010001000, 0x0000000010001010, 0x0000000010100000, 0x0000000010100010, 0x0000000010101000, 0x0000000010101010, ), ( 0x0000000000000000, 0x0000000000000001, 0x0000000000000100, 0x0000000000000101, 0x0000000000010000, 0x0000000000010001, 0x0000000000010100, 0x0000000000010101, 0x0000000001000000, 0x0000000001000001, 0x0000000001000100, 0x0000000001000101, 0x0000000001010000, 0x0000000001010001, 0x0000000001010100, 0x0000000001010101, ), ( 0x0000000000000000, 0x0000004000000000, 0x0000400000000000, 0x0000404000000000, 0x0040000000000000, 0x0040004000000000, 0x0040400000000000, 0x0040404000000000, 0x4000000000000000, 0x4000004000000000, 0x4000400000000000, 0x4000404000000000, 0x4040000000000000, 0x4040004000000000, 0x4040400000000000, 0x4040404000000000, ), ( 0x0000000000000000, 0x0000000400000000, 0x0000040000000000, 0x0000040400000000, 0x0004000000000000, 0x0004000400000000, 0x0004040000000000, 0x0004040400000000, 0x0400000000000000, 0x0400000400000000, 0x0400040000000000, 0x0400040400000000, 0x0404000000000000, 0x0404000400000000, 0x0404040000000000, 0x0404040400000000, ), ( 0x0000000000000000, 0x0000000000000040, 0x0000000000004000, 0x0000000000004040, 0x0000000000400000, 0x0000000000400040, 0x0000000000404000, 0x0000000000404040, 0x0000000040000000, 0x0000000040000040, 0x0000000040004000, 0x0000000040004040, 0x0000000040400000, 0x0000000040400040, 0x0000000040404000, 0x0000000040404040, ), ( 0x0000000000000000, 0x0000000000000004, 0x0000000000000400, 0x0000000000000404, 0x0000000000040000, 0x0000000000040004, 0x0000000000040400, 0x0000000000040404, 0x0000000004000000, 0x0000000004000004, 0x0000000004000400, 0x0000000004000404, 0x0000000004040000, 0x0000000004040004, 0x0000000004040400, 0x0000000004040404, ), ) #========================================================= #eof load_data #========================================================= def permute(c, p): """Returns the permutation of the given 32-bit or 64-bit code with the specified permutation table.""" #NOTE: only difference between 32 & 64 bit permutations #is that len(p)==8 for 32 bit, and len(p)==16 for 64 bit. out = 0 for r in p: out |= r[c&0xf] c >>= 4 return out #========================================================= #des frontend #========================================================= def expand_des_key(key): "convert 7 byte des key to 8 byte des key (by adding parity bit every 7 bits)" if not isinstance(key, bytes): raise TypeError("key must be bytes, not %s" % (type(key),)) #NOTE: could probably do this much more cleverly and efficiently, # but no need really given it's use. #NOTE: the parity bits are generally ignored, including by des_encrypt_block below assert len(key) == 7 def iter_bits(source): for c in source: v = bord(c) for i in xrange(7,-1,-1): yield (v>>i) & 1 out = 0 p = 1 for i, b in enumerate(iter_bits(key)): out = (out<<1) + b p ^= b if i % 7 == 6: out = (out<<1) + p p = 1 return bjoin_ints( ((out>>s) & 0xFF) for s in xrange(8*7,-8,-8) ) def des_encrypt_block(key, input): """do traditional encryption of a single DES block :arg key: 8 byte des key :arg input: 8 byte plaintext :returns: 8 byte ciphertext all values must be :class:`bytes` """ if not isinstance(key, bytes): raise TypeError("key must be bytes, not %s" % (type(key),)) if len(key) == 7: key = expand_des_key(key) assert len(key) == 8 if not isinstance(input, bytes): raise TypeError("input must be bytes, not %s" % (type(input),)) assert len(input) == 8 input = bytes_to_int(input) key = bytes_to_int(key) out = mdes_encrypt_int_block(key, input, 0, 1) return int_to_bytes(out, 8) def mdes_encrypt_int_block(key, input, salt=0, rounds=1): """do modified multi-round DES encryption of single DES block. the function implements the salted, variable-round version of DES used by :class:`~passlib.hash.des_crypt` and related variants. it also can perform regular DES encryption by using ``salt=0, rounds=1`` (the default values). :arg key: 8 byte des key as integer :arg input: 8 byte plaintext block as integer :arg salt: integer 24 bit salt, used to mutate output (defaults to 0) :arg rounds: number of rounds of DES encryption to apply (defaults to 1) The salt is used to to mutate the normal DES encrypt operation by swapping bits ``i`` and ``i+24`` in the DES E-Box output if and only if bit ``i`` is set in the salt value. Thus, if the salt is set to ``0``, normal DES encryption is performed. :returns: resulting block as 8 byte integer """ global SPE, PCXROT, IE3264, CF6464 #bounds check assert 0 <= input <= INT_64_MAX, "input value out of range" assert 0 <= salt <= INT_24_MAX, "salt value out of range" assert rounds >= 0, "rounds out of range" assert 0 <= key <= INT_64_MAX, "key value out of range" #load tables if not already done if PCXROT is None: load_tables() #convert key int -> key schedule #NOTE: generation was modified to output two elements at a time, #to optimize for per-round algorithm below. mask = ~0x0303030300000000 def _gen(K): for p_even, p_odd in PCXROT: K1 = permute(K, p_even) K = permute(K1, p_odd) yield K1 & mask, K & mask ks_list = list(_gen(key)) #expand 24 bit salt -> 32 bit salt = ( ((salt & 0x00003f) << 26) | ((salt & 0x000fc0) << 12) | ((salt & 0x03f000) >> 2) | ((salt & 0xfc0000) >> 16) ) #init L & R if input == 0: L = R = 0 else: L = ((input >> 31) & 0xaaaaaaaa) | (input & 0x55555555) L = permute(L, IE3264) R = ((input >> 32) & 0xaaaaaaaa) | ((input >> 1) & 0x55555555) R = permute(R, IE3264) #load SPE into local vars to speed things up and remove an array access call SPE0, SPE1, SPE2, SPE3, SPE4, SPE5, SPE6, SPE7 = SPE #run specified number of passed while rounds: rounds -= 1 #run over each part of the schedule, 2 parts at a time for ks_even, ks_odd in ks_list: k = ((R>>32) ^ R) & salt #use the salt to alter specific bits B = (k<<32) ^ k ^ R ^ ks_even L ^= (SPE0[(B>>58)&0x3f] ^ SPE1[(B>>50)&0x3f] ^ SPE2[(B>>42)&0x3f] ^ SPE3[(B>>34)&0x3f] ^ SPE4[(B>>26)&0x3f] ^ SPE5[(B>>18)&0x3f] ^ SPE6[(B>>10)&0x3f] ^ SPE7[(B>>2)&0x3f]) k = ((L>>32) ^ L) & salt #use the salt to alter specific bits B = (k<<32) ^ k ^ L ^ ks_odd R ^= (SPE0[(B>>58)&0x3f] ^ SPE1[(B>>50)&0x3f] ^ SPE2[(B>>42)&0x3f] ^ SPE3[(B>>34)&0x3f] ^ SPE4[(B>>26)&0x3f] ^ SPE5[(B>>18)&0x3f] ^ SPE6[(B>>10)&0x3f] ^ SPE7[(B>>2)&0x3f]) # swap L and R L, R = R, L C = ( ((L>>3) & 0x0f0f0f0f00000000) | ((L<<33) & 0xf0f0f0f000000000) | ((R>>35) & 0x000000000f0f0f0f) | ((R<<1) & 0x00000000f0f0f0f0) ) C = permute(C, CF6464) return C #========================================================= #eof #========================================================= passlib-1.5.3/passlib/utils/pbkdf2.py0000644000175000017500000002371511643466373020647 0ustar biscuitbiscuit00000000000000"""passlib.pbkdf2 - PBKDF2 support this module is getting increasingly poorly named. maybe rename to "kdf" since it's getting more key derivation functions added. """ #================================================================================= #imports #================================================================================= #core from binascii import unhexlify 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, to_bytes, native_str, b, bytes #local __all__ = [ "hmac_sha1", "get_prf", "pbkdf1", "pbkdf2", ] # Py2k # from cStringIO import StringIO as BytesIO # Py3k # #from io import BytesIO # end Py3k # #================================================================================= #quick hmac_sha1 implementation used various places #================================================================================= 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(b('x'),b('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 == b(',\x1cb\xe0H\xa5\x82M\xfb>\xd6\x98\xef\x8e\xf9oQ\x85\xa3i'): hmac_sha1 = _EVP.hmac #================================================================================= #general prf lookup #================================================================================= def _get_hmac_prf(digest): "helper to return HMAC prf for specific digest" #check if m2crypto is present and supports requested digest if _EVP: try: result = _EVP.hmac(b('x'), b('y'), digest) except ValueError: pass else: #it does. so use M2Crypto's hmac & digest code hmac_const = _EVP.hmac def prf(key, msg): "prf(key,msg)->digest; generated by passlib.utils.pbkdf2.get_prf()" return hmac_const(key, msg, digest) prf.__name__ = "hmac_" + digest digest_size = len(result) return prf, 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 prf(key, msg): "prf(key,msg)->digest; generated by passlib.utils.pbkdf2.get_prf()" return hmac_const(key, msg, digest_const).digest() prf.__name__ = "hmac_" + digest return prf, digest_size #cache mapping prf name/func -> (func, digest_size) _prf_cache = {} def _clear_prf_cache(): "helper for unit tests" _prf_cache.clear() def get_prf(name): """lookup pseudo-random family (prf) by name. :arg name: this must be the name of a recognized prf. currently this only recognizes names with the format :samp:`hmac-{digest}`, where :samp:`{digest}` is the name of a hash function such as ``md5``, ``sha256``, etc. this can also be a callable with the signature ``prf(secret, message) -> digest``, in which case it will be returned unchanged. :raises ValueError: if the name is not known :raises TypeError: if the name is not a callable or string :returns: a tuple of :samp:`({func}, {digest_size})`. * :samp:`{func}` is a function implementing the specified prf, and has the signature ``func(secret, message) -> digest``. * :samp:`{digest_size}` is an integer indicating the number of bytes the function returns. usage example:: >>> from passlib.utils.pbkdf2 import get_prf >>> hmac_sha256, dsize = get_prf("hmac-sha256") >>> hmac_sha256 >>> dsize 32 >>> digest = hmac_sha256('password', 'message') this function will attempt to return the fastest implementation it can find; if M2Crypto is present, and supports the specified prf, :func:`M2Crypto.EVP.hmac` will be used behind the scenes. """ global _prf_cache if name in _prf_cache: return _prf_cache[name] if isinstance(name, native_str): if name.startswith("hmac-") or name.startswith("hmac_"): retval = _get_hmac_prf(name[5:]) else: raise ValueError("unknown prf algorithm: %r" % (name,)) elif callable(name): #assume it's a callable, use it directly digest_size = len(name(b('x'),b('y'))) retval = (name, digest_size) else: raise TypeError("prf must be string or callable") _prf_cache[name] = retval return retval #================================================================================= #pbkdf1 support #================================================================================= def pbkdf1(secret, salt, rounds, keylen, hash="sha1"): """pkcs#5 password-based key derivation v1.5 :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 hash: hash function to use. if specified, it must be one of the following: * a callable with the prototype ``hash(message) -> raw digest`` * a string matching one of the hashes recognized by hashlib :returns: raw bytes of generated key .. note:: This algorithm is deprecated, new code should use PBKDF2. Among other reasons, ``keylen`` cannot be larger than the digest size of the specified hash. """ #prepare secret & salt if not isinstance(secret, bytes): raise TypeError("secret must be bytes, not %s" % (type(secret),)) if not isinstance(salt, bytes): raise TypeError("salt must be bytes, not %s" % (type(salt),)) #prepare 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") #prep keylen if keylen < 0: raise ValueError("keylen must be at least 0") #resolve hash if isinstance(hash, native_str): #check for builtin hash hf = getattr(hashlib, hash, None) if hf is None: #check for ssl hash #NOTE: if hash unknown, will throw ValueError, which we'd just # reraise anyways; so instead of checking, we just let it get # thrown during first use, below def hf(msg): return hashlib.new(hash, msg) #run pbkdf1 block = hf(secret + salt).digest() if keylen > len(block): raise ValueError, "keylength too large for digest: %r > %r" % (keylen, len(block)) r = 1 while r < rounds: block = hf(block).digest() r += 1 return block[:keylen] #================================================================================= #pbkdf2 #================================================================================= MAX_BLOCKS = 0xffffffff #2**32-1 MAX_HMAC_SHA1_KEYLEN = MAX_BLOCKS*20 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 family to use for key strengthening. this can be any string or callable accepted by :func:`get_prf`. this defaults to ``hmac-sha1`` (the only prf explicitly listed in the PBKDF2 specification) :returns: raw bytes of generated key """ #prepare secret & salt if not isinstance(secret, bytes): raise TypeError("secret must be bytes, not %s" % (type(secret),)) if not isinstance(salt, bytes): raise TypeError("salt must be bytes, not %s" % (type(salt),)) #prepare 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: #NOTE: doing check here, because M2crypto won't take longs (which this is, under 32bit) if keylen > MAX_HMAC_SHA1_KEYLEN: raise ValueError("key length too long") #NOTE: M2crypto reliably segfaults for me if given keylengths # larger than 40 (crashes at 41 on one system, 61 on another). # so just avoiding it for longer calls. if keylen < 41: return _EVP.pbkdf2(secret, salt, rounds, keylen) #resolve prf encode_block, digest_size = get_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 = BytesIO() 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 j = 1 while j < rounds: tmp = encode_block(secret, tmp) block = xor_bytes(block, tmp) j += 1 write(block) #and done return out.getvalue()[:keylen] #================================================================================= #eof #================================================================================= passlib-1.5.3/passlib/utils/handlers.py0000644000175000017500000013562211643754032021270 0ustar biscuitbiscuit00000000000000"""passlib.handler - code for implementing handlers, and global registry for handlers""" #========================================================= #imports #========================================================= from __future__ import with_statement #core import inspect import re import hashlib import logging; log = logging.getLogger(__name__) import time import os from warnings import warn #site #libs from passlib.registry import get_crypt_handler from passlib.utils import to_hash_str, bytes, b, \ classproperty, h64, getrandstr, getrandbytes, \ rng, is_crypt_handler, ALL_BYTE_VALUES, MissingBackendError #pkg #local __all__ = [ #framework for implementing handlers 'StaticHandler', 'GenericHandler', 'HasRawChecksum', 'HasManyIdents', 'HasSalt', 'HasRawSalt', 'HasRounds', 'HasManyBackends', 'PrefixWrapper', ] #========================================================= #constants #========================================================= #common salt_chars & checksum_chars values H64_CHARS = h64.CHARS B64_CHARS = u"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" PADDED_B64_CHARS = B64_CHARS + u"=" U64_CHARS = u"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" HEX_CHARS = u"0123456789abcdefABCDEF" UC_HEX_CHARS = u"0123456789ABCDEF" LC_HEX_CHARS = u"0123456789abcdef" #========================================================= #identify helpers #========================================================= def identify_regexp(hash, pat): "identify() helper for matching regexp" if not hash: return False if isinstance(hash, bytes): try: hash = hash.decode("ascii") except UnicodeDecodeError: return False return pat.match(hash) is not None def identify_prefix(hash, prefix): "identify() helper for matching against prefixes" #NOTE: prefix may be a tuple of strings (since startswith supports that) if not hash: return False if isinstance(hash, bytes): try: hash = hash.decode("ascii") except UnicodeDecodeError: return False return hash.startswith(prefix) #========================================================= #parsing helpers #========================================================= def parse_mc2(hash, prefix, name="", sep=u"$"): "parse hash using 2-part modular crypt format" assert isinstance(prefix, unicode) assert isinstance(sep, unicode) #eg: MD5-Crypt: $1$salt[$checksum] if not hash: raise ValueError("no hash specified") if isinstance(hash, bytes): hash = hash.decode('ascii') if not hash.startswith(prefix): raise ValueError("not a valid %s hash (wrong prefix)" % (name,)) parts = hash[len(prefix):].split(sep) if len(parts) == 2: salt, chk = parts return salt, chk or None elif len(parts) == 1: return parts[0], None else: raise ValueError("not a valid %s hash (malformed)" % (name,)) def parse_mc3(hash, prefix, name="", sep=u"$"): "parse hash using 3-part modular crypt format" assert isinstance(prefix, unicode) assert isinstance(sep, unicode) #eg: SHA1-Crypt: $sha1$rounds$salt[$checksum] if not hash: raise ValueError("no hash specified") if isinstance(hash, bytes): hash = hash.decode('ascii') if not hash.startswith(prefix): raise ValueError("not a valid %s hash" % (name,)) parts = hash[len(prefix):].split(sep) if len(parts) == 3: rounds, salt, chk = parts return rounds, salt, chk or None elif len(parts) == 2: rounds, salt = parts return rounds, salt, None else: raise ValueError("not a valid %s hash" % (name,)) #===================================================== #formatting helpers #===================================================== def render_mc2(ident, salt, checksum, sep=u"$"): "format hash using 2-part modular crypt format; inverse of parse_mc2" if checksum: hash = u"%s%s%s%s" % (ident, salt, sep, checksum) else: hash = u"%s%s" % (ident, salt) return to_hash_str(hash) def render_mc3(ident, rounds, salt, checksum, sep=u"$"): "format hash using 3-part modular crypt format; inverse of parse_mc3" if checksum: hash = u"%s%s%s%s%s%s" % (ident, rounds, sep, salt, sep, checksum) else: hash = u"%s%s%s%s" % (ident, rounds, sep, salt) return to_hash_str(hash) #===================================================== #StaticHandler #===================================================== class StaticHandler(object): """helper class for implementing hashes which have no settings. This class is designed to help in writing hash handlers which have no settings whatsoever; that is to say: no salt, no rounds, etc. These hashes can typically be recognized by the fact that they will always hash a password to *exactly* the same hash string. Usage ===== In order to use this class, just subclass it, and then do the following: * fill out the :attr:`name` attribute with the name of your hash. * provide an implementation of the :meth:`~PasswordHash.genhash` method. * provide an implementation of the :meth:`~PasswordHash.identify` method. (a default is provided, but it's inefficient). Based on the methods above, this class provides: * a :meth:`genconfig` method that returns ``None``. * a :meth:`encrypt` method that wraps :meth:`genhash`. * a :meth:`verify` method that wraps :meth:`genhash`. Implementation Details ====================== The :meth:`genhash` method you implement must accept all valid hashes, *as well as* whatever value :meth:`genconfig` returns. This defaults to ``None``, but you may set the :attr:`_stub_config` attr to a random hash string, and :meth:`genconfig` will return this instead. The default :meth:`verify` method uses simple equality to compare hash strings. If your hash may have multiple encoding (eg case-insensitive), this method (or the private :meth:`_norm_hash` method) should be overridden on a per-handler basis. If your hash has options, such as multiple identifiers, salts, or variable rounds, this is not the right class to start with. You should use the :class:`GenericHandler` class, or implement the handler yourself. """ #===================================================== #class attrs #===================================================== name = None #required - handler name setting_kwds = () context_kwds = () _stub_config = None #===================================================== #methods #===================================================== @classmethod def identify(cls, hash): #NOTE: this relys on genhash() throwing error for invalid hashes. # this approach is bad because genhash may take a long time on valid hashes, # so subclasses *really* should override this. try: cls.genhash('fakesecret', hash) return True except ValueError: return False @classmethod def genconfig(cls): return cls._stub_config @classmethod def genhash(cls, secret, config, **context): raise NotImplementedError("%s subclass must implement genhash()" % (cls,)) @classmethod def encrypt(cls, secret, *cargs, **context): #NOTE: subclasses generally won't need to override this. config = cls.genconfig() return cls.genhash(secret, config, *cargs, **context) @classmethod def verify(cls, secret, hash, *cargs, **context): #NOTE: subclasses generally won't need to override this. if hash is None: raise ValueError("no hash specified") hash = cls._norm_hash(hash) result = cls.genhash(secret, hash, *cargs, **context) return cls._norm_hash(result) == hash @classmethod def _norm_hash(cls, hash): """[helper for verify] normalize hash for comparsion purposes""" #NOTE: this is mainly provided for case-insenstive subclasses to override. if isinstance(hash, bytes): hash = hash.decode("ascii") return hash #===================================================== #eoc #===================================================== #===================================================== #GenericHandler #===================================================== class GenericHandler(object): """helper class for implementing hash handlers. :param checksum: this should contain the digest portion of a parsed hash (mainly provided when the constructor is called by :meth:`from_string()`). defaults to ``None``. :param strict: If ``True``, this flag signals that :meth:`norm_checksum` (as well as the other :samp:`norm_{xxx}` methods provided by the mixins) should throw a :exc:`ValueError` if any errors are found in any of the provided parameters. If ``False`` (the default), the :exc:`ValueError` should only be throw if the error is not recoverable (eg: clipping salt string to max size). This is typically only set to ``True`` when the constructor is called by :meth:`from_string`, in order to perform validation on the hash string it's parsing; whereas :meth:`encrypt` does not set this flag, allowing user-provided values to be handled in a more permissive manner. Class Attributes ================ .. attribute:: ident [optional] If this attribute is filled in, the default :meth:`identify` method will use it as a identifying prefix that can be used to recognize instances of this handler's hash. Filling this out is recommended for speed. This should be a unicode str. .. attribute:: checksum_size [optional] Specifies the number of characters that should be expected in the checksum string. If omitted, no check will be performed. .. attribute:: checksum_chars [optional] A string listing all the characters allowed in the checksum string. If omitted, no check will be performed. This should be a unicode str. Instance Attributes =================== .. attribute:: checksum The checksum string as provided by the constructor (after passing through :meth:`norm_checksum`). Required Class Methods ====================== The following methods must be provided by handler subclass: .. automethod:: from_string .. automethod:: to_string .. automethod:: calc_checksum Default Class Methods ===================== The following methods provide generally useful default behaviors, though they may be overridden if the hash subclass needs to: .. automethod:: norm_checksum .. automethod:: genconfig .. automethod:: genhash .. automethod:: identify .. automethod:: encrypt .. automethod:: verify """ #===================================================== #class attr #===================================================== context_kwds = () ident = None #identifier prefix if known checksum_size = None #if specified, norm_checksum will require this length checksum_chars = None #if specified, norm_checksum() will validate this #===================================================== #instance attrs #===================================================== checksum = None #===================================================== #init #===================================================== def __init__(self, checksum=None, strict=False, **kwds): self.checksum = self.norm_checksum(checksum, strict=strict) super(GenericHandler, self).__init__(**kwds) #XXX: support a subclass-specified _norm_checksum method # to normalize for the purposes of verify()? # currently the code cost seems smaller to just have classes override verify. @classmethod def norm_checksum(cls, checksum, strict=False): "validates checksum keyword against class requirements, returns normalized version of checksum" if checksum is None: if strict: raise ValueError("checksum not specified") return None if isinstance(checksum, bytes): checksum = checksum.decode('ascii') cc = cls.checksum_size if cc and len(checksum) != cc: raise ValueError("%s checksum must be %d characters" % (cls.name, cc)) cs = cls.checksum_chars if cs and any(c not in cs for c in checksum): raise ValueError("invalid characters in %s checksum" % (cls.name,)) return checksum #===================================================== #password hash api - formatting interface #===================================================== @classmethod def identify(cls, hash): #NOTE: subclasses may wish to use faster / simpler identify, # and raise value errors only when an invalid (but identifiable) string is parsed if not hash: return False ident = cls.ident if ident: #class specified a known prefix to look for assert isinstance(ident, unicode) if isinstance(hash, bytes): ident = ident.encode('ascii') return hash.startswith(ident) else: #don't have that, so fall back to trying to parse hash #(inefficient for these purposes) try: cls.from_string(hash) return True except ValueError: return False @classmethod def from_string(cls, hash): #pragma: no cover """return parsed instance from hash/configuration string :raises ValueError: if hash is incorrectly formatted :returns: hash parsed into components, for formatting / calculating checksum. """ raise NotImplementedError("%s must implement from_string()" % (cls,)) def to_string(self): #pragma: no cover """render instance to hash or configuration string :returns: if :attr:`checksum` is set, should return full hash string. if not, should either return abbreviated configuration string, or fill in a stub checksum. should return native string type (ascii-bytes under python 2, unicode under python 3) """ #NOTE: documenting some non-standardized but common kwd flags # that passlib to_string() method may have # # native=True -- if false, return unicode under py2 -- ignored under py3 # withchk=True -- if false, omit checksum portion of hash # raise NotImplementedError("%s must implement from_string()" % (type(self),)) ##def to_config_string(self): ## "helper for generating configuration string (ignoring hash)" ## chk = self.checksum ## if chk: ## try: ## self.checksum = None ## return self.to_string() ## finally: ## self.checksum = chk ## else: ## return self.to_string() #========================================================= #'crypt-style' interface (default implementation) #========================================================= @classmethod def genconfig(cls, **settings): return cls(**settings).to_string() @classmethod def genhash(cls, secret, config): self = cls.from_string(config) self.checksum = self.calc_checksum(secret) return self.to_string() def calc_checksum(self, secret): #pragma: no cover "given secret; calcuate and return encoded checksum portion of hash string, taking config from object state" raise NotImplementedError("%s must implement calc_checksum()" % (self.__class__,)) #========================================================= #'application' interface (default implementation) #========================================================= @classmethod def encrypt(cls, secret, **settings): self = cls(**settings) self.checksum = self.calc_checksum(secret) return self.to_string() @classmethod def verify(cls, secret, hash): #NOTE: classes with multiple checksum encodings (rare) # may wish to either override this, or override norm_checksum # to normalize any checksums provided by from_string() self = cls.from_string(hash) return self.checksum == self.calc_checksum(secret) #========================================================= #eoc #========================================================= #===================================================== #GenericHandler mixin classes #===================================================== #XXX: add a HasContext helper to override GenericHandler's methods? class HasRawChecksum(GenericHandler): """mixin for classes which work with decoded checksum bytes .. todo:: document this class's usage """ checksum_chars = None @classmethod def norm_checksum(cls, checksum, strict=False): if checksum is None: return None if isinstance(checksum, unicode): raise TypeError("checksum must be specified as bytes") cc = cls.checksum_size if cc and len(checksum) != cc: raise ValueError("%s checksum must be %d characters" % (cls.name, cc)) return checksum #NOTE: commented out because all use-cases work better with StaticHandler ##class HasNoSettings(GenericHandler): ## """overrides some GenericHandler methods w/ versions more appropriate for hash w/no settings""" ## ## setting_kwds = () ## ## _stub_checksum = None ## ## @classmethod ## def genconfig(cls): ## if cls._stub_checksum: ## return cls().to_string() ## else: ## return None ## ## @classmethod ## def genhash(cls, secret, config): ## if config is None and not cls._stub_checksum: ## self = cls() ## else: ## self = cls.from_string(config) #just to validate the input ## self.checksum = self.calc_checksum(secret) ## return self.to_string() ## ## @classmethod ## def encrypt(cls, secret): ## self = cls() ## self.checksum = self.calc_checksum(secret) ## return self.to_string() class HasManyIdents(GenericHandler): """mixin for hashes which use multiple prefix identifiers For the hashes which may use multiple identifier prefixes, this mixin adds an ``ident`` keyword to constructor. Any value provided is passed through the :meth:`norm_idents` method, which takes care of validating the identifier, as well as allowing aliases for easier specification of the identifiers by the user. .. todo:: document this class's usage """ #========================================================= #class attrs #========================================================= default_ident = None #: should be unicode ident_values = None #: should be list of unicode strings ident_aliases = None #: should be dict of unicode -> unicode #NOTE: any aliases provided to norm_ident() as bytes # will have been converted to unicode before # comparing against this dictionary. #NOTE: relying on test_06_HasManyIdents() to verify # these are configured correctly. #========================================================= #instance attrs #========================================================= ident = None #========================================================= #init #========================================================= def __init__(self, ident=None, strict=False, **kwds): self.ident = self.norm_ident(ident, strict=strict) super(HasManyIdents, self).__init__(strict=strict, **kwds) @classmethod def norm_ident(cls, ident, strict=False): #fill in default identifier if not ident: if strict: raise ValueError("no ident specified") return cls.default_ident #handle unicode if isinstance(ident, bytes): ident = ident.decode('ascii') #check if identifier is valid iv = cls.ident_values if ident in iv: return ident #check if it's an alias ia = cls.ident_aliases if ia: try: value = ia[ident] except KeyError: pass else: if value in iv: return value #failure! raise ValueError("invalid ident: %r" % (ident,)) #========================================================= #password hash api #========================================================= @classmethod def identify(cls, hash): if not hash: return False if isinstance(hash, bytes): try: hash = hash.decode('ascii') except UnicodeDecodeError: return False return any(hash.startswith(ident) for ident in cls.ident_values) #========================================================= #eoc #========================================================= class HasSalt(GenericHandler): """mixin for validating salts. This :class:`GenericHandler` mixin adds a ``salt`` keyword to the class constuctor; any value provided is passed through the :meth:`norm_salt` method, which takes care of validating salt length and content, as well as generating new salts if one it not provided. :param salt: optional salt string :param salt_size: optional size of salt (only used if no salt provided); defaults to :attr:`default_salt_size`. :param strict: if ``True``, requires a valid salt be provided; otherwise is tolerant of correctable errors (the default). Class Attributes ================ In order for :meth:`!norm_salt` to do it's job, the following attributes must be provided by the handler subclass: .. attribute:: min_salt_size [required] The minimum number of characters allowed in a salt string. An :exc:`ValueError` will be throw if the salt is too small. .. attribute:: max_salt_size [required] The maximum number of characters allowed in a salt string. When ``strict=True`` (such as when parsing a hash), an :exc:`ValueError` will be throw if the salt is too large. WHen ``strict=False`` (such as when parsing user-provided values), the salt will be silently trimmed to this length if it's too long. .. attribute:: default_salt_size [optional] If no salt is provided, this should specify the size of the salt that will be generated by :meth:`generate_salt`. If this is not specified, it will default to :attr:`max_salt_size`. .. attribute:: salt_chars [required] A string containing all the characters which are allowed in the salt string. An :exc:`ValueError` will be throw if any other characters are encountered. May be set to ``None`` to skip this check (but see in :attr:`default_salt_chars`). .. attribute:: default_salt_chars [optional] This attribute controls the set of characters use to generate *new* salt strings. By default, it mirrors :attr:`salt_chars`. If :attr:`!salt_chars` is ``None``, this attribute must be specified in order to generate new salts. Aside from that purpose, the main use of this attribute is for hashes which wish to generate salts from a restricted subset of :attr:`!salt_chars`; such as accepting all characters, but only using a-z. Instance Attributes =================== .. attribute:: salt This instance attribute will be filled in with the salt provided to the constructor (as adapted by :meth:`norm_salt`) Class Methods ============= .. automethod:: norm_salt .. automethod:: generate_salt """ #TODO: split out "HasRawSalt" mixin for classes where salt should be provided as raw bytes. # also might need a "HasRawChecksum" to accompany it. #XXX: allow providing raw salt to this class, and encoding it? #========================================================= #class attrs #========================================================= #NOTE: min/max/default_salt_chars is deprecated, use min/max/default_salt_size instead #: required - minimum size of salt (error if too small) min_salt_size = None #: required - maximum size of salt (truncated if too large) max_salt_size = None @classproperty def default_salt_size(cls): "default salt chars (defaults to max_salt_size if not specified by subclass)" return cls.max_salt_size #: optional - set of characters allowed in salt string. salt_chars = None @classproperty def default_salt_chars(cls): "required - set of characters used to generate *new* salt strings (defaults to salt_chars)" return cls.salt_chars #: helper for HasRawSalt, shouldn't be used publically _salt_is_bytes = False _salt_unit = "char" #========================================================= #instance attrs #========================================================= salt = None #========================================================= #init #========================================================= def __init__(self, salt=None, salt_size=None, strict=False, **kwds): self.salt = self.norm_salt(salt, salt_size=salt_size, strict=strict) super(HasSalt, self).__init__(strict=strict, **kwds) @classmethod def generate_salt(cls, salt_size=None, strict=False): """helper method for norm_salt(); generates a new random salt string. :param salt_size: optional salt size, falls back to :attr:`default_salt_size`. :param strict: if too-large salt should throw error, or merely be trimmed. """ if salt_size is None: salt_size = cls.default_salt_size else: mn = cls.min_salt_size if mn and salt_size < mn: raise ValueError("%s salt string must be at least %d characters" % (cls.name, mn)) mx = cls.max_salt_size if mx and salt_size > mx: if strict: raise ValueError("%s salt string must be at most %d characters" % (cls.name, mx)) salt_size = mx if cls._salt_is_bytes: if cls.salt_chars != ALL_BYTE_VALUES: raise NotImplementedError("raw salts w/ only certain bytes not supported") return getrandbytes(rng, salt_size) else: return getrandstr(rng, cls.default_salt_chars, salt_size) @classmethod def norm_salt(cls, salt, salt_size=None, strict=False): """helper to normalize & validate user-provided salt string :arg salt: salt string or ``None`` :param strict: enable strict checking (see below); disabled by default :raises ValueError: * if ``strict=True`` and no salt is provided * if ``strict=True`` and salt contains greater than :attr:`max_salt_size` characters * if salt contains chars that aren't in :attr:`salt_chars`. * if salt contains less than :attr:`min_salt_size` characters. if no salt provided and ``strict=False``, a random salt is generated using :attr:`default_salt_size` and :attr:`default_salt_chars`. if the salt is longer than :attr:`max_salt_size` and ``strict=False``, the salt string is clipped to :attr:`max_salt_size`. :returns: normalized or generated salt """ #generate new salt if none provided if salt is None: if strict: raise ValueError("no salt specified") return cls.generate_salt(salt_size=salt_size, strict=strict) #validate input charset if cls._salt_is_bytes: if isinstance(salt, unicode): raise TypeError("salt must be specified as bytes") else: if isinstance(salt, bytes): salt = salt.decode("ascii") sc = cls.salt_chars if sc is not None: for c in salt: if c not in sc: raise ValueError("invalid character in %s salt: %r" % (cls.name, c)) #check min size mn = cls.min_salt_size if mn and len(salt) < mn: raise ValueError("%s salt string must be at least %d %ss" % (cls.name, mn, cls._salt_unit)) #check max size mx = cls.max_salt_size if mx is not None and len(salt) > mx: if strict: raise ValueError("%s salt string must be at most %d %ss" % (cls.name, mx, cls._salt_unit)) salt = salt[:mx] return salt #========================================================= #eoc #========================================================= class HasRawSalt(HasSalt): """mixin for classes which use decoded salt parameter A variant of :class:`!HasSalt` which takes in decoded bytes instead of an encoded string. .. todo:: document this class's usage """ salt_chars = ALL_BYTE_VALUES #NOTE: all HasRawSalt code is currently part of HasSalt, # using private _salt_is_bytes flag. # this arrangement may be changed in the future. _salt_is_bytes = True _salt_unit = "byte" class HasRounds(GenericHandler): """mixin for validating rounds parameter This :class:`GenericHandler` mixin adds a ``rounds`` keyword to the class constuctor; any value provided is passed through the :meth:`norm_rounds` method, which takes care of validating the number of rounds. :param rounds: optional number of rounds hash should use :param strict: if ``True``, requires a valid rounds vlaue be provided; otherwise is tolerant of correctable errors (the default). Class Attributes ================ In order for :meth:`!norm_rounds` to do it's job, the following attributes must be provided by the handler subclass: .. attribute:: min_rounds [optional] The minimum number of rounds allowed. An :exc:`ValueError` will be thrown if the rounds value is too small. When ``strict=True`` (such as when parsing a hash), an :exc:`ValueError` will be throw if the rounds value is too small. WHen ``strict=False`` (such as when parsing user-provided values), the rounds value will be silently clipped if it's too small. Defaults to ``0``. .. attribute:: max_rounds [required] The maximum number of rounds allowed. When ``strict=True`` (such as when parsing a hash), an :exc:`ValueError` will be throw if the rounds value is too large. WHen ``strict=False`` (such as when parsing user-provided values), the rounds value will be silently clipped if it's too large. .. attribute:: default_rounds [required] If no rounds value is provided to constructor, this value will be used. .. attribute:: rounds_cost [required] The ``rounds`` parameter typically encodes a cpu-time cost for calculating a hash. This should be set to ``"linear"`` (the default) or ``"log2"``, depending on how the rounds value relates to the actual amount of time that will be required. .. attribute:: _strict_rounds_bounds [optional] If the handler subclass wishes to *always* throw an error if a rounds value is provided that's out of bounds (such as when it's provided by the user), set this private attribute to ``True``. The default policy in such cases is to silently clip the rounds value to within :attr:`min_rounds` and :attr:`max_rounds`; while issuing a :exc:`UserWarning`. Instance Attributes =================== .. attribute:: rounds This instance attribute will be filled in with the rounds value provided to the constructor (as adapted by :meth:`norm_rounds`) Class Methods ============= .. automethod:: norm_rounds """ #========================================================= #class attrs #========================================================= min_rounds = 0 max_rounds = None #required by ExtendedHandler.norm_rounds() default_rounds = None #if not specified, ExtendedHandler.norm_rounds() will require explicit rounds value every time rounds_cost = "linear" #common case _strict_rounds_bounds = False #if true, always raises error if specified rounds values out of range - required by spec for some hashes #========================================================= #instance attrs #========================================================= rounds = None #========================================================= #init #========================================================= def __init__(self, rounds=None, strict=False, **kwds): self.rounds = self.norm_rounds(rounds, strict=strict) super(HasRounds, self).__init__(strict=strict, **kwds) @classmethod def norm_rounds(cls, rounds, strict=False): """helper routine for normalizing rounds :arg rounds: rounds integer or ``None`` :param strict: enable strict checking (see below); disabled by default :raises ValueError: * if rounds is ``None`` and ``strict=True`` * if rounds is ``None`` and no :attr:`default_rounds` are specified by class. * if rounds is outside bounds of :attr:`min_rounds` and :attr:`max_rounds`, and ``strict=True``. if rounds are not specified and ``strict=False``, uses :attr:`default_rounds`. if rounds are outside bounds and ``strict=False``, rounds are clipped as appropriate, but a warning is issued. :returns: normalized rounds value """ #provide default if rounds not explicitly set if rounds is None: if strict: raise ValueError("no rounds specified") rounds = cls.default_rounds if rounds is None: raise ValueError("%s rounds value must be specified explicitly" % (cls.name,)) #if class requests, always throw error instead of clipping if cls._strict_rounds_bounds: strict = True mn = cls.min_rounds if rounds < mn: if strict: raise ValueError("%s rounds must be >= %d" % (cls.name, mn)) warn("%s does not allow less than %d rounds: %d" % (cls.name, mn, rounds)) rounds = mn mx = cls.max_rounds if mx and rounds > mx: if strict: raise ValueError("%s rounds must be <= %d" % (cls.name, mx)) warn("%s does not allow more than %d rounds: %d" % (cls.name, mx, rounds)) rounds = mx return rounds #========================================================= #eoc #========================================================= class HasManyBackends(GenericHandler): """GenericHandler mixin which provides selecting from multiple backends. .. todo:: finish documenting this class's usage For hashes which need to select from multiple backends, depending on the host environment, this class offers a way to specify alternate :meth:`calc_checksum` methods, and will dynamically chose the best one at runtime. Backend Methods --------------- .. automethod:: get_backend .. automethod:: set_backend .. automethod:: has_backend Subclass Hooks -------------- The following attributes and methods should be filled in by the subclass which is using :class:`HasManyBackends` as a mixin: .. attribute:: backends This attribute should be a tuple containing the names of the backends which are supported. Two common names are ``"os_crypt"`` (if backend uses :mod:`crypt`), and ``"builtin"`` (if the backend is a pure-python fallback). .. attribute:: _has_backend_{name} private class attribute checked by :meth:`has_backend` to see if a specific backend is available, it should be either ``True`` or ``False``. One of these should be provided by the subclass for each backend listed in :attr:`backends`. .. classmethod:: _calc_checksum_{name} private class method that should implement :meth:`calc_checksum` for a given backend. it will only be called if the backend has been selected by :meth:`set_backend`. One of these should be provided by the subclass for each backend listed in :attr:`backends`. """ #NOTE: subclass must provide: # * attr 'backends' containing list of known backends (top priority backend first) # * attr '_has_backend_xxx' for each backend 'xxx', indicating if backend is available on system # * attr '_calc_checksum_xxx' for each backend 'xxx', containing calc_checksum implementation using that backend backends = None #: list of backend names, provided by subclass. _backend = None #: holds currently loaded backend (if any) or None @classmethod def get_backend(cls): """return name of currently active backend. if no backend has been loaded, loads and returns name of default backend. :raises MissingBackendError: if no backends are available. :returns: name of active backend """ name = cls._backend if not name: cls.set_backend() name = cls._backend assert name, "set_backend() didn't load any backends" return name @classmethod def has_backend(cls, name="any"): """check if support is currently available for specified backend. :arg name: name of backend to check for. defaults to ``"any"``, but can be any string accepted by :meth:`set_backend`. :raises ValueError: if backend name is unknown :returns: ``True`` if backend is currently supported, else ``False``. """ if name in (None, "any", "default"): if name is None: warn("has_backend(None) is deprecated," " and support will be removed in Passlib 1.6;" " use has_backend('any') instead.", DeprecationWarning, stacklevel=2) try: cls.set_backend() return True except MissingBackendError: return False elif name in cls.backends: return getattr(cls, "_has_backend_" + name) else: raise ValueError("unknown backend: %r" % (name,)) @classmethod def _no_backends_msg(cls): return "no %s backends available" % (cls.name,) @classmethod def set_backend(cls, name="any"): """load specified backend to be used for future calc_checksum() calls this method replaces :meth:`calc_checksum` with a method which uses the specified backend. :arg name: name of backend to load, defaults to ``"any"``. this can be any of the following values: * any string in :attr:`backends`, indicating the specific backend to use. * the special string ``"default"``, which means to use the preferred backend on the given host (this is generally the first backend in :attr:`backends` which can be loaded). * the special string ``"any"``, which means to use the current backend if one has been loaded, else acts like ``"default"``. :raises MissingBackendError: * if a specific backend was specified, but is not currently available. * if ``"any"`` or ``"default"`` was specified, and NO backends are currently available. return value should be ignored. .. note:: :exc:`~passlib.utils.MissingBackendError` derives from :exc:`RuntimeError`, since this usually indicates lack of an external library or OS feature. """ if name is None: warn("set_backend(None) is deprecated," " and support will be removed in Passlib 1.6;" " use set_backend('any') instead.", DeprecationWarning, stacklevel=2) name = "any" if name == "any": name = cls._backend if name: return name name = "default" if name == "default": for name in cls.backends: if cls.has_backend(name): break else: raise MissingBackendError(cls._no_backends_msg()) elif not cls.has_backend(name): raise MissingBackendError("%s backend not available: %r" % (cls.name, name)) cls.calc_checksum = getattr(cls, "_calc_checksum_" + name) cls._backend = name return name def calc_checksum(self, secret): "stub for calc_checksum(), default backend will be selected first time stub is called" #backend not loaded - run detection and call replacement assert not self._backend, "set_backend() failed to replace lazy loader" self.set_backend() assert self._backend, "set_backend() failed to load a default backend" #set_backend() should have replaced this method, so call it again. return self.calc_checksum(secret) #========================================================= #wrappers #========================================================= class PrefixWrapper(object): """wraps another handler, adding a constant prefix. instances of this class wrap another password hash handler, altering the constant prefix that's prepended to the wrapped handlers' hashes. this is used mainly by the :doc:`ldap crypt ` handlers; such as :class:`~passlib.hash.ldap_md5_crypt` which wraps :class:`~passlib.hash.md5_crypt` and adds a ``{CRYPT}`` prefix. usage:: myhandler = PrefixWrapper("myhandler", "md5_crypt", prefix="$mh$", orig_prefix="$1$") :param name: name to assign to handler :param wrapped: handler object or name of registered handler :param prefix: identifying prefix to prepend to all hashes :param orig_prefix: prefix to strip (defaults to ''). :param lazy: if True and wrapped handler is specified by name, don't look it up until needed. """ def __init__(self, name, wrapped, prefix=u'', orig_prefix=u'', lazy=False, doc=None): self.name = name if isinstance(prefix, bytes): prefix = prefix.decode("ascii") self.prefix = prefix if isinstance(orig_prefix, bytes): orig_prefix = orig_prefix.decode("ascii") self.orig_prefix = orig_prefix if doc: self.__doc__ = doc if hasattr(wrapped, "name"): self._check_handler(wrapped) self._wrapped_handler = wrapped else: self._wrapped_name = wrapped if not lazy: self._get_wrapped() _wrapped_name = None _wrapped_handler = None def _check_handler(self, handler): if 'ident' in handler.setting_kwds and self.orig_prefix: #TODO: look into way to fix the issues. warn("PrefixWrapper: 'orig_prefix' option may not work correctly for handlers which have multiple identifiers: %r" % (handler.name,)) def _get_wrapped(self): handler = self._wrapped_handler if handler is None: handler = get_crypt_handler(self._wrapped_name) self._check_handler(handler) self._wrapped_handler = handler return handler wrapped = property(_get_wrapped) ##@property ##def ident(self): ## return self._prefix #attrs that should be proxied _proxy_attrs = ( "setting_kwds", "context_kwds", "default_rounds", "min_rounds", "max_rounds", "rounds_cost", "backends", "has_backend", "get_backend", "set_backend", ) def __repr__(self): args = [ repr(self._wrapped_name or self._wrapped_handler) ] if self.prefix: args.append("prefix=%r" % self.prefix) if self.orig_prefix: args.append("orig_prefix=%r", self.orig_prefix) args = ", ".join(args) return 'PrefixWrapper(%r, %s)' % (self.name, args) def __getattr__(self, attr): "proxy most attributes from wrapped class (eg rounds, salt size, etc)" if attr in self._proxy_attrs: return getattr(self.wrapped, attr) raise AttributeError("missing attribute: %r" % (attr,)) def _unwrap_hash(self, hash): "given hash belonging to wrapper, return orig version" if isinstance(hash, bytes): hash = hash.decode('ascii') prefix = self.prefix if not hash.startswith(prefix): raise ValueError("not a valid %s hash" % (self.name,)) #NOTE: always passing to handler as unicode, to save reconversion return self.orig_prefix + hash[len(prefix):] def _wrap_hash(self, hash): "given orig hash; return one belonging to wrapper" #NOTE: should usually be native string. # (which does mean extra work under py2, but not py3) if isinstance(hash, bytes): hash = hash.decode('ascii') orig_prefix = self.orig_prefix if not hash.startswith(orig_prefix): raise ValueError("not a valid %s hash" % (self.wrapped.name,)) wrapped = self.prefix + hash[len(orig_prefix):] return to_hash_str(wrapped) def identify(self, hash): if not hash: return False if isinstance(hash, bytes): hash = hash.decode('ascii') if not hash.startswith(self.prefix): return False hash = self._unwrap_hash(hash) return self.wrapped.identify(hash) def genconfig(self, **kwds): config = self.wrapped.genconfig(**kwds) if config: return self._wrap_hash(config) else: return config def genhash(self, secret, config, **kwds): if config: config = self._unwrap_hash(config) return self._wrap_hash(self.wrapped.genhash(secret, config, **kwds)) def encrypt(self, secret, **kwds): return self._wrap_hash(self.wrapped.encrypt(secret, **kwds)) def verify(self, secret, hash, **kwds): if not hash: raise ValueError("no %s hash specified" % (self.name,)) hash = self._unwrap_hash(hash) return self.wrapped.verify(secret, hash, **kwds) #========================================================= # eof #========================================================= passlib-1.5.3/passlib/utils/__init__.py0000644000175000017500000006307711643754032021233 0ustar biscuitbiscuit00000000000000"""passlib utility functions""" #================================================================================= #imports #================================================================================= #core from base64 import b64encode, b64decode from codecs import lookup as _lookup_codec from cStringIO import StringIO ##from functools import update_wrapper from hashlib import sha256 import logging; log = logging.getLogger(__name__) from math import log as logb import os import sys import random import time from warnings import warn #site #pkg #local __all__ = [ #decorators "classproperty", ## "memoized_class_property", ## "abstractmethod", ## "abstractclassmethod", #byte compat aliases 'bytes', 'native_str', #misc 'os_crypt', #tests 'is_crypt_handler', 'is_crypt_context', #bytes<->unicode 'to_bytes', 'to_unicode', 'to_native_str', 'is_same_codec', #byte manipulation "xor_bytes", #random 'rng', 'getrandbytes', 'getrandstr', #constants 'pypy_vm', 'jython_vm', 'py32_lang', 'py3k_lang', 'sys_bits', 'unix_crypt_schemes', ] #================================================================================= #constants #================================================================================= #: detect what we're running on pypy_vm = hasattr(sys, "pypy_version_info") jython_vm = sys.platform.startswith('java') py3k_lang = sys.version_info >= (3,0) py32_lang = sys.version_info >= (3,2) #: number of bits in system architecture sys_bits = int(logb(sys.maxint,2)+1.5) assert sys_bits in (32,64), "unexpected sys_bits value: %r" % (sys_bits,) #: list of names of hashes found in unix crypt implementations... unix_crypt_schemes = [ "sha512_crypt", "sha256_crypt", "sha1_crypt", "bcrypt", "md5_crypt", "bsdi_crypt", "des_crypt" ] #: list of rounds_cost constants rounds_cost_values = [ "linear", "log2" ] #: special byte string containing all possible byte values, used in a few places. #XXX: treated as singleton by some of the code for efficiency. # Py2k # ALL_BYTE_VALUES = ''.join(chr(x) for x in xrange(256)) # Py3k # #ALL_BYTE_VALUES = bytes(xrange(256)) # end Py3k # #NOTE: Undef is only used in *one* place now, could just remove it class UndefType(object): _undef = None def __new__(cls): if cls._undef is None: cls._undef = object.__new__(cls) return cls._undef def __repr__(self): return '' def __eq__(self, other): return False def __ne__(self, other): return True #: singleton used as default kwd value in some functions, indicating "NO VALUE" Undef = UndefType() NoneType = type(None) class MissingBackendError(RuntimeError): """error raised if multi-backend handler has no available backends; or if specifically requested backend is not available. see :class:`~passlib.utils.handlers.HasManyBackends`. """ #========================================================== #bytes compat aliases - bytes, native_str, b() #========================================================== # Py2k # if sys.version_info < (2,6): #py25 doesn't define 'bytes', so we have to here - #and then import it everywhere bytes is needed, #just so we retain py25 compat - if that were sacrificed, #the need for this would go away bytes = str else: bytes = bytes #just so it *can* be imported from this module native_str = bytes # Py3k # #bytes = bytes #just so it *can* be imported from this module #native_str = unicode # end Py3k # #NOTE: have to provide b() because we're supporting py25, # and py25 doesn't support the b'' notation. # if py25 compat were sacrificed, this func could be removed. def b(source): "convert native str to bytes (noop under py2; uses latin-1 under py3)" #assert isinstance(source, native_str) # Py2k # return source # Py3k # #return source.encode("latin-1") # end Py3k # #================================================================================= #os crypt helpers #================================================================================= #expose crypt function as 'os_crypt', set to None if not available. try: from crypt import crypt as os_crypt except ImportError: #pragma: no cover safe_os_crypt = os_crypt = None else: def safe_os_crypt(secret, hash): """wrapper around stdlib's crypt. Python 3's crypt behaves slightly differently from Python 2's crypt. for one, it takes in and returns unicode. internally, it converts to utf-8 before hashing. Annoyingly, *there is no way to call it using bytes*. thus, it can't be used to hash non-ascii passwords using any encoding but utf-8 (eg, using latin-1). This wrapper attempts to gloss over all those issues: Under Python 2, it accept passwords as unicode or bytes, accepts hashes only as unicode, and always returns unicode. Under Python 3, it will signal that it cannot hash a password if provided as non-utf-8 bytes, but otherwise behave the same as crypt. :arg secret: password as bytes or unicode :arg hash: hash/salt as unicode :returns: ``(False, None)`` if the password can't be hashed (3.x only), or ``(True, result: unicode)`` otherwise. """ #XXX: source indicates crypt() may return None on some systems # if an error occurrs - could make this return False in that case. # Py2k # #NOTE: this guard logic is designed purely to match py3 behavior, # with the exception that it accepts secret as bytes if isinstance(secret, unicode): secret = secret.encode("utf-8") if isinstance(hash, bytes): raise TypeError("hash must be unicode") else: hash = hash.encode("utf-8") return True, os_crypt(secret, hash).decode("ascii") # Py3k # #if isinstance(secret, bytes): # #decode to utf-8. if successful, will be reencoded with os_crypt, # #and we'll get back correct hash. # #if not, we can't use os_crypt for this. # orig = secret # try: # secret = secret.decode("utf-8") # except UnicodeDecodeError: # return False, None # if secret.encode("utf-8") != orig: # #just in case original encoding wouldn't be reproduced # #during call to os_crypt. # #not sure if/how this could happen, but being paranoid. # warn("utf-8 password didn't re-encode correctly") # return False, None #return True, os_crypt(secret, hash) # end Py3k # #================================================================================= #decorators and meta helpers #================================================================================= class classproperty(object): """Function decorator which acts like a combination of classmethod+property (limited to read-only properties)""" def __init__(self, func): self.im_func = func def __get__(self, obj, cls): return self.im_func(cls) #works but not used ##class memoized_class_property(object): ## """function decorator which calls function as classmethod, and replaces itself with result for current and all future invocations""" ## def __init__(self, func): ## self.im_func = func ## ## def __get__(self, obj, cls): ## func = self.im_func ## value = func(cls) ## setattr(cls, func.__name__, value) ## return value #works but not used... ##def abstractmethod(func): ## """Method decorator which indicates this is a placeholder method which ## should be overridden by subclass. ## ## If called directly, this method will raise an :exc:`NotImplementedError`. ## """ ## msg = "object %(self)r method %(name)r is abstract, and must be subclassed" ## def wrapper(self, *args, **kwds): ## text = msg % dict(self=self, name=wrapper.__name__) ## raise NotImplementedError(text) ## update_wrapper(wrapper, func) ## return wrapper #works but not used... ##def abstractclassmethod(func): ## """Class Method decorator which indicates this is a placeholder method which ## should be overridden by subclass, and must be a classmethod. ## ## If called directly, this method will raise an :exc:`NotImplementedError`. ## """ ## msg = "class %(cls)r method %(name)r is abstract, and must be subclassed" ## def wrapper(cls, *args, **kwds): ## text = msg % dict(cls=cls, name=wrapper.__name__) ## raise NotImplementedError(text) ## update_wrapper(wrapper, func) ## return classmethod(wrapper) #========================================================== #protocol helpers #========================================================== def is_crypt_handler(obj): "check if object follows the :ref:`password-hash-api`" return all(hasattr(obj, name) for name in ( "name", "setting_kwds", "context_kwds", "genconfig", "genhash", "verify", "encrypt", "identify", )) def is_crypt_context(obj): "check if object appears to be a :class:`~passlib.context.CryptContext` instance" return all(hasattr(obj, name) for name in ( "hash_needs_update", "genconfig", "genhash", "verify", "encrypt", "identify", )) ##def has_many_backends(handler): ## "check if handler provides multiple baceknds" ## #NOTE: should also provide get_backend(), .has_backend(), and .backends attr ## return hasattr(handler, "set_backend") def has_rounds_info(handler): "check if handler provides the optional :ref:`rounds information ` attributes" return 'rounds' in handler.setting_kwds and getattr(handler, "min_rounds", None) is not None def has_salt_info(handler): "check if handler provides the optional :ref:`salt information ` attributes" return 'salt' in handler.setting_kwds and getattr(handler, "min_salt_size", None) is not None ##def has_raw_salt(handler): ## "check if handler takes in encoded salt as unicode (False), or decoded salt as bytes (True)" ## sc = getattr(handler, "salt_chars", None) ## if sc is None: ## return None ## elif isinstance(sc, unicode): ## return False ## elif isinstance(sc, bytes): ## return True ## else: ## raise TypeError("handler.salt_chars must be None/unicode/bytes") #========================================================== #bytes <-> unicode conversion helpers #========================================================== def to_bytes(source, encoding="utf-8", source_encoding=None, errname="value"): """helper to encoding unicode -> bytes this function takes in a ``source`` string. if unicode, encodes it using the specified ``encoding``. if bytes, returns unchanged - unless ``source_encoding`` is specified, in which case the bytes are transcoded if and only if the source encoding doesn't match the desired encoding. all other types result in a :exc:`TypeError`. :arg source: source bytes/unicode to process :arg encoding: target character encoding or ``None``. :param source_encoding: optional source encoding :param errname: optional name of variable/noun to reference when raising errors :raises TypeError: if unicode encountered but ``encoding=None`` specified; or if source is not unicode or bytes. :returns: bytes object .. note:: if ``encoding`` is set to ``None``, then unicode strings will be rejected, and only byte strings will be allowed through. """ if isinstance(source, bytes): if source_encoding and encoding and \ not is_same_codec(source_encoding, encoding): return source.decode(source_encoding).encode(encoding) else: return source elif not encoding: raise TypeError("%s must be bytes, not %s" % (errname, type(source))) elif isinstance(source, unicode): return source.encode(encoding) elif source_encoding: raise TypeError("%s must be unicode or %s-encoded bytes, not %s" % (errname, source_encoding, type(source))) else: raise TypeError("%s must be unicode or bytes, not %s" % (errname, type(source))) def to_unicode(source, source_encoding="utf-8", errname="value"): """take in unicode or bytes, return unicode if bytes provided, decodes using specified encoding. leaves unicode alone. :raises TypeError: if source is not unicode or bytes. :arg source: source bytes/unicode to process :arg source_encoding: encoding to use when decoding bytes instances :param errname: optional name of variable/noun to reference when raising errors :returns: unicode object """ if isinstance(source, unicode): return source elif not source_encoding: raise TypeError("%s must be unicode, not %s" % (errname, type(source))) elif isinstance(source, bytes): return source.decode(source_encoding) else: raise TypeError("%s must be unicode or %s-encoded bytes, not %s" % (errname, source_encoding, type(source))) def to_native_str(source, encoding="utf-8", errname="value"): """take in unicode or bytes, return native string python 2: encodes unicode using specified encoding, leaves bytes alone. python 3: decodes bytes using specified encoding, leaves unicode alone. :raises TypeError: if source is not unicode or bytes. :arg source: source bytes/unicode to process :arg encoding: encoding to use when encoding unicode / decoding bytes :param errname: optional name of variable/noun to reference when raising errors :returns: :class:`str` instance """ assert encoding if isinstance(source, bytes): # Py2k # return source # Py3k # #return source.decode(encoding) # end Py3k # elif isinstance(source, unicode): # Py2k # return source.encode(encoding) # Py3k # #return source # end Py3k # else: raise TypeError("%s must be unicode or bytes, not %s" % (errname, type(source))) def to_hash_str(hash, encoding="ascii", errname="hash"): "given hash string as bytes or unicode; normalize according to hash policy" #NOTE: for now, policy is ascii-bytes under py2, unicode under py3. # but plan to make flag allowing apps to enable unicode behavior under py2. return to_native_str(hash, encoding, errname) #-------------------------------------------------- #support utils #-------------------------------------------------- def is_same_codec(left, right): "check if two codecs names are aliases for same codec" if left == right: return True if not (left and right): return False return _lookup_codec(left).name == _lookup_codec(right).name _U80 = u'\x80' _B80 = b('\x80') def is_ascii_safe(source): "check if source (bytes or unicode) contains only 7-bit ascii" if isinstance(source, bytes): # Py2k # return all(c < _B80 for c in source) # Py3k # #return all(c < 128 for c in source) # end Py3k # else: return all(c < _U80 for c in source) #================================================================================= #string helpers #================================================================================= def splitcomma(source, sep=","): "split comma-separated string into list of elements, stripping whitespace and discarding empty elements" return [ elem.strip() for elem in source.split(sep) if elem.strip() ] #========================================================== #bytes helpers #========================================================== #some common constants / aliases BEMPTY = b('') #helpers for joining / extracting elements bjoin = BEMPTY.join ujoin = u''.join def belem_join(elems): """takes series of bytes elements, returns bytes. elem should be result of bytes[x]. this is another bytes instance under py2, but it int under py3. returns bytes. this is bytes() constructor under py3, but b"".join() under py2. """ # Py2k # return bjoin(elems) # Py3k # #return bytes(elems) # end Py3k # #for efficiency, don't bother with above wrapper... # Py2k # belem_join = bjoin # Py3k # #belem_join = bytes # end Py3k # def bord(elem): """takes bytes element, returns integer. elem should be result of bytes[x]. this is another bytes instance under py2, but it int under py3. returns int in range(0,256). this is ord() under py2, and noop under py3. """ # Py2k # assert isinstance(elem, bytes) return ord(elem) # Py3k # ##assert isinstance(elem, int) #return elem # end Py3k # #for efficiency, don't bother with wrapper # Py2k # bord = ord # end Py2k # def bchrs(*values): "takes series of ints, returns bytes; like chr() but for bytes, and w/ multi args" # Py2k # return bjoin(chr(v) for v in values) # Py3k # #return bytes(values) # end Py3k # # Py2k # def bjoin_ints(values): return bjoin(chr(v) for v in values) # Py3k # #bjoin_ints = bytes # end Py3k # def render_bytes(source, *args): """helper for using formatting operator with bytes. this function is motivated by the fact that :class:`bytes` instances do not support % or {} formatting under python 3. this function is an attempt to provide a replacement that will work uniformly under python 2 & 3. it converts everything to unicode (including bytes arguments), then encodes the result to latin-1. """ if isinstance(source, bytes): source = source.decode("latin-1") def adapt(arg): if isinstance(arg, bytes): return arg.decode("latin-1") return arg result = source % tuple(adapt(arg) for arg in args) return result.encode("latin-1") #================================================================================= #numeric helpers #================================================================================= ##def int_to_bytes(value, count=None, order="big"): ## """encode a integer into a string of bytes ## ## :arg value: the integer ## :arg count: optional number of bytes to expose, uses minimum needed if count not specified ## :param order: the byte ordering; "big" (the default), "little", or "native" ## ## :raises ValueError: ## * if count specified and integer too large to fit. ## * if integer is negative ## ## :returns: ## bytes encoding integer ## """ ## ## ##def bytes_to_int(value, order="big"): ## """decode a byte string into an integer representation of it's binary value. ## ## :arg value: the string to decode. ## :param order: the byte ordering; "big" (the default), "little", or "native" ## ## :returns: the decoded positive integer. ## """ ## if not value: ## return 0 ## if order == "native": ## order = sys.byteorder ## if order == "little": ## value = reversed(value) ## out = 0 ## for v in value: ## out = (out<<8) | ord(v) ## return out def bytes_to_int(value): "decode string of bytes as single big-endian integer" out = 0 for v in value: out = (out<<8) | bord(v) return out def int_to_bytes(value, count): "encodes integer into single big-endian byte string" assert value < (1<<(8*count)), "value too large for %d bytes: %d" % (count, value) return bjoin_ints( ((value>>s) & 0xff) for s in xrange(8*count-8,-8,-8) ) def xor_bytes(left, right): "perform bitwise-xor of two byte-strings" #NOTE: this could use bjoin_ints(), but speed is *really* important here (c.f. PBKDF2) # Py2k # return bjoin(chr(ord(l) ^ ord(r)) for l, r in zip(left, right)) # Py3k # #return bytes(l ^ r for l, r in zip(left, right)) # end Py3k # #================================================================================= #alt base64 encoding #================================================================================= _A64_ALTCHARS = b("./") _A64_STRIP = b("=\n") _A64_PAD1 = b("=") _A64_PAD2 = b("==") def adapted_b64_encode(data): """encode using variant of base64 the output of this function is identical to b64_encode, except that it uses ``.`` instead of ``+``, and omits trailing padding ``=`` and whitepsace. it is primarily used for by passlib's custom pbkdf2 hashes. """ return b64encode(data, _A64_ALTCHARS).strip(_A64_STRIP) def adapted_b64_decode(data, sixthree="."): """decode using variant of base64 the input of this function is identical to b64_decode, except that it uses ``.`` instead of ``+``, and should not include trailing padding ``=`` or whitespace. it is primarily used for by passlib's custom pbkdf2 hashes. """ off = len(data) % 4 if off == 0: return b64decode(data, _A64_ALTCHARS) elif off == 1: raise ValueError("invalid bas64 input") elif off == 2: return b64decode(data + _A64_PAD2, _A64_ALTCHARS) else: return b64decode(data + _A64_PAD1, _A64_ALTCHARS) #================================================================================= #randomness #================================================================================= #----------------------------------------------------------------------- # setup rng for generating salts #----------------------------------------------------------------------- #NOTE: # generating salts (eg h64_gensalt, below) doesn't require cryptographically # strong randomness. it just requires enough range of possible outputs # that making a rainbow table is too costly. # so python's builtin merseen twister prng is used, but seeded each time # this module is imported, using a couple of minor entropy sources. try: os.urandom(1) has_urandom = True except NotImplementedError: #pragma: no cover has_urandom = False def genseed(value=None): "generate prng seed value from system resources" #if value is rng, extract a bunch of bits from it's state if hasattr(value, "getrandbits"): value = value.getrandbits(256) text = u"%s %s %s %.15f %s" % ( value, #if user specified a seed value (eg current rng state), mix it in os.getpid() if hasattr(os, "getpid") else None, #add current process id #NOTE: not available in some environments, eg GAE id(object()), #id of a freshly created object. #(at least 2 bytes of which should be hard to predict) time.time(), #the current time, to whatever precision os uses os.urandom(16).decode("latin-1") if has_urandom else 0, #if urandom available, might as well mix some bytes in. ) #hash it all up and return it as int return long(sha256(text.encode("utf-8")).hexdigest(), 16) if has_urandom: rng = random.SystemRandom() else: #pragma: no cover #NOTE: to reseed - rng.seed(genseed(rng)) rng = random.Random(genseed()) #----------------------------------------------------------------------- # some rng helpers #----------------------------------------------------------------------- def getrandbytes(rng, count): """return byte-string containing *count* number of randomly generated bytes, using specified rng""" #NOTE: would be nice if this was present in stdlib Random class ###just in case rng provides this... ##meth = getattr(rng, "getrandbytes", None) ##if meth: ## return meth(count) if not count: return BEMPTY def helper(): #XXX: break into chunks for large number of bits? value = rng.getrandbits(count<<3) i = 0 while i < count: # Py2k # yield chr(value & 0xff) # Py3k # #yield value & 0xff # end Py3k # value >>= 3 i += 1 # Py2k # return bjoin(helper()) # Py3k # #return bytes(helper()) # end Py3k # def getrandstr(rng, charset, count): """return string containing *count* number of chars/bytes, whose elements are drawn from specified charset, using specified rng""" #check alphabet & count if count < 0: raise ValueError("count must be >= 0") letters = len(charset) if letters == 0: raise ValueError("alphabet must not be empty") if letters == 1: return charset * count #get random value, and write out to buffer def helper(): #XXX: break into chunks for large number of letters? value = rng.randrange(0, letters**count) i = 0 while i < count: yield charset[value % letters] value //= letters i += 1 if isinstance(charset, unicode): return ujoin(helper()) else: # Py2k # return bjoin(helper()) # Py3k # #return bytes(helper()) # end Py3k # def generate_password(size=10, charset='2346789ABCDEFGHJKMNPQRTUVWXYZabcdefghjkmnpqrstuvwxyz'): """generate random password using given length & chars :param size: size of password. :param charset: optional string specified set of characters to draw from. the default charset contains all normal alphanumeric characters, except for the characters ``1IiLl0OoS5``, which were omitted due to their visual similarity. :returns: randomly generated password. """ return getrandstr(rng, charset, size) #================================================================================= #eof #================================================================================= passlib-1.5.3/passlib/utils/h64.py0000644000175000017500000002456211643753301020067 0ustar biscuitbiscuit00000000000000"""passlib.utils.h64 - hash64 encoding helpers""" #================================================================================= #imports #================================================================================= #core import logging; log = logging.getLogger(__name__) #site #pkg from passlib.utils import bytes, bjoin, bchrs, bord, belem_join #local __all__ = [ "CHARS", "decode_bytes", "encode_bytes", "decode_transposed_bytes", "encode_transposed_bytes", "decode_int6", "encode_int6", "decode_int12", "encode_int12" "decode_int18", "encode_int18" "decode_int24", "encode_int24", "decode_int64", "encode_int64", "decode_int", "encode_int", ] #================================================================================= #6 bit value <-> char mapping, and other internal helpers #================================================================================= #: hash64 char sequence CHARS = u"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" BCHARS = CHARS.encode("ascii") #: encode int -> hash64 char as efficiently as possible, w/ minimal checking # Py2k # _encode_6bit = BCHARS.__getitem__ # Py3k # #_encode_6bit = lambda v: BCHARS[v:v+1] # end Py3k # #: decode hash64 char -> int as efficiently as possible, w/ minimal checking _CHARIDX = dict((_encode_6bit(i),i) for i in xrange(64)) _decode_6bit = _CHARIDX.__getitem__ # char -> int #for py3, enhance _CHARIDX to also support int value of bytes # Py3k # #_CHARIDX.update((v,i) for i,v in enumerate(BCHARS)) # end Py3k # #================================================================================= #encode offsets from buffer - used by md5_crypt, sha_crypt, et al #================================================================================= def _encode_bytes_helper(source): #FIXME: do something much more efficient here. # can't quite just use base64 and then translate chars, # since this scheme is little-endian. end = len(source) tail = end % 3 end -= tail idx = 0 while idx < end: v1 = bord(source[idx]) v2 = bord(source[idx+1]) v3 = bord(source[idx+2]) yield encode_int24(v1 + (v2<<8) + (v3<<16)) idx += 3 if tail: v1 = bord(source[idx]) if tail == 1: #NOTE: 4 msb of int are always 0 yield encode_int12(v1) else: #NOTE: 2 msb of int are always 0 v2 = bord(source[idx+1]) yield encode_int18(v1 + (v2<<8)) def encode_bytes(source): "encode byte string to h64 format" if not isinstance(source, bytes): raise TypeError("source must be bytes, not %s" % (type(source),)) return bjoin(_encode_bytes_helper(source)) def _decode_bytes_helper(source): end = len(source) tail = end % 4 if tail == 1: #only 6 bits left, can't encode a whole byte! raise ValueError("input string length cannot be == 1 mod 4") end -= tail idx = 0 while idx < end: v = decode_int24(source[idx:idx+4]) yield bchrs(v&0xff, (v>>8)&0xff, v>>16) idx += 4 if tail: if tail == 2: #NOTE: 2 msb of int are ignored (should be 0) v = decode_int12(source[idx:idx+2]) yield bchrs(v&0xff) else: #NOTE: 4 msb of int are ignored (should be 0) v = decode_int18(source[idx:idx+3]) yield bchrs(v&0xff, (v>>8)&0xff) def decode_bytes(source): "decode h64 format into byte string" if not isinstance(source, bytes): raise TypeError("source must be bytes, not %s" % (type(source),)) return bjoin(_decode_bytes_helper(source)) def encode_transposed_bytes(source, offsets): "encode byte string to h64 format, using offset list to transpose elements" if not isinstance(source, bytes): raise TypeError("source must be bytes, not %s" % (type(source),)) #XXX: could make this a dup of encode_bytes(), which directly accesses source[offsets[idx]], # but speed isn't *that* critical for this function tmp = belem_join(source[off] for off in offsets) return encode_bytes(tmp) def decode_transposed_bytes(source, offsets): "decode h64 format into byte string, then undoing specified transposition; inverse of :func:`encode_transposed_bytes`" #NOTE: if transposition does not use all bytes of source, original can't be recovered tmp = decode_bytes(source) buf = [None] * len(offsets) for off, char in zip(offsets, tmp): buf[off] = char return belem_join(buf) #================================================================================= # int <-> b64 string, used by des_crypt, bsdi_crypt #================================================================================= def decode_int6(source): "decodes single hash64 character -> 6-bit integer" if not isinstance(source, bytes): raise TypeError("source must be bytes, not %s" % (type(source),)) try: return _decode_6bit(source) except KeyError: raise ValueError("invalid character") def encode_int6(value): "encodes 6-bit integer -> single hash64 character" if value < 0 or value > 63: raise ValueError("value out of range") return _encode_6bit(value) #--------------------------------------------------------------------- def decode_int12(source): "decodes 2 char hash64 string -> 12-bit integer (little-endian order)" #NOTE: this is optimized form of decode_int(value) for 4 chars if not isinstance(source, bytes): raise TypeError("source must be bytes, not %s" % (type(source),)) try: return (_decode_6bit(source[1])<<6)+_decode_6bit(source[0]) except KeyError: raise ValueError("invalid character") def encode_int12(value): "encodes 12-bit integer -> 2 char hash64 string (little-endian order)" #NOTE: this is optimized form of encode_int(value,2) return _encode_6bit(value & 0x3f) + _encode_6bit((value>>6) & 0x3f) #--------------------------------------------------------------------- def decode_int18(source): "decodes 3 char hash64 string -> 18-bit integer (little-endian order)" #NOTE: this is optimized form of decode_int(value) for 3 chars if not isinstance(source, bytes): raise TypeError("source must be bytes, not %s" % (type(source),)) return ( _decode_6bit(source[0]) + (_decode_6bit(source[1])<<6) + (_decode_6bit(source[2])<<12) ) def encode_int18(value): "encodes 18-bit integer -> 3 char hash64 string (little-endian order)" #NOTE: this is optimized form of encode_int(value,3) return ( _encode_6bit(value & 0x3f) + _encode_6bit((value>>6) & 0x3f) + _encode_6bit((value>>12) & 0x3f) ) #--------------------------------------------------------------------- def decode_int24(source): "decodes 4 char hash64 string -> 24-bit integer (little-endian order)" #NOTE: this is optimized form of decode_int(source) for 4 chars if not isinstance(source, bytes): raise TypeError("source must be bytes, not %s" % (type(source),)) try: return _decode_6bit(source[0]) +\ (_decode_6bit(source[1])<<6)+\ (_decode_6bit(source[2])<<12)+\ (_decode_6bit(source[3])<<18) except KeyError: raise ValueError("invalid character") def encode_int24(value): "encodes 24-bit integer -> 4 char hash64 string (little-endian order)" #NOTE: this is optimized form of encode_int(value,4) return _encode_6bit(value & 0x3f) + \ _encode_6bit((value>>6) & 0x3f) + \ _encode_6bit((value>>12) & 0x3f) + \ _encode_6bit((value>>18) & 0x3f) #--------------------------------------------------------------------- def decode_int64(source): "decodes 11 char hash64 string -> 64-bit integer (little-endian order; 2 msb assumed to be padding)" return decode_int(source) def encode_int64(value): "encodes 64-bit integer -> 11 char hash64 string (little-endian order; 2 msb of 0's added as padding)" return encode_int(value, 11) def decode_dc_int64(source): """decode 11 char hash64 string -> 64-bit integer (big-endian order; 2 lsb assumed to be padding) this format is used primarily by des-crypt & variants to encode the DES output value used as a checksum. """ return decode_int(source, True)>>2 def encode_dc_int64(value): """encode 64-bit integer -> 11 char hash64 string (big-endian order; 2 lsb added as padding) this format is used primarily by des-crypt & variants to encode the DES output value used as a checksum. """ #NOTE: insert 2 padding bits as lsb, to make 66 bits total return encode_int(value<<2,11,True) #--------------------------------------------------------------------- def decode_int(source, big=False): """decode hash64 string -> integer :arg source: hash64 string of any length :arg big: if ``True``, big-endian encoding is used instead of little-endian (the default). :raises ValueError: if the string contains invalid hash64 characters. :returns: a integer whose value is in ``range(0,2**(6*len(source)))`` """ if not isinstance(source, bytes): raise TypeError("source must be bytes, not %s" % (type(source),)) #FORMAT: little-endian, each char contributes 6 bits, # char value = index in H64_CHARS string if not big: source = reversed(source) try: out = 0 for c in source: #NOTE: under py3, 'c' is int, relying on _CHARIDX to support this. out = (out<<6) + _decode_6bit(c) return out except KeyError: raise ValueError("invalid character in string") def encode_int(value, count, big=False): """encode integer into hash-64 format :arg value: non-negative integer to encode :arg count: number of output characters / 6 bit chunks to encode :arg big: if ``True``, big-endian encoding is used instead of little-endian (the default). :returns: a hash64 string of length ``count``. """ if value < 0: raise ValueError("value cannot be negative") if big: itr = xrange(6*count-6, -6, -6) else: itr = xrange(0, 6*count, 6) return bjoin( _encode_6bit((value>>off) & 0x3f) for off in itr ) #================================================================================= #eof #================================================================================= passlib-1.5.3/passlib/tests/0000755000175000017500000000000011643754212017107 5ustar biscuitbiscuit00000000000000passlib-1.5.3/passlib/tests/test_utils_handlers.py0000644000175000017500000004073011643466373023554 0ustar biscuitbiscuit00000000000000"""tests for passlib.pwhash -- (c) Assurance Technologies 2003-2009""" #========================================================= #imports #========================================================= from __future__ import with_statement #core import re import hashlib from logging import getLogger import warnings #site #pkg from passlib.hash import ldap_md5 from passlib.registry import _unload_handler_name as unload_handler_name, \ register_crypt_handler, get_crypt_handler from passlib.utils import rng, getrandstr, handlers as uh, bytes, b, \ to_hash_str, to_unicode, MissingBackendError, jython_vm from passlib.tests.utils import HandlerCase, TestCase, catch_warnings, \ dummy_handler_in_registry #module log = getLogger(__name__) #========================================================= #test support classes - StaticHandler, GenericHandler, etc #========================================================= class SkeletonTest(TestCase): "test hash support classes" #========================================================= #StaticHandler #========================================================= def test_00_static_handler(self): "test StaticHandler helper class" class d1(uh.StaticHandler): name = "d1" context_kwds = ("flag",) @classmethod def genhash(cls, secret, hash, flag=False): if isinstance(hash, bytes): hash = hash.decode("ascii") if hash not in (u'a',u'b'): raise ValueError return to_hash_str(u'b' if flag else u'a') #check default identify method self.assertTrue(d1.identify(u'a')) self.assertTrue(d1.identify(b('a'))) self.assertTrue(d1.identify(u'b')) self.assertFalse(d1.identify(u'c')) self.assertFalse(d1.identify(b('c'))) self.assertFalse(d1.identify(u'')) self.assertFalse(d1.identify(None)) #check default genconfig method self.assertIs(d1.genconfig(), None) d1._stub_config = u'b' self.assertEqual(d1.genconfig(), to_hash_str('b')) #check default verify method self.assertTrue(d1.verify('s','a')) self.assertTrue(d1.verify('s',u'a')) self.assertFalse(d1.verify('s','b')) self.assertFalse(d1.verify('s',u'b')) self.assertTrue(d1.verify('s', 'b', flag=True)) self.assertRaises(ValueError, d1.verify, 's', 'c') #check default encrypt method self.assertEqual(d1.encrypt('s'), to_hash_str('a')) self.assertEqual(d1.encrypt('s'), to_hash_str('a')) self.assertEqual(d1.encrypt('s', flag=True), to_hash_str('b')) #========================================================= #GenericHandler & mixins #========================================================= def test_10_identify(self): "test GenericHandler.identify()" class d1(uh.GenericHandler): @classmethod def from_string(cls, hash): if hash == 'a': return cls(checksum='a') else: raise ValueError #check fallback self.assertFalse(d1.identify(None)) self.assertFalse(d1.identify('')) self.assertTrue(d1.identify('a')) self.assertFalse(d1.identify('b')) #check ident-based d1.ident = u'!' self.assertFalse(d1.identify(None)) self.assertFalse(d1.identify('')) self.assertTrue(d1.identify('!a')) self.assertFalse(d1.identify('a')) def test_11_norm_checksum(self): "test GenericHandler.norm_checksum()" class d1(uh.GenericHandler): name = 'd1' checksum_size = 4 checksum_chars = 'x' self.assertRaises(ValueError, d1.norm_checksum, 'xxx') self.assertEqual(d1.norm_checksum('xxxx'), 'xxxx') self.assertRaises(ValueError, d1.norm_checksum, 'xxxxx') self.assertRaises(ValueError, d1.norm_checksum, 'xxyx') def test_20_norm_salt(self): "test GenericHandler+HasSalt: .norm_salt(), .generate_salt()" class d1(uh.HasSalt, uh.GenericHandler): name = 'd1' setting_kwds = ('salt',) min_salt_size = 1 max_salt_size = 3 default_salt_size = 2 salt_chars = 'a' #check salt=None self.assertEqual(d1.norm_salt(None), 'aa') self.assertRaises(ValueError, d1.norm_salt, None, strict=True) #check small & large salts with catch_warnings(): warnings.filterwarnings("ignore", ".* salt string must be at (least|most) .*", UserWarning) self.assertEqual(d1.norm_salt('aaaa'), 'aaa') self.assertRaises(ValueError, d1.norm_salt, '') self.assertRaises(ValueError, d1.norm_salt, 'aaaa', strict=True) #check generate salt (indirectly) self.assertEqual(len(d1.norm_salt(None)), 2) self.assertEqual(len(d1.norm_salt(None,salt_size=1)), 1) self.assertEqual(len(d1.norm_salt(None,salt_size=3)), 3) self.assertEqual(len(d1.norm_salt(None,salt_size=5)), 3) self.assertRaises(ValueError, d1.norm_salt, None, salt_size=5, strict=True) def test_21_norm_salt(self): "test GenericHandler+HasSalt: .norm_salt(), .generate_salt() - with no max_salt_size" class d1(uh.HasSalt, uh.GenericHandler): name = 'd1' setting_kwds = ('salt',) min_salt_size = 1 max_salt_size = None default_salt_size = 2 salt_chars = 'a' #check salt=None self.assertEqual(d1.norm_salt(None), 'aa') self.assertRaises(ValueError, d1.norm_salt, None, strict=True) #check small & large salts self.assertRaises(ValueError, d1.norm_salt, '') self.assertEqual(d1.norm_salt('aaaa', strict=True), 'aaaa') #check generate salt (indirectly) self.assertEqual(len(d1.norm_salt(None)), 2) self.assertEqual(len(d1.norm_salt(None,salt_size=1)), 1) self.assertEqual(len(d1.norm_salt(None,salt_size=3)), 3) self.assertEqual(len(d1.norm_salt(None,salt_size=5)), 5) def test_30_norm_rounds(self): "test GenericHandler+HasRounds: .norm_rounds()" class d1(uh.HasRounds, uh.GenericHandler): name = 'd1' setting_kwds = ('rounds',) min_rounds = 1 max_rounds = 3 default_rounds = 2 #check rounds=None self.assertEqual(d1.norm_rounds(None), 2) self.assertRaises(ValueError, d1.norm_rounds, None, strict=True) #check small & large rounds with catch_warnings(): warnings.filterwarnings("ignore", ".* does not allow (less|more) than \d rounds: .*", UserWarning) self.assertEqual(d1.norm_rounds(0), 1) self.assertEqual(d1.norm_rounds(4), 3) self.assertRaises(ValueError, d1.norm_rounds, 0, strict=True) self.assertRaises(ValueError, d1.norm_rounds, 4, strict=True) #check no default rounds d1.default_rounds = None self.assertRaises(ValueError, d1.norm_rounds, None) def test_40_backends(self): "test GenericHandler+HasManyBackends" class d1(uh.HasManyBackends, uh.GenericHandler): name = 'd1' setting_kwds = () backends = ("a", "b") _has_backend_a = False _has_backend_b = False def _calc_checksum_a(self, secret): return 'a' def _calc_checksum_b(self, secret): return 'b' #test no backends self.assertRaises(MissingBackendError, d1.get_backend) self.assertRaises(MissingBackendError, d1.set_backend) self.assertRaises(MissingBackendError, d1.set_backend, 'any') self.assertRaises(MissingBackendError, d1.set_backend, 'default') self.assertFalse(d1.has_backend()) #enable 'b' backend d1._has_backend_b = True #test lazy load obj = d1() self.assertEqual(obj.calc_checksum('s'), 'b') #test repeat load d1.set_backend('b') d1.set_backend('any') self.assertEqual(obj.calc_checksum('s'), 'b') #test unavailable self.assertRaises(MissingBackendError, d1.set_backend, 'a') self.assertTrue(d1.has_backend('b')) self.assertFalse(d1.has_backend('a')) #enable 'a' backend also d1._has_backend_a = True #test explicit self.assertTrue(d1.has_backend()) d1.set_backend('a') self.assertEqual(obj.calc_checksum('s'), 'a') #test unknown backend self.assertRaises(ValueError, d1.set_backend, 'c') self.assertRaises(ValueError, d1.has_backend, 'c') def test_50_bh_norm_ident(self): "test GenericHandler+HasManyIdents: .norm_ident() & .identify()" class d1(uh.HasManyIdents, uh.GenericHandler): name = 'd1' setting_kwds = ('ident',) ident_values = [ u"!A", u"!B" ] ident_aliases = { u"A": u"!A"} #check ident=None w/ no default self.assertIs(d1.norm_ident(None), None) self.assertRaises(ValueError, d1.norm_ident, None, strict=True) #check ident=None w/ default d1.default_ident = u"!A" self.assertEqual(d1.norm_ident(None), u'!A') self.assertRaises(ValueError, d1.norm_ident, None, strict=True) #check explicit self.assertEqual(d1.norm_ident(u'!A'), u'!A') self.assertEqual(d1.norm_ident(u'!B'), u'!B') self.assertRaises(ValueError, d1.norm_ident, u'!C') #check aliases self.assertEqual(d1.norm_ident(u'A'), u'!A') self.assertRaises(ValueError, d1.norm_ident, u'B') #check identify self.assertTrue(d1.identify(u"!Axxx")) self.assertTrue(d1.identify(u"!Bxxx")) self.assertFalse(d1.identify(u"!Cxxx")) self.assertFalse(d1.identify(u"A")) self.assertFalse(d1.identify(u"")) self.assertFalse(d1.identify(None)) #========================================================= #eoc #========================================================= #========================================================= #PrefixWrapper #========================================================= class PrefixWrapperTest(TestCase): "test PrefixWrapper class" def test_00_lazy_loading(self): "test PrefixWrapper lazy loading of handler" d1 = uh.PrefixWrapper("d1", "ldap_md5", "{XXX}", "{MD5}", lazy=True) #check base state self.assertEqual(d1._wrapped_name, "ldap_md5") self.assertIs(d1._wrapped_handler, None) #check loading works self.assertIs(d1.wrapped, ldap_md5) self.assertIs(d1._wrapped_handler, ldap_md5) #replace w/ wrong handler, make sure doesn't reload w/ dummy with dummy_handler_in_registry("ldap_md5") as dummy: self.assertIs(d1.wrapped, ldap_md5) def test_01_active_loading(self): "test PrefixWrapper active loading of handler" d1 = uh.PrefixWrapper("d1", "ldap_md5", "{XXX}", "{MD5}") #check base state self.assertEqual(d1._wrapped_name, "ldap_md5") self.assertIs(d1._wrapped_handler, ldap_md5) self.assertIs(d1.wrapped, ldap_md5) #replace w/ wrong handler, make sure doesn't reload w/ dummy with dummy_handler_in_registry("ldap_md5") as dummy: self.assertIs(d1.wrapped, ldap_md5) def test_02_explicit(self): "test PrefixWrapper with explicitly specified handler" d1 = uh.PrefixWrapper("d1", ldap_md5, "{XXX}", "{MD5}") #check base state self.assertEqual(d1._wrapped_name, None) self.assertIs(d1._wrapped_handler, ldap_md5) self.assertIs(d1.wrapped, ldap_md5) #replace w/ wrong handler, make sure doesn't reload w/ dummy with dummy_handler_in_registry("ldap_md5") as dummy: self.assertIs(d1.wrapped, ldap_md5) def test_10_wrapped_attributes(self): d1 = uh.PrefixWrapper("d1", "ldap_md5", "{XXX}", "{MD5}") self.assertEqual(d1.name, "d1") self.assertIs(d1.setting_kwds, ldap_md5.setting_kwds) def test_11_wrapped_methods(self): d1 = uh.PrefixWrapper("d1", "ldap_md5", "{XXX}", "{MD5}") dph = "{XXX}X03MO1qnZdYdgyfeuILPmQ==" lph = "{MD5}X03MO1qnZdYdgyfeuILPmQ==" #genconfig self.assertIs(d1.genconfig(), None) #genhash self.assertEqual(d1.genhash("password", None), dph) self.assertEqual(d1.genhash("password", dph), dph) self.assertRaises(ValueError, d1.genhash, "password", lph) #encrypt self.assertEqual(d1.encrypt("password"), dph) #identify self.assertTrue(d1.identify(dph)) self.assertFalse(d1.identify(lph)) #verify self.assertRaises(ValueError, d1.verify, "password", lph) self.assertTrue(d1.verify("password", dph)) #========================================================= #sample algorithms - these serve as known quantities # to test the unittests themselves, as well as other # parts of passlib. they shouldn't be used as actual password schemes. #========================================================= class UnsaltedHash(uh.StaticHandler): "test algorithm which lacks a salt" name = "unsalted_test_hash" _stub_config = "0" * 40 @classmethod def identify(cls, hash): return uh.identify_regexp(hash, re.compile(u"^[0-9a-f]{40}$")) @classmethod def genhash(cls, secret, hash): if not cls.identify(hash): raise ValueError("not a unsalted-example hash") if isinstance(secret, unicode): secret = secret.encode("utf-8") data = b("boblious") + secret return to_hash_str(hashlib.sha1(data).hexdigest()) class SaltedHash(uh.HasSalt, uh.GenericHandler): "test algorithm with a salt" name = "salted_test_hash" setting_kwds = ("salt",) min_salt_size = 2 max_salt_size = 4 checksum_size = 40 salt_chars = checksum_chars = uh.LC_HEX_CHARS @classmethod def identify(cls, hash): return uh.identify_regexp(hash, re.compile(u"^@salt[0-9a-f]{42,44}$")) @classmethod def from_string(cls, hash): if not cls.identify(hash): raise ValueError("not a salted-example hash") if isinstance(hash, bytes): hash = hash.decode("ascii") return cls(salt=hash[5:-40], checksum=hash[-40:], strict=True) _stub_checksum = '0' * 40 def to_string(self): hash = u"@salt%s%s" % (self.salt, self.checksum or self._stub_checksum) return to_hash_str(hash) def calc_checksum(self, secret): if isinstance(secret, unicode): secret = secret.encode("utf-8") data = self.salt.encode("ascii") + secret + self.salt.encode("ascii") return to_unicode(hashlib.sha1(data).hexdigest(), "latin-1") #========================================================= #test sample algorithms - really a self-test of HandlerCase #========================================================= #TODO: provide data samples for algorithms # (positive knowns, negative knowns, invalid identify) class UnsaltedHashTest(HandlerCase): handler = UnsaltedHash known_correct_hashes = [ ("password", "61cfd32684c47de231f1f982c214e884133762c0"), ] def test_bad_kwds(self): if not jython_vm: #FIXME: annoyingly, the object() constructor of Jython (as of 2.5.2) # silently drops any extra kwds (old 2.4 behavior) # instead of raising TypeError (new 2.5 behavior). # we *could* use a custom base object to restore correct # behavior, but that's a lot of effort for a non-critical # border case. so just skipping this test instead... self.assertRaises(TypeError, UnsaltedHash, salt='x') self.assertRaises(ValueError, SaltedHash, checksum=SaltedHash._stub_checksum, salt=None, strict=True) self.assertRaises(ValueError, SaltedHash, checksum=SaltedHash._stub_checksum, salt='xxx', strict=True) self.assertRaises(TypeError, UnsaltedHash.genconfig, rounds=1) class SaltedHashTest(HandlerCase): handler = SaltedHash known_correct_hashes = [ ("password", '@salt77d71f8fe74f314dac946766c1ac4a2a58365482c0'), (u'\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2', '@salt9f978a9bfe360d069b0c13f2afecd570447407fa7e48'), ] #========================================================= #EOF #========================================================= passlib-1.5.3/passlib/tests/test_ext_django.py0000644000175000017500000005062411643466373022661 0ustar biscuitbiscuit00000000000000"""test passlib.ext.django""" #========================================================= #imports #========================================================= from __future__ import with_statement #core import logging; log = logging.getLogger(__name__) import sys import warnings #site #pkg from passlib.context import CryptContext, CryptPolicy from passlib.apps import django_context from passlib.ext.django import utils from passlib.hash import sha256_crypt from passlib.tests.utils import TestCase, unittest, ut_version, catch_warnings import passlib.tests.test_drivers as td from passlib.utils import Undef from passlib.registry import get_crypt_handler #module #========================================================= # import & configure django settings, #========================================================= try: from django.conf import settings, LazySettings has_django = True except ImportError: settings = None has_django = False has_django0 = False #are we using django 0.9 release? has_django1 = False #inverse - are we using django >= 1.0 if has_django: from django import VERSION has_django0 = (VERSION < (1,0)) has_django1 = (VERSION >= (1,0)) if not isinstance(settings, LazySettings): #this could mean django has been configured somehow, #which we don't want, since test cases reset and manipulate settings. raise RuntimeError("expected django.conf.settings to be LazySettings: %r" % (settings,)) #else configure a blank settings instance for our unittests if has_django0: if settings._target is None: from django.conf import UserSettingsHolder, global_settings settings._target = UserSettingsHolder(global_settings) else: if not settings.configured: settings.configure() def update_settings(**kwds): for k,v in kwds.iteritems(): if v is Undef: if hasattr(settings, k): if has_django0: delattr(settings._target, k) else: delattr(settings, k) else: setattr(settings, k, v) #========================================================= # and prepare helper to skip all relevant tests # if django isn't installed. #========================================================= def skipUnlessDjango(cls): "helper to skip class if django not present" if has_django: return cls if ut_version < 2: return None return unittest.skip("Django not installed")(cls) #========================================================= # mock user object #========================================================= if has_django: import django.contrib.auth.models as dam class FakeUser(dam.User): "stub user object for testing" #this mainly just overrides .save() to test commit behavior. saved_password = None def save(self): self.saved_password = self.password #========================================================= # helper contexts #========================================================= # simple context which looks NOTHING like django, # so we can tell if patching worked. simple_context = CryptContext( schemes = [ "md5_crypt", "des_crypt" ], default = "md5_crypt", deprecated = [ "des_crypt" ], ) # some sample hashes sample1 = 'password' sample1_md5 = '$1$kAd49ifN$biuRAv1Tv0zGHyCv0uIqW.' sample1_des = 'PPPTDkiCeu/jM' sample1_sha1 = 'sha1$b215d$9ee0a66f84ef1ad99096355e788135f7e949bd41' # context for testing category funcs category_context = CryptContext( schemes = [ "sha256_crypt" ], sha256_crypt__rounds = 1000, staff__sha256_crypt__rounds = 2000, superuser__sha256_crypt__rounds = 3000, ) def get_cc_rounds(**kwds): "helper for testing category funcs" user = FakeUser(**kwds) user.set_password("placeholder") return sha256_crypt.from_string(user.password).rounds #========================================================= # test utils #========================================================= class PatchTest(TestCase): "test passlib.ext.django.utils:set_django_password_context" case_prefix = "passlib.ext.django utils" def assert_unpatched(self): "helper to ensure django hasn't been patched" state = utils._django_patch_state #make sure we aren't currently patched self.assertIs(state, None) #make sure nothing else patches django for func in [ dam.check_password, dam.User.check_password, dam.User.set_password, ]: self.assertEquals(func.__module__, "django.contrib.auth.models") self.assertFalse(hasattr(dam.User, "password_context")) def assert_patched(self, context=Undef): "helper to ensure django HAS been patched" state = utils._django_patch_state #make sure we're patched self.assertIsNot(state, None) #make sure our methods are exposed for func in [ dam.check_password, dam.User.check_password, dam.User.set_password, ]: self.assertEquals(func.__module__, "passlib.ext.django.utils") #make sure methods match self.assertIs(dam.check_password, state['models_check_password']) self.assertIs(dam.User.check_password.im_func, state['user_check_password']) self.assertIs(dam.User.set_password.im_func, state['user_set_password']) #make sure context matches obj = dam.User.password_context self.assertIs(obj, state['context']) if context is not Undef: self.assertIs(obj, context) #make sure old methods were stored for key in [ "orig_models_check_password", "orig_user_check_password", "orig_user_set_password", ]: value = state[key] self.assertEquals(value.__module__, "django.contrib.auth.models") def setUp(self): #reset to baseline, and verify utils.set_django_password_context(None) self.assert_unpatched() def tearDown(self): #reset to baseline, and verify utils.set_django_password_context(None) self.assert_unpatched() def test_00_patch_control(self): "test set_django_password_context patch/unpatch" #check context=None has no effect utils.set_django_password_context(None) self.assert_unpatched() #patch to use stock django context utils.set_django_password_context(django_context) self.assert_patched(context=django_context) #try to remove patch utils.set_django_password_context(None) self.assert_unpatched() #patch to use stock django context again utils.set_django_password_context(django_context) self.assert_patched(context=django_context) #try to remove patch again utils.set_django_password_context(None) self.assert_unpatched() def test_01_patch_control_detection(self): "test set_django_password_context detection of foreign monkeypatches" def dummy(): pass with catch_warnings(record=True) as wlog: warnings.simplefilter("always") #patch to use stock django context utils.set_django_password_context(django_context) self.assert_patched(context=django_context) self.assertEquals(len(wlog), 0) #mess with User.set_password, make sure it's detected dam.User.set_password = dummy utils.set_django_password_context(django_context) self.assert_patched(context=django_context) self.assertEquals(len(wlog), 1) self.assertWarningMatches(wlog.pop(), message_re="^another library has patched.*User\.set_password$") #mess with user.check_password, make sure it's detected dam.User.check_password = dummy utils.set_django_password_context(django_context) self.assert_patched(context=django_context) self.assertEquals(len(wlog), 1) self.assertWarningMatches(wlog.pop(), message_re="^another library has patched.*User\.check_password$") #mess with user.check_password, make sure it's detected dam.check_password = dummy utils.set_django_password_context(django_context) self.assert_patched(context=django_context) self.assertEquals(len(wlog), 1) self.assertWarningMatches(wlog.pop(), message_re="^another library has patched.*models:check_password$") def test_01_patch_bad_types(self): "test set_django_password_context bad inputs" set = utils.set_django_password_context self.assertRaises(TypeError, set, CryptPolicy()) self.assertRaises(TypeError, set, "") def test_02_models_check_password(self): "test monkeypatched models.check_password()" # patch to use simple context utils.set_django_password_context(simple_context) self.assert_patched(context=simple_context) # check correct hashes pass self.assertTrue(dam.check_password(sample1, sample1_des)) self.assertTrue(dam.check_password(sample1, sample1_md5)) # check bad password fail w/ false self.assertFalse(dam.check_password('x', sample1_des)) self.assertFalse(dam.check_password('x', sample1_md5)) # and other hashes fail w/ error self.assertRaises(ValueError, dam.check_password, sample1, sample1_sha1) self.assertRaises(ValueError, dam.check_password, sample1, None) def test_03_check_password(self): "test monkeypatched User.check_password()" # NOTE: using FakeUser so we can test .save() user = FakeUser() # patch to use simple context utils.set_django_password_context(simple_context) self.assert_patched(context=simple_context) # test that blank hash is never accepted self.assertEqual(user.password, '') self.assertIs(user.saved_password, None) self.assertFalse(user.check_password('x')) # check correct secrets pass, and wrong ones fail user.password = sample1_md5 self.assertTrue(user.check_password(sample1)) self.assertFalse(user.check_password('x')) self.assertFalse(user.check_password(None)) # none of that should have triggered update of password self.assertEqual(user.password, sample1_md5) self.assertIs(user.saved_password, None) #check unusable password if has_django1: user.set_unusable_password() self.assertFalse(user.has_usable_password()) self.assertFalse(user.check_password(None)) self.assertFalse(user.check_password('')) self.assertFalse(user.check_password(sample1)) def test_04_check_password_migration(self): "test User.check_password() hash migration" # NOTE: using FakeUser so we can test .save() user = FakeUser() # patch to use simple context utils.set_django_password_context(simple_context) self.assert_patched(context=simple_context) # set things up with a password that needs migration user.password = sample1_des self.assertEqual(user.password, sample1_des) self.assertIs(user.saved_password, None) # run check with bad password... # shouldn't have migrated self.assertFalse(user.check_password('x')) self.assertFalse(user.check_password(None)) self.assertEqual(user.password, sample1_des) self.assertIs(user.saved_password, None) # run check with correct password... # should have migrated to md5 and called save() self.assertTrue(user.check_password(sample1)) self.assertTrue(user.password.startswith("$1$")) self.assertEqual(user.saved_password, user.password) # check resave doesn't happen user.saved_password = None self.assertTrue(user.check_password(sample1)) self.assertIs(user.saved_password, None) def test_05_set_password(self): "test monkeypatched User.set_password()" user = FakeUser() # patch to use simple context utils.set_django_password_context(simple_context) self.assert_patched(context=simple_context) # sanity check self.assertEqual(user.password, '') self.assertIs(user.saved_password, None) if has_django1: self.assertTrue(user.has_usable_password()) # set password user.set_password(sample1) self.assertTrue(user.check_password(sample1)) self.assertEquals(simple_context.identify(user.password), "md5_crypt") self.assertIs(user.saved_password, None) #check unusable password user.set_password(None) if has_django1: self.assertFalse(user.has_usable_password()) self.assertIs(user.saved_password, None) def test_06_get_category(self): "test default get_category function" func = utils.get_category self.assertIs(func(FakeUser()), None) self.assertEquals(func(FakeUser(is_staff=True)), "staff") self.assertEquals(func(FakeUser(is_superuser=True)), "superuser") self.assertEquals(func(FakeUser(is_staff=True, is_superuser=True)), "superuser") def test_07_get_category(self): "test set_django_password_context's get_category parameter" # test patch uses default get_category utils.set_django_password_context(category_context) self.assertEquals(get_cc_rounds(), 1000) self.assertEquals(get_cc_rounds(is_staff=True), 2000) self.assertEquals(get_cc_rounds(is_superuser=True), 3000) # test patch uses explicit get_category def get_category(user): return user.first_name or None utils.set_django_password_context(category_context, get_category) self.assertEquals(get_cc_rounds(), 1000) self.assertEquals(get_cc_rounds(first_name='other'), 1000) self.assertEquals(get_cc_rounds(first_name='staff'), 2000) self.assertEquals(get_cc_rounds(first_name='superuser'), 3000) # test patch can disable get_category utils.set_django_password_context(category_context, None) self.assertEquals(get_cc_rounds(), 1000) self.assertEquals(get_cc_rounds(first_name='other'), 1000) self.assertEquals(get_cc_rounds(first_name='staff', is_staff=True), 1000) self.assertEquals(get_cc_rounds(first_name='superuser', is_superuser=True), 1000) PatchTest = skipUnlessDjango(PatchTest) #========================================================= # test django plugin #========================================================= django_hash_tests = [ td.HexMd5Test, td.DjangoDesCryptTest, td.DjangoSaltedMd5Test, td.DjangoSaltedSha1Test, ] default_hash_tests = django_hash_tests + [ td.Builtin_SHA512CryptTest ] if has_django0: django_hash_tests.remove(td.DjangoDesCryptTest) class PluginTest(TestCase): "test django plugin via settings" case_prefix = "passlib.ext.django plugin" def setUp(self): #remove django patch utils.set_django_password_context(None) #ensure django settings are empty update_settings( PASSLIB_CONTEXT=Undef, PASSLIB_GET_CATEGORY=Undef, ) #unload module so it's re-run sys.modules.pop("passlib.ext.django.models", None) def tearDown(self): #remove django patch utils.set_django_password_context(None) def check_hashes(self, tests, new_hash=None, deprecated=None): u = FakeUser() deprecated = None # check new hash construction if new_hash: u.set_password("placeholder") handler = get_crypt_handler(new_hash) self.assertTrue(handler.identify(u.password)) # run against hashes from tests... for test in tests: for secret, hash in test.all_correct_hashes: # check against valid password u.password = hash if has_django0 and isinstance(secret, unicode): secret = secret.encode("utf-8") self.assertTrue(u.check_password(secret)) if new_hash and deprecated and test.handler.name in deprecated: self.assertFalse(handler.identify(hash)) self.assertTrue(handler.identify(u.password)) # check against invalid password u.password = hash self.assertFalse(u.check_password('x'+secret)) if new_hash and deprecated and test.handler.name in deprecated: self.assertFalse(handler.identify(hash)) self.assertEquals(u.password, hash) # check disabled handling if has_django1: u.set_password(None) handler = get_crypt_handler("django_disabled") self.assertTrue(handler.identify(u.password)) self.assertFalse(u.check_password('placeholder')) def test_00_actual_django(self): "test actual Django behavior has not changed" #NOTE: if this test fails, # probably means newer version of Django, # and passlib's policies should be updated. self.check_hashes(django_hash_tests, "django_salted_sha1", ["hex_md5"]) def test_01_explicit_unset(self, value=None): "test PASSLIB_CONTEXT = None" update_settings( PASSLIB_CONTEXT=value, ) import passlib.ext.django.models self.check_hashes(django_hash_tests, "django_salted_sha1", ["hex_md5"]) def test_02_stock_ctx(self): "test PASSLIB_CONTEXT = utils.STOCK_CTX" self.test_01_explicit_unset(value=utils.STOCK_CTX) def test_03_implicit_default_ctx(self): "test PASSLIB_CONTEXT unset" import passlib.ext.django.models self.check_hashes(default_hash_tests, "sha512_crypt", ["hex_md5", "django_salted_sha1", "django_salted_md5", "django_des_crypt", ]) def test_04_explicit_default_ctx(self): "test PASSLIB_CONTEXT = utils.DEFAULT_CTX" update_settings( PASSLIB_CONTEXT=utils.DEFAULT_CTX, ) self.test_03_implicit_default_ctx() def test_05_default_ctx_alias(self): "test PASSLIB_CONTEXT = 'passlib-default'" update_settings( PASSLIB_CONTEXT="passlib-default", ) self.test_03_implicit_default_ctx() def test_06_categories(self): "test PASSLIB_GET_CATEGORY unset" update_settings( PASSLIB_CONTEXT=category_context.policy, ) import passlib.ext.django.models self.assertEquals(get_cc_rounds(), 1000) self.assertEquals(get_cc_rounds(is_staff=True), 2000) self.assertEquals(get_cc_rounds(is_superuser=True), 3000) def test_07_categories_explicit(self): "test PASSLIB_GET_CATEGORY = function" def get_category(user): return user.first_name or None update_settings( PASSLIB_CONTEXT = category_context.policy, PASSLIB_GET_CATEGORY = get_category, ) import passlib.ext.django.models self.assertEquals(get_cc_rounds(), 1000) self.assertEquals(get_cc_rounds(first_name='other'), 1000) self.assertEquals(get_cc_rounds(first_name='staff'), 2000) self.assertEquals(get_cc_rounds(first_name='superuser'), 3000) def test_08_categories_disabled(self): "test PASSLIB_GET_CATEGORY = None" update_settings( PASSLIB_CONTEXT = category_context.policy, PASSLIB_GET_CATEGORY = None, ) import passlib.ext.django.models self.assertEquals(get_cc_rounds(), 1000) self.assertEquals(get_cc_rounds(first_name='other'), 1000) self.assertEquals(get_cc_rounds(first_name='staff', is_staff=True), 1000) self.assertEquals(get_cc_rounds(first_name='superuser', is_superuser=True), 1000) PluginTest = skipUnlessDjango(PluginTest) #========================================================= #eof #========================================================= passlib-1.5.3/passlib/tests/test_context.py0000644000175000017500000010355311643753301022211 0ustar biscuitbiscuit00000000000000"""tests for passlib.pwhash -- (c) Assurance Technologies 2003-2009""" #========================================================= #imports #========================================================= from __future__ import with_statement #core import hashlib from logging import getLogger import os import time import warnings import sys #site try: from pkg_resources import resource_filename except ImportError: resource_filename = None #pkg from passlib import hash from passlib.context import CryptContext, CryptPolicy, LazyCryptContext from passlib.utils import to_bytes, to_unicode import passlib.utils.handlers as uh from passlib.tests.utils import TestCase, mktemp, catch_warnings, \ gae_env, set_file from passlib.registry import register_crypt_handler_path, has_crypt_handler, \ _unload_handler_name as unload_handler_name #module log = getLogger(__name__) #========================================================= # #========================================================= class CryptPolicyTest(TestCase): "test CryptPolicy object" #TODO: need to test user categories w/in all this case_prefix = "CryptPolicy" #========================================================= #sample crypt policies used for testing #========================================================= #----------------------------------------------------- #sample 1 - average config file #----------------------------------------------------- #NOTE: copy of this is stored in file passlib/tests/sample_config_1s.cfg sample_config_1s = """\ [passlib] schemes = des_crypt, md5_crypt, bsdi_crypt, sha512_crypt default = md5_crypt all.vary_rounds = 10%% bsdi_crypt.max_rounds = 30000 bsdi_crypt.default_rounds = 25000 sha512_crypt.max_rounds = 50000 sha512_crypt.min_rounds = 40000 """ sample_config_1s_path = os.path.abspath(os.path.join( os.path.dirname(__file__), "sample_config_1s.cfg")) if not os.path.exists(sample_config_1s_path) and resource_filename: #in case we're zipped up in an egg. sample_config_1s_path = resource_filename("passlib.tests", "sample_config_1s.cfg") #make sure sample_config_1s uses \n linesep - tests rely on this assert sample_config_1s.startswith("[passlib]\nschemes") sample_config_1pd = dict( schemes = [ "des_crypt", "md5_crypt", "bsdi_crypt", "sha512_crypt"], default = "md5_crypt", all__vary_rounds = "10%", bsdi_crypt__max_rounds = 30000, bsdi_crypt__default_rounds = 25000, sha512_crypt__max_rounds = 50000, sha512_crypt__min_rounds = 40000, ) sample_config_1pid = { "schemes": "des_crypt, md5_crypt, bsdi_crypt, sha512_crypt", "default": "md5_crypt", "all.vary_rounds": "10%", "bsdi_crypt.max_rounds": 30000, "bsdi_crypt.default_rounds": 25000, "sha512_crypt.max_rounds": 50000, "sha512_crypt.min_rounds": 40000, } sample_config_1prd = dict( schemes = [ hash.des_crypt, hash.md5_crypt, hash.bsdi_crypt, hash.sha512_crypt], default = hash.md5_crypt, all__vary_rounds = "10%", bsdi_crypt__max_rounds = 30000, bsdi_crypt__default_rounds = 25000, sha512_crypt__max_rounds = 50000, sha512_crypt__min_rounds = 40000, ) #----------------------------------------------------- #sample 2 - partial policy & result of overlay on sample 1 #----------------------------------------------------- sample_config_2s = """\ [passlib] bsdi_crypt.min_rounds = 29000 bsdi_crypt.max_rounds = 35000 bsdi_crypt.default_rounds = 31000 sha512_crypt.min_rounds = 45000 """ sample_config_2pd = dict( #using this to test full replacement of existing options bsdi_crypt__min_rounds = 29000, bsdi_crypt__max_rounds = 35000, bsdi_crypt__default_rounds = 31000, #using this to test partial replacement of existing options sha512_crypt__min_rounds=45000, ) sample_config_12pd = dict( schemes = [ "des_crypt", "md5_crypt", "bsdi_crypt", "sha512_crypt"], default = "md5_crypt", all__vary_rounds = "10%", bsdi_crypt__min_rounds = 29000, bsdi_crypt__max_rounds = 35000, bsdi_crypt__default_rounds = 31000, sha512_crypt__max_rounds = 50000, sha512_crypt__min_rounds=45000, ) #----------------------------------------------------- #sample 3 - just changing default #----------------------------------------------------- sample_config_3pd = dict( default="sha512_crypt", ) sample_config_123pd = dict( schemes = [ "des_crypt", "md5_crypt", "bsdi_crypt", "sha512_crypt"], default = "sha512_crypt", all__vary_rounds = "10%", bsdi_crypt__min_rounds = 29000, bsdi_crypt__max_rounds = 35000, bsdi_crypt__default_rounds = 31000, sha512_crypt__max_rounds = 50000, sha512_crypt__min_rounds=45000, ) #----------------------------------------------------- #sample 4 - category specific #----------------------------------------------------- sample_config_4s = """ [passlib] schemes = sha512_crypt all.vary_rounds = 10%% default.sha512_crypt.max_rounds = 20000 admin.all.vary_rounds = 5%% admin.sha512_crypt.max_rounds = 40000 """ sample_config_4pd = dict( schemes = [ "sha512_crypt" ], all__vary_rounds = "10%", sha512_crypt__max_rounds = 20000, admin__all__vary_rounds = "5%", admin__sha512_crypt__max_rounds = 40000, ) #----------------------------------------------------- #sample 5 - to_string & deprecation testing #----------------------------------------------------- sample_config_5s = sample_config_1s + """\ deprecated = des_crypt admin__context__deprecated = des_crypt, bsdi_crypt """ sample_config_5pd = sample_config_1pd.copy() sample_config_5pd.update( deprecated = [ "des_crypt" ], admin__context__deprecated = [ "des_crypt", "bsdi_crypt" ], ) sample_config_5pid = sample_config_1pid.copy() sample_config_5pid.update({ "deprecated": "des_crypt", "admin.context.deprecated": "des_crypt, bsdi_crypt", }) sample_config_5prd = sample_config_1prd.copy() sample_config_5prd.update({ # XXX: should deprecated return the actual handlers in this case? # would have to modify how policy stores info, for one. "deprecated": ["des_crypt"], "admin__context__deprecated": ["des_crypt", "bsdi_crypt"], }) #========================================================= #constructors #========================================================= def test_00_constructor(self): "test CryptPolicy() constructor" policy = CryptPolicy(**self.sample_config_1pd) self.assertEqual(policy.to_dict(), self.sample_config_1pd) #check with bad key self.assertRaises(KeyError, CryptPolicy, schemes = [ "des_crypt", "md5_crypt", "bsdi_crypt", "sha512_crypt"], bad__key__bsdi_crypt__max_rounds = 30000, ) #check with bad handler self.assertRaises(TypeError, CryptPolicy, schemes=[uh.StaticHandler]) #check with multiple handlers class dummy_1(uh.StaticHandler): name = 'dummy_1' self.assertRaises(KeyError, CryptPolicy, schemes=[dummy_1, dummy_1]) #with unknown deprecated value self.assertRaises(KeyError, CryptPolicy, schemes=['des_crypt'], deprecated=['md5_crypt']) #with unknown default value self.assertRaises(KeyError, CryptPolicy, schemes=['des_crypt'], default='md5_crypt') def test_01_from_path_simple(self): "test CryptPolicy.from_path() constructor" #NOTE: this is separate so it can also run under GAE #test preset stored in existing file path = self.sample_config_1s_path policy = CryptPolicy.from_path(path) self.assertEqual(policy.to_dict(), self.sample_config_1pd) #test if path missing self.assertRaises(EnvironmentError, CryptPolicy.from_path, path + 'xxx') def test_01_from_path(self): "test CryptPolicy.from_path() constructor with encodings" if gae_env: return self.skipTest("GAE doesn't offer read/write filesystem access") path = mktemp() #test "\n" linesep set_file(path, self.sample_config_1s) policy = CryptPolicy.from_path(path) self.assertEqual(policy.to_dict(), self.sample_config_1pd) #test "\r\n" linesep set_file(path, self.sample_config_1s.replace("\n","\r\n")) policy = CryptPolicy.from_path(path) self.assertEqual(policy.to_dict(), self.sample_config_1pd) #test with custom encoding uc2 = to_bytes(self.sample_config_1s, "utf-16", source_encoding="utf-8") set_file(path, uc2) policy = CryptPolicy.from_path(path, encoding="utf-16") self.assertEqual(policy.to_dict(), self.sample_config_1pd) def test_02_from_string(self): "test CryptPolicy.from_string() constructor" #test "\n" linesep policy = CryptPolicy.from_string(self.sample_config_1s) self.assertEqual(policy.to_dict(), self.sample_config_1pd) #test "\r\n" linesep policy = CryptPolicy.from_string( self.sample_config_1s.replace("\n","\r\n")) self.assertEqual(policy.to_dict(), self.sample_config_1pd) #test with unicode data = to_unicode(self.sample_config_1s) policy = CryptPolicy.from_string(data) self.assertEqual(policy.to_dict(), self.sample_config_1pd) #test with non-ascii-compatible encoding uc2 = to_bytes(self.sample_config_1s, "utf-16", source_encoding="utf-8") policy = CryptPolicy.from_string(uc2, encoding="utf-16") self.assertEqual(policy.to_dict(), self.sample_config_1pd) #test category specific options policy = CryptPolicy.from_string(self.sample_config_4s) self.assertEqual(policy.to_dict(), self.sample_config_4pd) def test_03_from_source(self): "test CryptPolicy.from_source() constructor" #pass it a path policy = CryptPolicy.from_source(self.sample_config_1s_path) self.assertEqual(policy.to_dict(), self.sample_config_1pd) #pass it a string policy = CryptPolicy.from_source(self.sample_config_1s) self.assertEqual(policy.to_dict(), self.sample_config_1pd) #pass it a dict (NOTE: make a copy to detect in-place modifications) policy = CryptPolicy.from_source(self.sample_config_1pd.copy()) self.assertEqual(policy.to_dict(), self.sample_config_1pd) #pass it existing policy p2 = CryptPolicy.from_source(policy) self.assertIs(policy, p2) #pass it something wrong self.assertRaises(TypeError, CryptPolicy.from_source, 1) self.assertRaises(TypeError, CryptPolicy.from_source, []) def test_04_from_sources(self): "test CryptPolicy.from_sources() constructor" #pass it empty list self.assertRaises(ValueError, CryptPolicy.from_sources, []) #pass it one-element list policy = CryptPolicy.from_sources([self.sample_config_1s]) self.assertEqual(policy.to_dict(), self.sample_config_1pd) #pass multiple sources policy = CryptPolicy.from_sources( [ self.sample_config_1s_path, self.sample_config_2s, self.sample_config_3pd, ]) self.assertEqual(policy.to_dict(), self.sample_config_123pd) def test_05_replace(self): "test CryptPolicy.replace() constructor" p1 = CryptPolicy(**self.sample_config_1pd) #check overlaying sample 2 p2 = p1.replace(**self.sample_config_2pd) self.assertEqual(p2.to_dict(), self.sample_config_12pd) #check repeating overlay makes no change p2b = p2.replace(**self.sample_config_2pd) self.assertEqual(p2b.to_dict(), self.sample_config_12pd) #check overlaying sample 3 p3 = p2.replace(self.sample_config_3pd) self.assertEqual(p3.to_dict(), self.sample_config_123pd) def test_06_forbidden(self): "test CryptPolicy() forbidden kwds" #salt not allowed to be set self.assertRaises(KeyError, CryptPolicy, schemes=["des_crypt"], des_crypt__salt="xx", ) self.assertRaises(KeyError, CryptPolicy, schemes=["des_crypt"], all__salt="xx", ) #schemes not allowed for category self.assertRaises(KeyError, CryptPolicy, schemes=["des_crypt"], user__context__schemes=["md5_crypt"], ) #========================================================= #reading #========================================================= def test_10_has_schemes(self): "test has_schemes() method" p1 = CryptPolicy(**self.sample_config_1pd) self.assertTrue(p1.has_schemes()) p3 = CryptPolicy(**self.sample_config_3pd) self.assertTrue(not p3.has_schemes()) def test_11_iter_handlers(self): "test iter_handlers() method" p1 = CryptPolicy(**self.sample_config_1pd) s = self.sample_config_1prd['schemes'] self.assertEqual(list(p1.iter_handlers()), s) p3 = CryptPolicy(**self.sample_config_3pd) self.assertEqual(list(p3.iter_handlers()), []) def test_12_get_handler(self): "test get_handler() method" p1 = CryptPolicy(**self.sample_config_1pd) #check by name self.assertIs(p1.get_handler("bsdi_crypt"), hash.bsdi_crypt) #check by missing name self.assertIs(p1.get_handler("sha256_crypt"), None) self.assertRaises(KeyError, p1.get_handler, "sha256_crypt", required=True) #check default self.assertIs(p1.get_handler(), hash.md5_crypt) def test_13_get_options(self): "test get_options() method" p12 = CryptPolicy(**self.sample_config_12pd) self.assertEqual(p12.get_options("bsdi_crypt"),dict( vary_rounds = "10%", min_rounds = 29000, max_rounds = 35000, default_rounds = 31000, )) self.assertEqual(p12.get_options("sha512_crypt"),dict( vary_rounds = "10%", min_rounds = 45000, max_rounds = 50000, )) p4 = CryptPolicy.from_string(self.sample_config_4s) self.assertEqual(p4.get_options("sha512_crypt"), dict( vary_rounds="10%", max_rounds=20000, )) self.assertEqual(p4.get_options("sha512_crypt", "user"), dict( vary_rounds="10%", max_rounds=20000, )) self.assertEqual(p4.get_options("sha512_crypt", "admin"), dict( vary_rounds="5%", max_rounds=40000, )) def test_14_handler_is_deprecated(self): "test handler_is_deprecated() method" pa = CryptPolicy(**self.sample_config_1pd) pb = CryptPolicy(**self.sample_config_5pd) self.assertFalse(pa.handler_is_deprecated("des_crypt")) self.assertFalse(pa.handler_is_deprecated(hash.bsdi_crypt)) self.assertFalse(pa.handler_is_deprecated("sha512_crypt")) self.assertTrue(pb.handler_is_deprecated("des_crypt")) self.assertFalse(pb.handler_is_deprecated(hash.bsdi_crypt)) self.assertFalse(pb.handler_is_deprecated("sha512_crypt")) #check categories as well self.assertTrue(pb.handler_is_deprecated("des_crypt", "user")) self.assertFalse(pb.handler_is_deprecated("bsdi_crypt", "user")) self.assertTrue(pb.handler_is_deprecated("des_crypt", "admin")) self.assertTrue(pb.handler_is_deprecated("bsdi_crypt", "admin")) def test_15_min_verify_time(self): pa = CryptPolicy() self.assertEqual(pa.get_min_verify_time(), 0) self.assertEqual(pa.get_min_verify_time('admin'), 0) pb = pa.replace(min_verify_time=.1) self.assertEqual(pb.get_min_verify_time(), .1) self.assertEqual(pb.get_min_verify_time('admin'), .1) pc = pa.replace(admin__context__min_verify_time=.2) self.assertEqual(pc.get_min_verify_time(), 0) self.assertEqual(pc.get_min_verify_time('admin'), .2) pd = pb.replace(admin__context__min_verify_time=.2) self.assertEqual(pd.get_min_verify_time(), .1) self.assertEqual(pd.get_min_verify_time('admin'), .2) #TODO: test this. ##def test_gen_min_verify_time(self): ## "test get_min_verify_time() method" #========================================================= #serialization #========================================================= def test_20_iter_config(self): "test iter_config() method" p5 = CryptPolicy(**self.sample_config_5pd) self.assertEqual(dict(p5.iter_config()), self.sample_config_5pd) self.assertEqual(dict(p5.iter_config(resolve=True)), self.sample_config_5prd) self.assertEqual(dict(p5.iter_config(ini=True)), self.sample_config_5pid) def test_21_to_dict(self): "test to_dict() method" p5 = CryptPolicy(**self.sample_config_5pd) self.assertEqual(p5.to_dict(), self.sample_config_5pd) self.assertEqual(p5.to_dict(resolve=True), self.sample_config_5prd) def test_22_to_string(self): "test to_string() method" pa = CryptPolicy(**self.sample_config_5pd) s = pa.to_string() #NOTE: can't compare string directly, ordering etc may not match pb = CryptPolicy.from_string(s) self.assertEqual(pb.to_dict(), self.sample_config_5pd) #========================================================= # #========================================================= #========================================================= #CryptContext #========================================================= class CryptContextTest(TestCase): "test CryptContext object's behavior" case_prefix = "CryptContext" #========================================================= #constructor #========================================================= def test_00_constructor(self): "test constructor" #create crypt context using handlers cc = CryptContext([hash.md5_crypt, hash.bsdi_crypt, hash.des_crypt]) c,b,a = cc.policy.iter_handlers() self.assertIs(a, hash.des_crypt) self.assertIs(b, hash.bsdi_crypt) self.assertIs(c, hash.md5_crypt) #create context using names cc = CryptContext(["md5_crypt", "bsdi_crypt", "des_crypt"]) c,b,a = cc.policy.iter_handlers() self.assertIs(a, hash.des_crypt) self.assertIs(b, hash.bsdi_crypt) self.assertIs(c, hash.md5_crypt) #TODO: test policy & other options def test_01_replace(self): "test replace()" cc = CryptContext(["md5_crypt", "bsdi_crypt", "des_crypt"]) self.assertIs(cc.policy.get_handler(), hash.md5_crypt) cc2 = cc.replace() self.assertIsNot(cc2, cc) self.assertIs(cc2.policy, cc.policy) cc3 = cc.replace(default="bsdi_crypt") self.assertIsNot(cc3, cc) self.assertIsNot(cc3.policy, cc.policy) self.assertIs(cc3.policy.get_handler(), hash.bsdi_crypt) def test_02_no_handlers(self): "test no handlers" #check constructor... cc = CryptContext() self.assertRaises(KeyError, cc.identify, 'hash', required=True) self.assertRaises(KeyError, cc.encrypt, 'secret') self.assertRaises(KeyError, cc.verify, 'secret', 'hash') #check updating policy after the fact... cc = CryptContext(['md5_crypt']) p = CryptPolicy(schemes=[]) cc.policy = p self.assertRaises(KeyError, cc.identify, 'hash', required=True) self.assertRaises(KeyError, cc.encrypt, 'secret') self.assertRaises(KeyError, cc.verify, 'secret', 'hash') #========================================================= #policy adaptation #========================================================= sample_policy_1 = dict( schemes = [ "des_crypt", "md5_crypt", "nthash", "bsdi_crypt", "sha256_crypt"], deprecated = [ "des_crypt", ], default = "sha256_crypt", bsdi_crypt__max_rounds = 30, bsdi_crypt__default_rounds = 25, bsdi_crypt__vary_rounds = 0, sha256_crypt__max_rounds = 3000, sha256_crypt__min_rounds = 2000, sha256_crypt__default_rounds = 3000, nthash__ident = "NT", ) def test_10_genconfig_settings(self): "test genconfig() honors policy settings" cc = CryptContext(policy=None, **self.sample_policy_1) # hash specific settings self.assertEqual( cc.genconfig(scheme="nthash"), '$NT$00000000000000000000000000000000', ) self.assertEqual( cc.genconfig(scheme="nthash", ident="3"), '$3$$00000000000000000000000000000000', ) # min rounds self.assertEqual( cc.genconfig(rounds=1999, salt="nacl"), '$5$rounds=2000$nacl$', ) self.assertEqual( cc.genconfig(rounds=2001, salt="nacl"), '$5$rounds=2001$nacl$' ) #max rounds self.assertEqual( cc.genconfig(rounds=2999, salt="nacl"), '$5$rounds=2999$nacl$', ) self.assertEqual( cc.genconfig(rounds=3001, salt="nacl"), '$5$rounds=3000$nacl$' ) #default rounds - specified self.assertEqual( cc.genconfig(scheme="bsdi_crypt", salt="nacl"), '_N...nacl', ) #default rounds - fall back to max rounds self.assertEqual( cc.genconfig(salt="nacl"), '$5$rounds=3000$nacl$', ) #default rounds - out of bounds cc2 = CryptContext(policy=cc.policy.replace( bsdi_crypt__default_rounds=35)) self.assertEqual( cc2.genconfig(scheme="bsdi_crypt", salt="nacl"), '_S...nacl', ) # default+vary rounds # this runs enough times the min and max *should* be hit, # though there's a faint chance it will randomly fail. from passlib.hash import bsdi_crypt as bc cc3 = CryptContext(policy=cc.policy.replace( bsdi_crypt__vary_rounds = 3)) seen = set() for i in xrange(3*2*50): h = cc3.genconfig("bsdi_crypt", salt="nacl") r = bc.from_string(h).rounds seen.add(r) self.assertTrue(min(seen)==22) self.assertTrue(max(seen)==28) # default+vary % rounds # this runs enough times the min and max *should* be hit, # though there's a faint chance it will randomly fail. from passlib.hash import sha256_crypt as sc cc4 = CryptContext(policy=cc.policy.replace( all__vary_rounds = "1%")) seen = set() for i in xrange(30*50): h = cc4.genconfig(salt="nacl") r = sc.from_string(h).rounds seen.add(r) self.assertTrue(min(seen)==2970) self.assertTrue(max(seen)==3000) #NOTE: would be 3030, but clipped by max_rounds def test_11_encrypt_settings(self): "test encrypt() honors policy settings" cc = CryptContext(**self.sample_policy_1) # hash specific settings self.assertEqual( cc.encrypt("password", scheme="nthash"), '$NT$8846f7eaee8fb117ad06bdd830b7586c', ) self.assertEqual( cc.encrypt("password", scheme="nthash", ident="3"), '$3$$8846f7eaee8fb117ad06bdd830b7586c', ) # min rounds self.assertEqual( cc.encrypt("password", rounds=1999, salt="nacl"), '$5$rounds=2000$nacl$9/lTZ5nrfPuz8vphznnmHuDGFuvjSNvOEDsGmGfsS97', ) self.assertEqual( cc.encrypt("password", rounds=2001, salt="nacl"), '$5$rounds=2001$nacl$8PdeoPL4aXQnJ0woHhqgIw/efyfCKC2WHneOpnvF.31' ) #TODO: # max rounds # default rounds # falls back to max, then min. # specified # outside of min/max range # default+vary rounds # default+vary % rounds #make sure default > max doesn't cause error when vary is set cc2 = cc.replace(sha256_crypt__default_rounds=4000) with catch_warnings(): warnings.filterwarnings("ignore", "vary default rounds: lower bound > upper bound.*", UserWarning) self.assertEqual( cc2.encrypt("password", salt="nacl"), '$5$rounds=3000$nacl$oH831OVMbkl.Lbw1SXflly4dW8L3mSxpxDz1u1CK/B0', ) def test_12_hash_needs_update(self): "test hash_needs_update() method" cc = CryptContext(**self.sample_policy_1) #check deprecated scheme self.assertTrue(cc.hash_needs_update('9XXD4trGYeGJA')) self.assertTrue(not cc.hash_needs_update('$1$J8HC2RCr$HcmM.7NxB2weSvlw2FgzU0')) #check min rounds self.assertTrue(cc.hash_needs_update('$5$rounds=1999$jD81UCoo.zI.UETs$Y7qSTQ6mTiU9qZB4fRr43wRgQq4V.5AAf7F97Pzxey/')) self.assertTrue(not cc.hash_needs_update('$5$rounds=2000$228SSRje04cnNCaQ$YGV4RYu.5sNiBvorQDlO0WWQjyJVGKBcJXz3OtyQ2u8')) #check max rounds self.assertTrue(not cc.hash_needs_update('$5$rounds=3000$fS9iazEwTKi7QPW4$VasgBC8FqlOvD7x2HhABaMXCTh9jwHclPA9j5YQdns.')) self.assertTrue(cc.hash_needs_update('$5$rounds=3001$QlFHHifXvpFX4PLs$/0ekt7lSs/lOikSerQ0M/1porEHxYq7W/2hdFpxA3fA')) #========================================================= #identify #========================================================= def test_20_basic(self): "test basic encrypt/identify/verify functionality" handlers = [hash.md5_crypt, hash.des_crypt, hash.bsdi_crypt] cc = CryptContext(handlers, policy=None) #run through handlers for crypt in handlers: h = cc.encrypt("test", scheme=crypt.name) self.assertEqual(cc.identify(h), crypt.name) self.assertEqual(cc.identify(h, resolve=True), crypt) self.assertTrue(cc.verify('test', h)) self.assertTrue(not cc.verify('notest', h)) #test default h = cc.encrypt("test") self.assertEqual(cc.identify(h), "md5_crypt") #test genhash h = cc.genhash('secret', cc.genconfig()) self.assertEqual(cc.identify(h), 'md5_crypt') h = cc.genhash('secret', cc.genconfig(), scheme='md5_crypt') self.assertEqual(cc.identify(h), 'md5_crypt') self.assertRaises(ValueError, cc.genhash, 'secret', cc.genconfig(), scheme="des_crypt") def test_21_identify(self): "test identify() border cases" handlers = ["md5_crypt", "des_crypt", "bsdi_crypt"] cc = CryptContext(handlers, policy=None) #check unknown hash self.assertEqual(cc.identify('$9$232323123$1287319827'), None) self.assertRaises(ValueError, cc.identify, '$9$232323123$1287319827', required=True) #make sure "None" is accepted self.assertEqual(cc.identify(None), None) self.assertRaises(ValueError, cc.identify, None, required=True) def test_22_verify(self): "test verify() scheme kwd" handlers = ["md5_crypt", "des_crypt", "bsdi_crypt"] cc = CryptContext(handlers, policy=None) h = hash.md5_crypt.encrypt("test") #check base verify self.assertTrue(cc.verify("test", h)) self.assertTrue(not cc.verify("notest", h)) #check verify using right alg self.assertTrue(cc.verify('test', h, scheme='md5_crypt')) self.assertTrue(not cc.verify('notest', h, scheme='md5_crypt')) #check verify using wrong alg self.assertRaises(ValueError, cc.verify, 'test', h, scheme='bsdi_crypt') def test_23_verify_empty_hash(self): "test verify() allows hash=None" handlers = [hash.md5_crypt, hash.des_crypt, hash.bsdi_crypt] cc = CryptContext(handlers, policy=None) self.assertTrue(not cc.verify("test", None)) for handler in handlers: self.assertTrue(not cc.verify("test", None, scheme=handler.name)) def test_24_min_verify_time(self): "test verify() honors min_verify_time" #NOTE: this whole test assumes time.sleep() and time.time() # have at least 1ms accuracy delta = .1 min_delay = delta min_verify_time = min_delay + 2*delta max_delay = min_verify_time + 2*delta class TimedHash(uh.StaticHandler): "psuedo hash that takes specified amount of time" name = "timed_hash" delay = 0 @classmethod def identify(cls, hash): return True @classmethod def genhash(cls, secret, hash): time.sleep(cls.delay) return hash or 'x' cc = CryptContext([TimedHash], min_verify_time=min_verify_time) def timecall(func, *args, **kwds): start = time.time() result = func(*args, **kwds) end = time.time() return end-start, result #verify hashing works TimedHash.delay = min_delay elapsed, _ = timecall(TimedHash.genhash, 'stub', 'stub') self.assertAlmostEqual(elapsed, min_delay, delta=delta) #ensure min verify time is honored elapsed, _ = timecall(cc.verify, "stub", "stub") self.assertAlmostEqual(elapsed, min_verify_time, delta=delta) #ensure taking longer emits a warning. TimedHash.delay = max_delay with catch_warnings(record=True) as wlog: warnings.simplefilter("always") elapsed, _ = timecall(cc.verify, "stub", "stub") self.assertAlmostEqual(elapsed, max_delay, delta=delta) self.assertEqual(len(wlog), 1) self.assertWarningMatches(wlog[0], message_re="CryptContext: verify exceeded min_verify_time") def test_25_verify_and_update(self): "test verify_and_update()" cc = CryptContext(**self.sample_policy_1) #create some hashes h1 = cc.encrypt("password", scheme="des_crypt") h2 = cc.encrypt("password", scheme="sha256_crypt") #check bad password, deprecated hash ok, new_hash = cc.verify_and_update("wrongpass", h1) self.assertFalse(ok) self.assertIs(new_hash, None) #check bad password, good hash ok, new_hash = cc.verify_and_update("wrongpass", h2) self.assertFalse(ok) self.assertIs(new_hash, None) #check right password, deprecated hash ok, new_hash = cc.verify_and_update("password", h1) self.assertTrue(ok) self.assertTrue(cc.identify(new_hash), "sha256_crypt") #check right password, good hash ok, new_hash = cc.verify_and_update("password", h2) self.assertTrue(ok) self.assertIs(new_hash, None) #========================================================= # other #========================================================= def test_90_bcrypt_normhash(self): "teset verify_and_update / hash_needs_update corrects bcrypt padding" # see issue 25. bcrypt = hash.bcrypt PASS1 = "loppux" BAD1 = "$2a$12$oaQbBqq8JnSM1NHRPQGXORm4GCUMqp7meTnkft4zgSnrbhoKdDV0C" GOOD1 = "$2a$12$oaQbBqq8JnSM1NHRPQGXOOm4GCUMqp7meTnkft4zgSnrbhoKdDV0C" ctx = CryptContext(["bcrypt"]) with catch_warnings(record=True) as wlog: warnings.simplefilter("always") self.assertTrue(ctx.hash_needs_update(BAD1)) self.assertFalse(ctx.hash_needs_update(GOOD1)) if bcrypt.has_backend(): self.assertEquals(ctx.verify_and_update(PASS1,GOOD1), (True,None)) self.assertEquals(ctx.verify_and_update("x",BAD1), (False,None)) res = ctx.verify_and_update(PASS1, BAD1) self.assertTrue(res[0] and res[1] and res[1] != BAD1) #========================================================= #eoc #========================================================= #========================================================= #LazyCryptContext #========================================================= class dummy_2(uh.StaticHandler): name = "dummy_2" class LazyCryptContextTest(TestCase): case_prefix = "LazyCryptContext" def setUp(self): unload_handler_name("dummy_2") def tearDown(self): unload_handler_name("dummy_2") def test_kwd_constructor(self): self.assertFalse(has_crypt_handler("dummy_2")) register_crypt_handler_path("dummy_2", "passlib.tests.test_context") cc = LazyCryptContext(iter(["dummy_2", "des_crypt"]), deprecated=["des_crypt"]) self.assertFalse(has_crypt_handler("dummy_2", True)) self.assertTrue(cc.policy.handler_is_deprecated("des_crypt")) self.assertEqual(cc.policy.schemes(), ["dummy_2", "des_crypt"]) self.assertTrue(has_crypt_handler("dummy_2", True)) def test_callable_constructor(self): self.assertFalse(has_crypt_handler("dummy_2")) register_crypt_handler_path("dummy_2", "passlib.tests.test_context") def create_policy(flag=False): self.assertTrue(flag) return CryptPolicy(schemes=iter(["dummy_2", "des_crypt"]), deprecated=["des_crypt"]) cc = LazyCryptContext(create_policy=create_policy, flag=True) self.assertFalse(has_crypt_handler("dummy_2", True)) self.assertTrue(cc.policy.handler_is_deprecated("des_crypt")) self.assertEqual(cc.policy.schemes(), ["dummy_2", "des_crypt"]) self.assertTrue(has_crypt_handler("dummy_2", True)) #========================================================= #EOF #========================================================= passlib-1.5.3/passlib/tests/__main__.py0000644000175000017500000000012311643466373021205 0ustar biscuitbiscuit00000000000000import os from nose import run run( defaultTest=os.path.dirname(__file__), ) passlib-1.5.3/passlib/tests/test_hosts.py0000644000175000017500000000727311643466373021701 0ustar biscuitbiscuit00000000000000"""test passlib.hosts""" #========================================================= #imports #========================================================= from __future__ import with_statement #core import logging; log = logging.getLogger(__name__) #site #pkg from passlib import hosts, hash as hashmod from passlib.utils import unix_crypt_schemes from passlib.tests.utils import TestCase #module #========================================================= #test predefined app contexts #========================================================= class HostsTest(TestCase): "perform general tests to make sure contexts work" #NOTE: these tests are not really comprehensive, # since they would do little but duplicate # the presets in apps.py # # they mainly try to ensure no typos # or dynamic behavior foul-ups. def check_unix_fallback(self, ctx): for hash in [ "", "!", "*", "!$1$TXl/FX/U$BZge.lr.ux6ekjEjxmzwz0", ]: self.assertEqual(ctx.identify(hash), 'unix_fallback') self.assertFalse(ctx.verify('test', hash)) def test_linux_context(self): ctx = hosts.linux_context for hash in [ ('$6$rounds=41128$VoQLvDjkaZ6L6BIE$4pt.1Ll1XdDYduEwEYPCMOBiR6W6' 'znsyUEoNlcVXpv2gKKIbQolgmTGe6uEEVJ7azUxuc8Tf7zV9SD2z7Ij751'), ('$5$rounds=31817$iZGmlyBQ99JSB5n6$p4E.pdPBWx19OajgjLRiOW0itGny' 'xDGgMlDcOsfaI17'), '$1$TXl/FX/U$BZge.lr.ux6ekjEjxmzwz0', 'kAJJz.Rwp0A/I', ]: self.assertTrue(ctx.verify("test", hash)) self.check_unix_fallback(ctx) def test_bsd_contexts(self): for ctx in [ hosts.freebsd_context, hosts.openbsd_context, hosts.netbsd_context, ]: for hash in [ '$1$TXl/FX/U$BZge.lr.ux6ekjEjxmzwz0', 'kAJJz.Rwp0A/I', ]: self.assertTrue(ctx.verify("test", hash)) h1 = '$2a$10$Ljj0Kgu7Ddob9xWoqzn0ae.uNfxPRofowWdksk.6jCUHKTGYLD.QG' if hashmod.bcrypt.has_backend(): self.assertTrue(ctx.verify("test", h1)) else: self.assertEqual(ctx.identify(h1), "bcrypt") self.check_unix_fallback(ctx) def test_host_context(self): ctx = getattr(hosts, "host_context", None) if not ctx: return self.skipTest("host_context not available on this platform") # validate schemes is non-empty, # and contains unix_fallback + at least one real scheme schemes = ctx.policy.schemes() self.assertTrue(schemes, "appears to be unix system, but no known schemes supported by crypt") self.assertTrue('unix_fallback' in schemes) schemes.remove("unix_fallback") self.assertTrue(schemes, "should have schemes beside fallback scheme") self.assertTrue(set(unix_crypt_schemes).issuperset(schemes)) # check for hash support self.check_unix_fallback(ctx) for scheme, hash in [ ("sha512_crypt", ('$6$rounds=41128$VoQLvDjkaZ6L6BIE$4pt.1Ll1XdDYduEwEYPCMOBiR6W6' 'znsyUEoNlcVXpv2gKKIbQolgmTGe6uEEVJ7azUxuc8Tf7zV9SD2z7Ij751')), ("sha256_crypt", ('$5$rounds=31817$iZGmlyBQ99JSB5n6$p4E.pdPBWx19OajgjLRiOW0itGny' 'xDGgMlDcOsfaI17')), ("md5_crypt", '$1$TXl/FX/U$BZge.lr.ux6ekjEjxmzwz0'), ("des_crypt", 'kAJJz.Rwp0A/I'), ]: if scheme in schemes: self.assertTrue(ctx.verify("test", hash)) #========================================================= #eof #========================================================= passlib-1.5.3/passlib/tests/test_utils.py0000644000175000017500000006742711643466373021710 0ustar biscuitbiscuit00000000000000"""tests for passlib.util""" #========================================================= #imports #========================================================= from __future__ import with_statement #core from binascii import hexlify, unhexlify import sys import random import warnings #site #pkg #module from passlib.context import CryptContext from passlib import utils from passlib.utils import h64, des, Undef, sys_bits, bytes, b, \ native_str, to_bytes, to_unicode, to_native_str, to_hash_str, \ is_same_codec, is_ascii_safe, safe_os_crypt, md4 as md4_mod from passlib.tests.utils import TestCase, Params as ak, \ enable_option, catch_warnings def hb(source): return unhexlify(b(source)) #========================================================= #byte funcs #========================================================= class MiscTest(TestCase): "tests various parts of utils module" #NOTE: could test xor_bytes(), but it's exercised well enough by pbkdf2 test def test_undef(self): "test Undef singleton" self.assertEqual(repr(Undef), "") self.assertFalse(Undef==None,) self.assertFalse(Undef==Undef,) self.assertFalse(Undef==True,) self.assertTrue(Undef!=None,) self.assertTrue(Undef!=Undef,) self.assertTrue(Undef!=True,) def test_getrandbytes(self): "test getrandbytes()" def f(*a,**k): return utils.getrandbytes(utils.rng, *a, **k) self.assertEqual(len(f(0)), 0) a = f(10) b = f(10) self.assertIsInstance(a, bytes) self.assertEqual(len(a), 10) self.assertEqual(len(b), 10) self.assertNotEqual(a, b) def test_getrandstr(self): "test getrandstr()" def f(*a,**k): return utils.getrandstr(utils.rng, *a, **k) #count 0 self.assertEqual(f('abc',0), '') #count <0 self.assertRaises(ValueError, f, 'abc', -1) #letters 0 self.assertRaises(ValueError, f, '', 0) #letters 1 self.assertEqual(f('a',5), 'aaaaa') #letters x = f(u'abc', 16) y = f(u'abc', 16) self.assertIsInstance(x, unicode) self.assertNotEqual(x,y) self.assertEqual(sorted(set(x)), [u'a',u'b',u'c']) #bytes x = f(b('abc'), 16) y = f(b('abc'), 16) self.assertIsInstance(x, bytes) self.assertNotEqual(x,y) #NOTE: decoding this due to py3 bytes self.assertEqual(sorted(set(x.decode("ascii"))), [u'a',u'b',u'c']) #generate_password self.assertEqual(len(utils.generate_password(15)), 15) def test_is_crypt_context(self): "test is_crypt_context()" cc = CryptContext(["des_crypt"]) self.assertTrue(utils.is_crypt_context(cc)) self.assertFalse(not utils.is_crypt_context(cc)) def test_genseed(self): "test genseed()" rng = utils.random.Random(utils.genseed()) a = rng.randint(0, 100000) rng = utils.random.Random(utils.genseed()) b = rng.randint(0, 100000) self.assertNotEqual(a,b) rng.seed(utils.genseed(rng)) def test_safe_os_crypt(self): "test safe_os_crypt() wrapper" if not safe_os_crypt: raise self.skipTest("stdlib crypt module not available") #NOTE: this is assuming EVERY crypt will support des_crypt. # if this fails on some platform, this test will need modifying. #test normal case ok, hash = safe_os_crypt(u'test', u'aa') self.assertTrue(ok) self.assertIsInstance(hash, unicode) self.assertEqual(hash, u'aaqPiZY5xR5l.') #test hash-as-bytes self.assertRaises(TypeError, safe_os_crypt, u'test', b('aa')) #test password as ascii ret = safe_os_crypt(b('test'), u'aa') self.assertEqual(ret, (True, u'aaqPiZY5xR5l.')) #test unicode password w/ high char ret = safe_os_crypt(u'test\u1234', u'aa') self.assertEqual(ret, (True, u'aahWwbrUsKZk.')) #test utf-8 password w/ high char ret = safe_os_crypt(b('test\xe1\x88\xb4'), u'aa') self.assertEqual(ret, (True, u'aahWwbrUsKZk.')) #test latin-1 password ret = safe_os_crypt(b('test\xff'), u'aa') # Py2k # self.assertEqual(ret, (True, u'aaOx.5nbTU/.M')) # Py3k # #self.assertEqual(ret, (False, None)) # end Py3k # #========================================================= #byte/unicode helpers #========================================================= class CodecTest(TestCase): "tests bytes/unicode helpers in passlib.utils" def test_bytes(self): "test b() helper, bytes and native_str types" # Py2k # self.assertIs(bytes, type('')) self.assertIs(native_str, bytes) # Py3k # #self.assertIs(bytes, type(b'')) #self.assertIs(native_str, unicode) # end Py3k # self.assertIsInstance(b(''), bytes) self.assertIsInstance(b('\x00\xff'), bytes) # Py2k # self.assertEqual(b('\x00\xff'), "\x00\xff") # Py3k # #self.assertEqual(b('\x00\xff'), b"\x00\xff") # end Py3k # def test_to_bytes(self): "test to_bytes()" #check unicode inputs self.assertEqual(to_bytes(u'abc'), b('abc')) self.assertEqual(to_bytes(u'\x00\xff'), b('\x00\xc3\xbf')) #check unicode w/ encodings self.assertEqual(to_bytes(u'\x00\xff', 'latin-1'), b('\x00\xff')) self.assertRaises(ValueError, to_bytes, u'\x00\xff', 'ascii') self.assertRaises(TypeError, to_bytes, u'abc', None) #check bytes inputs self.assertEqual(to_bytes(b('abc')), b('abc')) self.assertEqual(to_bytes(b('\x00\xff')), b('\x00\xff')) self.assertEqual(to_bytes(b('\x00\xc3\xbf')), b('\x00\xc3\xbf')) #check byte inputs ignores enocding self.assertEqual(to_bytes(b('\x00\xc3\xbf'), "latin-1"), b('\x00\xc3\xbf')) self.assertEqual(to_bytes(b('\x00\xc3\xbf'), None, "utf-8"), b('\x00\xc3\xbf')) #check bytes transcoding self.assertEqual(to_bytes(b('\x00\xc3\xbf'), "latin-1", "utf-8"), b('\x00\xff')) #check other self.assertRaises(TypeError, to_bytes, None) def test_to_unicode(self): "test to_unicode()" #check unicode inputs self.assertEqual(to_unicode(u'abc'), u'abc') self.assertEqual(to_unicode(u'\x00\xff'), u'\x00\xff') #check unicode input ignores encoding self.assertEqual(to_unicode(u'\x00\xff', None), u'\x00\xff') self.assertEqual(to_unicode(u'\x00\xff', "ascii"), u'\x00\xff') #check bytes input self.assertEqual(to_unicode(b('abc')), u'abc') self.assertEqual(to_unicode(b('\x00\xc3\xbf')), u'\x00\xff') self.assertEqual(to_unicode(b('\x00\xff'), 'latin-1'), u'\x00\xff') self.assertRaises(ValueError, to_unicode, b('\x00\xff')) self.assertRaises(TypeError, to_unicode, b('\x00\xff'), None) #check other self.assertRaises(TypeError, to_unicode, None) def test_to_native_str(self): "test to_native_str()" self.assertEqual(to_native_str(u'abc'), 'abc') self.assertEqual(to_native_str(b('abc')), 'abc') self.assertRaises(TypeError, to_native_str, None) # Py2k # self.assertEqual(to_native_str(u'\x00\xff'), b('\x00\xc3\xbf')) self.assertEqual(to_native_str(b('\x00\xc3\xbf')), b('\x00\xc3\xbf')) self.assertEqual(to_native_str(u'\x00\xff', 'latin-1'), b('\x00\xff')) self.assertEqual(to_native_str(b('\x00\xff'), 'latin-1'), b('\x00\xff')) # Py3k # #self.assertEqual(to_native_str(u'\x00\xff'), '\x00\xff') #self.assertEqual(to_native_str(b('\x00\xc3\xbf')), '\x00\xff') #self.assertEqual(to_native_str(u'\x00\xff', 'latin-1'), # '\x00\xff') #self.assertEqual(to_native_str(b('\x00\xff'), 'latin-1'), # '\x00\xff') # # end Py3k # #TODO: test to_hash_str() def test_is_ascii_safe(self): "test is_ascii_safe()" self.assertTrue(is_ascii_safe(b("\x00abc\x7f"))) self.assertTrue(is_ascii_safe(u"\x00abc\x7f")) self.assertFalse(is_ascii_safe(b("\x00abc\x80"))) self.assertFalse(is_ascii_safe(u"\x00abc\x80")) def test_is_same_codec(self): "test is_same_codec()" self.assertTrue(is_same_codec(None, None)) self.assertFalse(is_same_codec(None, 'ascii')) self.assertTrue(is_same_codec("ascii", "ascii")) self.assertTrue(is_same_codec("ascii", "ASCII")) self.assertTrue(is_same_codec("utf-8", "utf-8")) self.assertTrue(is_same_codec("utf-8", "utf8")) self.assertTrue(is_same_codec("utf-8", "UTF_8")) self.assertFalse(is_same_codec("ascii", "utf-8")) #========================================================= #test des module #========================================================= class DesTest(TestCase): #test vectors taken from http://www.skepticfiles.org/faq/testdes.htm #data is list of (key, plaintext, ciphertext), all as 64 bit hex string test_des_vectors = [ (line[4:20], line[21:37], line[38:54]) for line in b(""" 0000000000000000 0000000000000000 8CA64DE9C1B123A7 FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF 7359B2163E4EDC58 3000000000000000 1000000000000001 958E6E627A05557B 1111111111111111 1111111111111111 F40379AB9E0EC533 0123456789ABCDEF 1111111111111111 17668DFC7292532D 1111111111111111 0123456789ABCDEF 8A5AE1F81AB8F2DD 0000000000000000 0000000000000000 8CA64DE9C1B123A7 FEDCBA9876543210 0123456789ABCDEF ED39D950FA74BCC4 7CA110454A1A6E57 01A1D6D039776742 690F5B0D9A26939B 0131D9619DC1376E 5CD54CA83DEF57DA 7A389D10354BD271 07A1133E4A0B2686 0248D43806F67172 868EBB51CAB4599A 3849674C2602319E 51454B582DDF440A 7178876E01F19B2A 04B915BA43FEB5B6 42FD443059577FA2 AF37FB421F8C4095 0113B970FD34F2CE 059B5E0851CF143A 86A560F10EC6D85B 0170F175468FB5E6 0756D8E0774761D2 0CD3DA020021DC09 43297FAD38E373FE 762514B829BF486A EA676B2CB7DB2B7A 07A7137045DA2A16 3BDD119049372802 DFD64A815CAF1A0F 04689104C2FD3B2F 26955F6835AF609A 5C513C9C4886C088 37D06BB516CB7546 164D5E404F275232 0A2AEEAE3FF4AB77 1F08260D1AC2465E 6B056E18759F5CCA EF1BF03E5DFA575A 584023641ABA6176 004BD6EF09176062 88BF0DB6D70DEE56 025816164629B007 480D39006EE762F2 A1F9915541020B56 49793EBC79B3258F 437540C8698F3CFA 6FBF1CAFCFFD0556 4FB05E1515AB73A7 072D43A077075292 2F22E49BAB7CA1AC 49E95D6D4CA229BF 02FE55778117F12A 5A6B612CC26CCE4A 018310DC409B26D6 1D9D5C5018F728C2 5F4C038ED12B2E41 1C587F1C13924FEF 305532286D6F295A 63FAC0D034D9F793 0101010101010101 0123456789ABCDEF 617B3A0CE8F07100 1F1F1F1F0E0E0E0E 0123456789ABCDEF DB958605F8C8C606 E0FEE0FEF1FEF1FE 0123456789ABCDEF EDBFD1C66C29CCC7 0000000000000000 FFFFFFFFFFFFFFFF 355550B2150E2451 FFFFFFFFFFFFFFFF 0000000000000000 CAAAAF4DEAF1DBAE 0123456789ABCDEF 0000000000000000 D5D44FF720683D0D FEDCBA9876543210 FFFFFFFFFFFFFFFF 2A2BB008DF97C2F2 """).split(b("\n")) if line.strip() ] def test_des_encrypt_block(self): for k,p,c in self.test_des_vectors: k = unhexlify(k) p = unhexlify(p) c = unhexlify(c) result = des.des_encrypt_block(k,p) self.assertEqual(result, c, "key=%r p=%r:" % (k,p)) #test 7 byte key #FIXME: use a better key k,p,c = b('00000000000000'), b('FFFFFFFFFFFFFFFF'), b('355550B2150E2451') k = unhexlify(k) p = unhexlify(p) c = unhexlify(c) result = des.des_encrypt_block(k,p) self.assertEqual(result, c, "key=%r p=%r:" % (k,p)) def test_mdes_encrypt_int_block(self): for k,p,c in self.test_des_vectors: k = int(k,16) p = int(p,16) c = int(c,16) result = des.mdes_encrypt_int_block(k,p, salt=0, rounds=1) self.assertEqual(result, c, "key=%r p=%r:" % (k,p)) #TODO: test other des methods (eg: mdes_encrypt_int_block w/ salt & rounds) # though des-crypt builtin backend test should thump it well enough #========================================================= #hash64 #========================================================= class H64_Test(TestCase): "test H64 codec functions" case_prefix = "H64 codec" #========================================================= #test basic encode/decode #========================================================= encoded_bytes = [ #test lengths 0..6 to ensure tail is encoded properly (b(""),b("")), (b("\x55"),b("J/")), (b("\x55\xaa"),b("Jd8")), (b("\x55\xaa\x55"),b("JdOJ")), (b("\x55\xaa\x55\xaa"),b("JdOJe0")), (b("\x55\xaa\x55\xaa\x55"),b("JdOJeK3")), (b("\x55\xaa\x55\xaa\x55\xaa"),b("JdOJeKZe")), #test padding bits are null (b("\x55\xaa\x55\xaf"),b("JdOJj0")), # len = 1 mod 3 (b("\x55\xaa\x55\xaa\x5f"),b("JdOJey3")), # len = 2 mod 3 ] decode_padding_bytes = [ #len = 2 mod 4 -> 2 msb of last digit is padding (b(".."), b("\x00")), # . = h64.CHARS[0b000000] (b(".0"), b("\x80")), # 0 = h64.CHARS[0b000010] (b(".2"), b("\x00")), # 2 = h64.CHARS[0b000100] (b(".U"), b("\x00")), # U = h64.CHARS[0b100000] #len = 3 mod 4 -> 4 msb of last digit is padding (b("..."), b("\x00\x00")), (b("..6"), b("\x00\x80")), # 6 = h64.CHARS[0b001000] (b("..E"), b("\x00\x00")), # E = h64.CHARS[0b010000] (b("..U"), b("\x00\x00")), ] def test_encode_bytes(self): for source, result in self.encoded_bytes: out = h64.encode_bytes(source) self.assertEqual(out, result) def test_decode_bytes(self): for result, source in self.encoded_bytes: out = h64.decode_bytes(source) self.assertEqual(out, result) #wrong size (1 % 4) self.assertRaises(ValueError, h64.decode_bytes, b('abcde')) self.assertRaises(TypeError, h64.decode_bytes, u'abcd') def test_encode_int(self): self.assertEqual(h64.encode_int(63, 11, True), b('..........z')) self.assertEqual(h64.encode_int(63, 11), b('z..........')) self.assertRaises(ValueError, h64.encode_int64, -1) def test_decode_int(self): self.assertEqual(h64.decode_int64(b('...........')), 0) self.assertRaises(ValueError, h64.decode_int12, b('a?')) self.assertRaises(ValueError, h64.decode_int24, b('aaa?')) self.assertRaises(ValueError, h64.decode_int64, b('aaa?aaa?aaa')) self.assertRaises(ValueError, h64.decode_dc_int64, b('aaa?aaa?aaa')) self.assertRaises(TypeError, h64.decode_int12, u'a'*2) self.assertRaises(TypeError, h64.decode_int24, u'a'*4) self.assertRaises(TypeError, h64.decode_int64, u'a'*11) self.assertRaises(TypeError, h64.decode_dc_int64, u'a'*11) def test_decode_bytes_padding(self): for source, result in self.decode_padding_bytes: out = h64.decode_bytes(source) self.assertEqual(out, result) self.assertRaises(TypeError, h64.decode_bytes, u'..') def test_decode_int6(self): self.assertEqual(h64.decode_int6(b('.')),0) self.assertEqual(h64.decode_int6(b('z')),63) self.assertRaises(ValueError, h64.decode_int6, b('?')) self.assertRaises(TypeError, h64.decode_int6, u'?') def test_encode_int6(self): self.assertEqual(h64.encode_int6(0),b('.')) self.assertEqual(h64.encode_int6(63),b('z')) self.assertRaises(ValueError, h64.encode_int6, -1) self.assertRaises(ValueError, h64.encode_int6, 64) #========================================================= #test transposed encode/decode #========================================================= encode_transposed = [ (b("\x33\x22\x11"), b("\x11\x22\x33"),[2,1,0]), (b("\x22\x33\x11"), b("\x11\x22\x33"),[1,2,0]), ] encode_transposed_dups = [ (b("\x11\x11\x22"), b("\x11\x22\x33"),[0,0,1]), ] def test_encode_transposed_bytes(self): for result, input, offsets in self.encode_transposed + self.encode_transposed_dups: tmp = h64.encode_transposed_bytes(input, offsets) out = h64.decode_bytes(tmp) self.assertEqual(out, result) def test_decode_transposed_bytes(self): for input, result, offsets in self.encode_transposed: tmp = h64.encode_bytes(input) out = h64.decode_transposed_bytes(tmp, offsets) self.assertEqual(out, result) def test_decode_transposed_bytes_bad(self): for input, _, offsets in self.encode_transposed_dups: tmp = h64.encode_bytes(input) self.assertRaises(TypeError, h64.decode_transposed_bytes, tmp, offsets) #========================================================= #TODO: test other h64 methods #========================================================= #========================================================= #test md4 #========================================================= class _MD4_Test(TestCase): #test vectors from http://www.faqs.org/rfcs/rfc1320.html - A.5 hash = None vectors = [ # input -> hex digest (b(""), "31d6cfe0d16ae931b73c59d7e0c089c0"), (b("a"), "bde52cb31de33e46245e05fbdbd6fb24"), (b("abc"), "a448017aaf21d8525fc10ae87aa6729d"), (b("message digest"), "d9130a8164549fe818874806e1c7014b"), (b("abcdefghijklmnopqrstuvwxyz"), "d79e1c308aa5bbcdeea8ed63df412da9"), (b("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"), "043f8582f241db351ce627e153e7f0e4"), (b("12345678901234567890123456789012345678901234567890123456789012345678901234567890"), "e33b4ddc9c38f2199c3e7b164fcc0536"), ] def test_md4_update(self): "test md4 update" md4 = self.hash h = md4(b('')) self.assertEqual(h.hexdigest(), "31d6cfe0d16ae931b73c59d7e0c089c0") #NOTE: under py2, hashlib methods try to encode to ascii, # though shouldn't rely on that. # Py3k # #self.assertRaises(TypeError, h.update, u'x') # end Py3k # h.update(b('a')) self.assertEqual(h.hexdigest(), "bde52cb31de33e46245e05fbdbd6fb24") h.update(b('bcdefghijklmnopqrstuvwxyz')) self.assertEqual(h.hexdigest(), "d79e1c308aa5bbcdeea8ed63df412da9") def test_md4_hexdigest(self): "test md4 hexdigest()" md4 = self.hash for input, hex in self.vectors: out = md4(input).hexdigest() self.assertEqual(out, hex) def test_md4_digest(self): "test md4 digest()" md4 = self.hash for input, hex in self.vectors: out = md4(input).digest() self.assertEqual(to_native_str(hexlify(out)), hex) def test_md4_copy(self): "test md4 copy()" md4 = self.hash h = md4(b('abc')) h2 = h.copy() h2.update(b('def')) self.assertEqual(h2.hexdigest(), '804e7f1c2586e50b49ac65db5b645131') h.update(b('ghi')) self.assertEqual(h.hexdigest(), 'c5225580bfe176f6deeee33dee98732c') # #now do a bunch of things to test multiple possible backends. # has_ssl_md4 = (md4_mod.md4 is not md4_mod._builtin_md4) if has_ssl_md4: class MD4_SSL_Test(_MD4_Test): case_prefix = "MD4 (SSL version)" hash = staticmethod(md4_mod.md4) if not has_ssl_md4 or enable_option("cover"): class MD4_Builtin_Test(_MD4_Test): case_prefix = "MD4 (builtin version)" hash = md4_mod._builtin_md4 #========================================================= #test passlib.utils.pbkdf2 #========================================================= import hashlib import hmac from passlib.utils import pbkdf2 #TODO: should we bother testing hmac_sha1() function? it's verified via sha1_crypt testing. class KdfTest(TestCase): "test kdf helpers" def test_pbkdf1(self): "test pbkdf1" for secret, salt, rounds, klen, hash, correct in [ #http://www.di-mgt.com.au/cryptoKDFs.html (b('password'), hb('78578E5A5D63CB06'), 1000, 16, 'sha1', hb('dc19847e05c64d2faf10ebfb4a3d2a20')), ]: result = pbkdf2.pbkdf1(secret, salt, rounds, klen, hash) self.assertEqual(result, correct) #test rounds < 1 #test klen < 0 #test klen > block size #test invalid hash #NOTE: this is not run directly, but via two subclasses (below) class _Pbkdf2BackendTest(TestCase): "test builtin unix crypt backend" enable_m2crypto = False def setUp(self): #disable m2crypto support so we'll always use software backend if not self.enable_m2crypto: self._orig_EVP = pbkdf2._EVP pbkdf2._EVP = None else: #set flag so tests can check for m2crypto presence quickly self.enable_m2crypto = bool(pbkdf2._EVP) pbkdf2._clear_prf_cache() def tearDown(self): if not self.enable_m2crypto: pbkdf2._EVP = self._orig_EVP pbkdf2._clear_prf_cache() #TODO: test get_prf() behavior in various situations - though overall behavior tested via pbkdf2 def test_rfc3962(self): "rfc3962 test vectors" self.assertFunctionResults(pbkdf2.pbkdf2, [ # result, secret, salt, rounds, keylen, digest="sha1" #test case 1 / 128 bit ( hb("cdedb5281bb2f801565a1122b2563515"), b("password"), b("ATHENA.MIT.EDUraeburn"), 1, 16 ), #test case 2 / 128 bit ( hb("01dbee7f4a9e243e988b62c73cda935d"), b("password"), b("ATHENA.MIT.EDUraeburn"), 2, 16 ), #test case 2 / 256 bit ( hb("01dbee7f4a9e243e988b62c73cda935da05378b93244ec8f48a99e61ad799d86"), b("password"), b("ATHENA.MIT.EDUraeburn"), 2, 32 ), #test case 3 / 256 bit ( hb("5c08eb61fdf71e4e4ec3cf6ba1f5512ba7e52ddbc5e5142f708a31e2e62b1e13"), b("password"), b("ATHENA.MIT.EDUraeburn"), 1200, 32 ), #test case 4 / 256 bit ( hb("d1daa78615f287e6a1c8b120d7062a493f98d203e6be49a6adf4fa574b6e64ee"), b("password"), b('\x12\x34\x56\x78\x78\x56\x34\x12'), 5, 32 ), #test case 5 / 256 bit ( hb("139c30c0966bc32ba55fdbf212530ac9c5ec59f1a452f5cc9ad940fea0598ed1"), b("X"*64), b("pass phrase equals block size"), 1200, 32 ), #test case 6 / 256 bit ( hb("9ccad6d468770cd51b10e6a68721be611a8b4d282601db3b36be9246915ec82a"), b("X"*65), b("pass phrase exceeds block size"), 1200, 32 ), ]) def test_rfc6070(self): "rfc6070 test vectors" self.assertFunctionResults(pbkdf2.pbkdf2, [ ( hb("0c60c80f961f0e71f3a9b524af6012062fe037a6"), b("password"), b("salt"), 1, 20, ), ( hb("ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957"), b("password"), b("salt"), 2, 20, ), ( hb("4b007901b765489abead49d926f721d065a429c1"), b("password"), b("salt"), 4096, 20, ), #just runs too long - could enable if ALL option is set ##( ## ## unhexlify("eefe3d61cd4da4e4e9945b3d6ba2158c2634e984"), ## "password", "salt", 16777216, 20, ##), ( hb("3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038"), b("passwordPASSWORDpassword"), b("saltSALTsaltSALTsaltSALTsaltSALTsalt"), 4096, 25, ), ( hb("56fa6aa75548099dcc37d7f03425e0c3"), b("pass\00word"), b("sa\00lt"), 4096, 16, ), ]) def test_invalid_values(self): #invalid rounds self.assertRaises(ValueError, pbkdf2.pbkdf2, b('password'), b('salt'), -1, 16) self.assertRaises(ValueError, pbkdf2.pbkdf2, b('password'), b('salt'), 0, 16) self.assertRaises(TypeError, pbkdf2.pbkdf2, b('password'), b('salt'), 'x', 16) #invalid keylen self.assertRaises(ValueError, pbkdf2.pbkdf2, b('password'), b('salt'), 1, 20*(2**32-1)+1) #invalid salt type self.assertRaises(TypeError, pbkdf2.pbkdf2, b('password'), 5, 1, 10) #invalid secret type self.assertRaises(TypeError, pbkdf2.pbkdf2, 5, b('salt'), 1, 10) #invalid hash self.assertRaises(ValueError, pbkdf2.pbkdf2, b('password'), b('salt'), 1, 16, 'hmac-foo') self.assertRaises(ValueError, pbkdf2.pbkdf2, b('password'), b('salt'), 1, 16, 'foo') self.assertRaises(TypeError, pbkdf2.pbkdf2, b('password'), b('salt'), 1, 16, 5) def test_hmac_sha1(self): "test independant hmac_sha1() method" self.assertEqual( pbkdf2.hmac_sha1(b("secret"), b("salt")), b('\xfc\xd4\x0c;]\r\x97\xc6\xf1S\x8d\x93\xb9\xeb\xc6\x00\x04.\x8b\xfe') ) def test_sha1_string(self): "test various prf values" self.assertEqual( pbkdf2.pbkdf2(b("secret"), b("salt"), 10, 16, "hmac-sha1"), b('\xe2H\xfbk\x136QF\xf8\xacc\x07\xcc"(\x12') ) def test_sha512_string(self): "test alternate digest string (sha512)" self.assertFunctionResults(pbkdf2.pbkdf2, [ # result, secret, salt, rounds, keylen, digest="sha1" #case taken from example in http://grub.enbug.org/Authentication ( hb("887CFF169EA8335235D8004242AA7D6187A41E3187DF0CE14E256D85ED97A97357AAA8FF0A3871AB9EEFF458392F462F495487387F685B7472FC6C29E293F0A0"), b("hello"), hb("9290F727ED06C38BA4549EF7DE25CF5642659211B7FC076F2D28FEFD71784BB8D8F6FB244A8CC5C06240631B97008565A120764C0EE9C2CB0073994D79080136"), 10000, 64, "hmac-sha512" ), ]) def test_sha512_function(self): "test custom digest function" def prf(key, msg): return hmac.new(key, msg, hashlib.sha512).digest() self.assertFunctionResults(pbkdf2.pbkdf2, [ # result, secret, salt, rounds, keylen, digest="sha1" #case taken from example in http://grub.enbug.org/Authentication ( hb("887CFF169EA8335235D8004242AA7D6187A41E3187DF0CE14E256D85ED97A97357AAA8FF0A3871AB9EEFF458392F462F495487387F685B7472FC6C29E293F0A0"), b("hello"), hb("9290F727ED06C38BA4549EF7DE25CF5642659211B7FC076F2D28FEFD71784BB8D8F6FB244A8CC5C06240631B97008565A120764C0EE9C2CB0073994D79080136"), 10000, 64, prf, ), ]) has_m2crypto = (pbkdf2._EVP is not None) if has_m2crypto: class Pbkdf2_M2Crypto_Test(_Pbkdf2BackendTest): case_prefix = "pbkdf2 (m2crypto backend)" enable_m2crypto = True if not has_m2crypto or enable_option("cover"): class Pbkdf2_Builtin_Test(_Pbkdf2BackendTest): case_prefix = "pbkdf2 (builtin backend)" enable_m2crypto = False #========================================================= #EOF #========================================================= passlib-1.5.3/passlib/tests/test_registry.py0000644000175000017500000001374611643466373022413 0ustar biscuitbiscuit00000000000000"""tests for passlib.pwhash -- (c) Assurance Technologies 2003-2009""" #========================================================= #imports #========================================================= from __future__ import with_statement #core import hashlib from logging import getLogger import os import time import warnings import sys #site #pkg from passlib import hash, registry from passlib.registry import register_crypt_handler, register_crypt_handler_path, \ get_crypt_handler, list_crypt_handlers, _unload_handler_name as unload_handler_name import passlib.utils.handlers as uh from passlib.tests.utils import TestCase, mktemp, catch_warnings #module log = getLogger(__name__) #========================================================= #dummy handlers # #NOTE: these are defined outside of test case # since they're used by test_register_crypt_handler_path(), # which needs them to be available as module globals. #========================================================= class dummy_0(uh.StaticHandler): name = "dummy_0" class alt_dummy_0(uh.StaticHandler): name = "dummy_0" dummy_x = 1 #========================================================= #test registry #========================================================= class RegistryTest(TestCase): case_prefix = "passlib registry" def tearDown(self): for name in ("dummy_0", "dummy_1", "dummy_x", "dummy_bad"): unload_handler_name(name) def test_hash_proxy(self): "test passlib.hash proxy object" #check dir works dir(hash) #check repr works repr(hash) #check non-existent attrs raise error self.assertRaises(AttributeError, getattr, hash, 'fooey') #GAE tries to set __loader__, #make sure that doesn't call register_crypt_handler. old = getattr(hash, "__loader__", None) test = object() hash.__loader__ = test self.assertIs(hash.__loader__, test) if old is None: del hash.__loader__ self.assertFalse(hasattr(hash, "__loader__")) else: hash.__loader__ = old self.assertIs(hash.__loader__, old) #check storing attr calls register_crypt_handler class dummy_1(uh.StaticHandler): name = "dummy_1" hash.dummy_1 = dummy_1 self.assertIs(get_crypt_handler("dummy_1"), dummy_1) #check storing under wrong name results in error self.assertRaises(ValueError, setattr, hash, "dummy_1x", dummy_1) def test_register_crypt_handler_path(self): "test register_crypt_handler_path()" #NOTE: this messes w/ internals of registry, shouldn't be used publically. paths = registry._handler_locations #check namespace is clear self.assertTrue('dummy_0' not in paths) self.assertFalse(hasattr(hash, 'dummy_0')) #try lazy load register_crypt_handler_path('dummy_0', __name__) self.assertTrue('dummy_0' in list_crypt_handlers()) self.assertTrue('dummy_0' not in list_crypt_handlers(loaded_only=True)) self.assertIs(hash.dummy_0, dummy_0) self.assertTrue('dummy_0' in list_crypt_handlers(loaded_only=True)) unload_handler_name('dummy_0') #try lazy load w/ alt register_crypt_handler_path('dummy_0', __name__ + ':alt_dummy_0') self.assertIs(hash.dummy_0, alt_dummy_0) unload_handler_name('dummy_0') #check lazy load w/ wrong type fails register_crypt_handler_path('dummy_x', __name__) self.assertRaises(TypeError, get_crypt_handler, 'dummy_x') #check lazy load w/ wrong name fails register_crypt_handler_path('alt_dummy_0', __name__) self.assertRaises(ValueError, get_crypt_handler, "alt_dummy_0") #TODO: check lazy load which calls register_crypt_handler (warning should be issued) sys.modules.pop("passlib.tests._test_bad_register", None) register_crypt_handler_path("dummy_bad", "passlib.tests._test_bad_register") with catch_warnings(): warnings.filterwarnings("ignore", "xxxxxxxxxx", DeprecationWarning) h = get_crypt_handler("dummy_bad") from passlib.tests import _test_bad_register as tbr self.assertIs(h, tbr.alt_dummy_bad) def test_register_crypt_handler(self): "test register_crypt_handler()" self.assertRaises(TypeError, register_crypt_handler, {}) self.assertRaises(ValueError, register_crypt_handler, uh.StaticHandler) self.assertRaises(ValueError, register_crypt_handler, type('x', (uh.StaticHandler,), dict(name="AB_CD"))) self.assertRaises(ValueError, register_crypt_handler, type('x', (uh.StaticHandler,), dict(name="ab-cd"))) class dummy_1(uh.StaticHandler): name = "dummy_1" class dummy_1b(uh.StaticHandler): name = "dummy_1" self.assertTrue('dummy_1' not in list_crypt_handlers()) register_crypt_handler(dummy_1) register_crypt_handler(dummy_1) self.assertIs(get_crypt_handler("dummy_1"), dummy_1) self.assertRaises(KeyError, register_crypt_handler, dummy_1b) self.assertIs(get_crypt_handler("dummy_1"), dummy_1) register_crypt_handler(dummy_1b, force=True) self.assertIs(get_crypt_handler("dummy_1"), dummy_1b) self.assertTrue('dummy_1' in list_crypt_handlers()) def test_get_crypt_handler(self): "test get_crypt_handler()" class dummy_1(uh.StaticHandler): name = "dummy_1" self.assertRaises(KeyError, get_crypt_handler, "dummy_1") register_crypt_handler(dummy_1) self.assertIs(get_crypt_handler("dummy_1"), dummy_1) with catch_warnings(): warnings.filterwarnings("ignore", "handler names should be lower-case, and use underscores instead of hyphens:.*", UserWarning) self.assertIs(get_crypt_handler("DUMMY-1"), dummy_1) #========================================================= #EOF #========================================================= passlib-1.5.3/passlib/tests/test_apache.py0000644000175000017500000003527611643466373021766 0ustar biscuitbiscuit00000000000000"""tests for passlib.apache -- (c) Assurance Technologies 2008-2011""" #========================================================= #imports #========================================================= from __future__ import with_statement #core import hashlib from logging import getLogger import os import time #site #pkg from passlib import apache from passlib.utils import b, native_str, bytes from passlib.tests.utils import TestCase, mktemp, gae_env, get_file, set_file #module log = getLogger(__name__) def backdate_file_mtime(path, offset=10): "backdate file's mtime by specified amount" #NOTE: this is used so we can test code which detects mtime changes, # without having to actually *pause* for that long. atime = os.path.getatime(path) mtime = os.path.getmtime(path)-offset os.utime(path, (atime, mtime)) #========================================================= #htpasswd #========================================================= class HtpasswdFileTest(TestCase): "test HtpasswdFile class" case_prefix = "HtpasswdFile" sample_01 = b('user2:2CHkkwa2AtqGs\nuser3:{SHA}3ipNV1GrBtxPmHFC21fCbVCSXIo=\nuser4:pass4\nuser1:$apr1$t4tc7jTh$GPIWVUo8sQKJlUdV8V5vu0\n') sample_02 = b('user3:{SHA}3ipNV1GrBtxPmHFC21fCbVCSXIo=\nuser4:pass4\n') sample_03 = b('user2:pass2x\nuser3:{SHA}3ipNV1GrBtxPmHFC21fCbVCSXIo=\nuser4:pass4\nuser1:$apr1$t4tc7jTh$GPIWVUo8sQKJlUdV8V5vu0\nuser5:pass5\n') sample_04_utf8 = b('user\xc3\xa6:2CHkkwa2AtqGs\n') sample_04_latin1 = b('user\xe6:2CHkkwa2AtqGs\n') sample_dup = b('user1:pass1\nuser1:pass2\n') def test_00_constructor_autoload(self): "test constructor autoload" if gae_env: return self.skipTest("GAE doesn't offer read/write filesystem access") #check with existing file path = mktemp() set_file(path, self.sample_01) ht = apache.HtpasswdFile(path) self.assertEqual(ht.to_string(), self.sample_01) #check autoload=False ht = apache.HtpasswdFile(path, autoload=False) self.assertEqual(ht.to_string(), b("")) #check missing file os.remove(path) self.assertRaises(IOError, apache.HtpasswdFile, path) #NOTE: "default" option checked via update() test, among others def test_01_delete(self): "test delete()" ht = apache.HtpasswdFile._from_string(self.sample_01) self.assertTrue(ht.delete("user1")) self.assertTrue(ht.delete("user2")) self.assertTrue(not ht.delete("user5")) self.assertEqual(ht.to_string(), self.sample_02) self.assertRaises(ValueError, ht.delete, "user:") def test_02_update(self): "test update()" ht = apache.HtpasswdFile._from_string( self.sample_01, default="plaintext") self.assertTrue(ht.update("user2", "pass2x")) self.assertTrue(not ht.update("user5", "pass5")) self.assertEqual(ht.to_string(), self.sample_03) self.assertRaises(ValueError, ht.update, "user:", "pass") def test_03_users(self): "test users()" ht = apache.HtpasswdFile._from_string(self.sample_01) ht.update("user5", "pass5") ht.delete("user3") ht.update("user3", "pass3") self.assertEqual(ht.users(), ["user2", "user4", "user1", "user5", "user3"]) def test_04_verify(self): "test verify()" ht = apache.HtpasswdFile._from_string(self.sample_01) self.assertTrue(ht.verify("user5","pass5") is None) for i in xrange(1,5): i = str(i) self.assertTrue(ht.verify("user"+i, "pass"+i)) self.assertTrue(ht.verify("user"+i, "pass5") is False) self.assertRaises(ValueError, ht.verify, "user:", "pass") def test_05_load(self): "test load()" if gae_env: return self.skipTest("GAE doesn't offer read/write filesystem access") #setup empty file path = mktemp() set_file(path, "") backdate_file_mtime(path, 5) ha = apache.HtpasswdFile(path, default="plaintext") self.assertEqual(ha.to_string(), b("")) #make changes, check force=False does nothing ha.update("user1", "pass1") ha.load(force=False) self.assertEqual(ha.to_string(), b("user1:pass1\n")) #change file set_file(path, self.sample_01) ha.load(force=False) self.assertEqual(ha.to_string(), self.sample_01) #make changes, check force=True overwrites them ha.update("user5", "pass5") ha.load() self.assertEqual(ha.to_string(), self.sample_01) #test load w/ no path hb = apache.HtpasswdFile() self.assertRaises(RuntimeError, hb.load) self.assertRaises(RuntimeError, hb.load, force=False) #test load w/ dups set_file(path, self.sample_dup) hc = apache.HtpasswdFile(path) self.assertTrue(hc.verify('user1','pass1')) def test_06_save(self): "test save()" if gae_env: return self.skipTest("GAE doesn't offer read/write filesystem access") #load from file path = mktemp() set_file(path, self.sample_01) ht = apache.HtpasswdFile(path) #make changes, check they saved ht.delete("user1") ht.delete("user2") ht.save() self.assertEqual(get_file(path), self.sample_02) #test save w/ no path hb = apache.HtpasswdFile() hb.update("user1", "pass1") self.assertRaises(RuntimeError, hb.save) def test_07_encodings(self): "test encoding parameter behavior" #test bad encodings cause failure in constructor self.assertRaises(ValueError, apache.HtpasswdFile, encoding="utf-16") #check users() returns native string by default ht = apache.HtpasswdFile._from_string(self.sample_01) self.assertIsInstance(ht.users()[0], native_str) #check returns unicode if encoding explicitly set ht = apache.HtpasswdFile._from_string(self.sample_01, encoding="utf-8") self.assertIsInstance(ht.users()[0], unicode) #check returns bytes if encoding explicitly disabled ht = apache.HtpasswdFile._from_string(self.sample_01, encoding=None) self.assertIsInstance(ht.users()[0], bytes) #check sample utf-8 ht = apache.HtpasswdFile._from_string(self.sample_04_utf8, encoding="utf-8") self.assertEqual(ht.users(), [ u"user\u00e6" ]) #check sample latin-1 ht = apache.HtpasswdFile._from_string(self.sample_04_latin1, encoding="latin-1") self.assertEqual(ht.users(), [ u"user\u00e6" ]) def test_08_to_string(self): "test to_string" #check with known sample ht = apache.HtpasswdFile._from_string(self.sample_01) self.assertEqual(ht.to_string(), self.sample_01) #test blank ht = apache.HtpasswdFile() self.assertEqual(ht.to_string(), b("")) #========================================================= #eoc #========================================================= #========================================================= #htdigest #========================================================= class HtdigestFileTest(TestCase): "test HtdigestFile class" case_prefix = "HtdigestFile" sample_01 = b('user2:realm:549d2a5f4659ab39a80dac99e159ab19\nuser3:realm:a500bb8c02f6a9170ae46af10c898744\nuser4:realm:ab7b5d5f28ccc7666315f508c7358519\nuser1:realm:2a6cf53e7d8f8cf39d946dc880b14128\n') sample_02 = b('user3:realm:a500bb8c02f6a9170ae46af10c898744\nuser4:realm:ab7b5d5f28ccc7666315f508c7358519\n') sample_03 = b('user2:realm:5ba6d8328943c23c64b50f8b29566059\nuser3:realm:a500bb8c02f6a9170ae46af10c898744\nuser4:realm:ab7b5d5f28ccc7666315f508c7358519\nuser1:realm:2a6cf53e7d8f8cf39d946dc880b14128\nuser5:realm:03c55fdc6bf71552356ad401bdb9af19\n') sample_04_utf8 = b('user\xc3\xa6:realm\xc3\xa6:549d2a5f4659ab39a80dac99e159ab19\n') sample_04_latin1 = b('user\xe6:realm\xe6:549d2a5f4659ab39a80dac99e159ab19\n') def test_00_constructor_autoload(self): "test constructor autoload" if gae_env: return self.skipTest("GAE doesn't offer read/write filesystem access") #check with existing file path = mktemp() set_file(path, self.sample_01) ht = apache.HtdigestFile(path) self.assertEqual(ht.to_string(), self.sample_01) #check autoload=False ht = apache.HtdigestFile(path, autoload=False) self.assertEqual(ht.to_string(), b("")) #check missing file os.remove(path) self.assertRaises(IOError, apache.HtdigestFile, path) def test_01_delete(self): "test delete()" ht = apache.HtdigestFile._from_string(self.sample_01) self.assertTrue(ht.delete("user1", "realm")) self.assertTrue(ht.delete("user2", "realm")) self.assertTrue(not ht.delete("user5", "realm")) self.assertEqual(ht.to_string(), self.sample_02) self.assertRaises(ValueError, ht.delete, "user:", "realm") def test_02_update(self): "test update()" ht = apache.HtdigestFile._from_string(self.sample_01) self.assertTrue(ht.update("user2", "realm", "pass2x")) self.assertTrue(not ht.update("user5", "realm", "pass5")) self.assertEqual(ht.to_string(), self.sample_03) self.assertRaises(ValueError, ht.update, "user:", "realm", "pass") self.assertRaises(ValueError, ht.update, "u"*256, "realm", "pass") self.assertRaises(ValueError, ht.update, "user", "realm:", "pass") self.assertRaises(ValueError, ht.update, "user", "r"*256, "pass") def test_03_users(self): "test users()" ht = apache.HtdigestFile._from_string(self.sample_01) ht.update("user5", "realm", "pass5") ht.delete("user3", "realm") ht.update("user3", "realm", "pass3") self.assertEqual(ht.users("realm"), ["user2", "user4", "user1", "user5", "user3"]) def test_04_verify(self): "test verify()" ht = apache.HtdigestFile._from_string(self.sample_01) self.assertTrue(ht.verify("user5", "realm","pass5") is None) for i in xrange(1,5): i = str(i) self.assertTrue(ht.verify("user"+i, "realm", "pass"+i)) self.assertTrue(ht.verify("user"+i, "realm", "pass5") is False) self.assertRaises(ValueError, ht.verify, "user:", "realm", "pass") def test_05_load(self): "test load()" if gae_env: return self.skipTest("GAE doesn't offer read/write filesystem access") #setup empty file path = mktemp() set_file(path, "") backdate_file_mtime(path, 5) ha = apache.HtdigestFile(path) self.assertEqual(ha.to_string(), b("")) #make changes, check force=False does nothing ha.update("user1", "realm", "pass1") ha.load(force=False) self.assertEqual(ha.to_string(), b('user1:realm:2a6cf53e7d8f8cf39d946dc880b14128\n')) #change file set_file(path, self.sample_01) ha.load(force=False) self.assertEqual(ha.to_string(), self.sample_01) #make changes, check force=True overwrites them ha.update("user5", "realm", "pass5") ha.load() self.assertEqual(ha.to_string(), self.sample_01) #test load w/ no path hb = apache.HtdigestFile() self.assertRaises(RuntimeError, hb.load) self.assertRaises(RuntimeError, hb.load, force=False) def test_06_save(self): "test save()" if gae_env: return self.skipTest("GAE doesn't offer read/write filesystem access") #load from file path = mktemp() set_file(path, self.sample_01) ht = apache.HtdigestFile(path) #make changes, check they saved ht.delete("user1", "realm") ht.delete("user2", "realm") ht.save() self.assertEqual(get_file(path), self.sample_02) #test save w/ no path hb = apache.HtdigestFile() hb.update("user1", "realm", "pass1") self.assertRaises(RuntimeError, hb.save) def test_07_realms(self): "test realms() & delete_realm()" ht = apache.HtdigestFile._from_string(self.sample_01) self.assertEqual(ht.delete_realm("x"), 0) self.assertEqual(ht.realms(), ['realm']) self.assertEqual(ht.delete_realm("realm"), 4) self.assertEqual(ht.realms(), []) self.assertEqual(ht.to_string(), b("")) def test_08_find(self): "test find()" ht = apache.HtdigestFile._from_string(self.sample_01) self.assertEqual(ht.find("user3", "realm"), "a500bb8c02f6a9170ae46af10c898744") self.assertEqual(ht.find("user4", "realm"), "ab7b5d5f28ccc7666315f508c7358519") self.assertEqual(ht.find("user5", "realm"), None) def test_09_encodings(self): "test encoding parameter" #test bad encodings cause failure in constructor self.assertRaises(ValueError, apache.HtdigestFile, encoding="utf-16") #check users() returns native string by default ht = apache.HtdigestFile._from_string(self.sample_01) self.assertIsInstance(ht.realms()[0], native_str) self.assertIsInstance(ht.users("realm")[0], native_str) #check returns unicode if encoding explicitly set ht = apache.HtdigestFile._from_string(self.sample_01, encoding="utf-8") self.assertIsInstance(ht.realms()[0], unicode) self.assertIsInstance(ht.users(u"realm")[0], unicode) #check returns bytes if encoding explicitly disabled ht = apache.HtdigestFile._from_string(self.sample_01, encoding=None) self.assertIsInstance(ht.realms()[0], bytes) self.assertIsInstance(ht.users(b("realm"))[0], bytes) #check sample utf-8 ht = apache.HtdigestFile._from_string(self.sample_04_utf8, encoding="utf-8") self.assertEqual(ht.realms(), [ u"realm\u00e6" ]) self.assertEqual(ht.users(u"realm\u00e6"), [ u"user\u00e6" ]) #check sample latin-1 ht = apache.HtdigestFile._from_string(self.sample_04_latin1, encoding="latin-1") self.assertEqual(ht.realms(), [ u"realm\u00e6" ]) self.assertEqual(ht.users(u"realm\u00e6"), [ u"user\u00e6" ]) def test_10_to_string(self): "test to_string()" #check sample ht = apache.HtdigestFile._from_string(self.sample_01) self.assertEqual(ht.to_string(), self.sample_01) #check blank ht = apache.HtdigestFile() self.assertEqual(ht.to_string(), b("")) #========================================================= #eoc #========================================================= #========================================================= #EOF #========================================================= passlib-1.5.3/passlib/tests/test_drivers.py0000644000175000017500000014131411643754032022202 0ustar biscuitbiscuit00000000000000"""tests for passlib.pwhash -- (c) Assurance Technologies 2003-2009""" #========================================================= #imports #========================================================= from __future__ import with_statement #core import hashlib import logging; log = logging.getLogger(__name__) import warnings #site #pkg from passlib import hash from passlib.tests.utils import TestCase, HandlerCase, create_backend_case, \ enable_option, b, catch_warnings #module #========================================================= #some #========================================================= #some common unicode passwords which used as test cases... UPASS_WAV = u'\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2' UPASS_USD = u"\u20AC\u00A5$" UPASS_TABLE = u"t\u00e1\u0411\u2113\u0259" #========================================================= #apr md5 crypt #========================================================= from passlib.handlers.md5_crypt import apr_md5_crypt class AprMd5CryptTest(HandlerCase): handler = apr_md5_crypt #values taken from http://httpd.apache.org/docs/2.2/misc/password_encryptions.html known_correct_hashes = [ ('myPassword', '$apr1$r31.....$HqJZimcKQFAMYayBlzkrA/'), ] known_malformed_hashes = [ #bad char in otherwise correct hash '$apr1$r31.....$HqJZimcKQFAMYayBlzkrA!' ] #========================================================= #bcrypt #========================================================= class _BCryptTest(HandlerCase): "base for BCrypt test cases" handler = hash.bcrypt secret_chars = 72 known_correct_hashes = [ #selected bcrypt test vectors ('', '$2a$06$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s.'), ('a', '$2a$10$k87L/MF28Q673VKh8/cPi.SUl7MU/rWuSiIDDFayrKk/1tBsSQu4u'), ('abc', '$2a$10$WvvTPHKwdBJ3uk0Z37EMR.hLA2W6N9AEBhEgrAOljy2Ae5MtaSIUi'), ('abcdefghijklmnopqrstuvwxyz', '$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq'), ('~!@#$%^&*() ~!@#$%^&*()PNBFRD', '$2a$10$LgfYWkbzEvQ4JakH7rOvHe0y8pHKF9OaFgwUZ2q7W2FFZmZzJYlfS'), ] known_unidentified_hashes = [ #unsupported minor version "$2b$12$EXRkfkdmXn!gzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q", ] known_malformed_hashes = [ #bad char in otherwise correct hash "$2a$12$EXRkfkdmXn!gzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q", #rounds not zero-padded (pybcrypt rejects this, therefore so do we) '$2a$6$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s.' #NOTE: salts with padding bits set are technically malformed, # but that's one we can reliably correct & issue warning for. ] #=============================================================== # extra tests #=============================================================== def iter_external_verifiers(self): try: from bcrypt import hashpw except ImportError: pass else: def check_pybcrypt(secret, hash): self.assertEqual(hashpw(secret, hash), hash, "pybcrypt: bcrypt.hashpw(%r,%r):" % (secret, hash)) yield check_pybcrypt try: from bcryptor.engine import Engine except ImportError: pass else: def check_bcryptor(secret, hash): result = Engine(False).hash_key(secret, hash) self.assertEqual(result, hash, "bcryptor: hash_key(%r,%r):" % (secret, hash)) yield check_bcryptor def test_90_idents(self): "test identifier validation" handler = self.handler kwds = dict(checksum='8CIhhFCj15KqqFvo/n.Jatx8dJ92f82', salt='VlsfIX9.apXuQBr6tego0.', rounds=12, ident="2a", strict=True) handler(**kwds) kwds['ident'] = None self.assertRaises(ValueError, handler, **kwds) del kwds['strict'] kwds['ident'] = 'Q' self.assertRaises(ValueError, handler, **kwds) #=============================================================== # see issue 25 - https://code.google.com/p/passlib/issues/detail?id=25 # bcrypt's salt ends with 4 padding bits. # openbsd, pybcrypt, etc assume these bits are always 0. # passlib <= 1.5.2 generated salts where this wasn't usually the case. # as of 1.5.3, we want to always generate salts w/ 0 padding, # and clear the padding of any incoming hashes #=============================================================== def do_genconfig(self, **kwds): # correct provided salts to handle ending correctly, # so test_33_genconfig_saltchars doesn't throw warnings. if 'salt' in kwds: from passlib.handlers.bcrypt import BCHARS, BSLAST salt = kwds['salt'] if salt and salt[-1] not in BSLAST: salt = salt[:-1] + BCHARS[BCHARS.index(salt[-1])&~15] kwds['salt'] = salt return self.handler.genconfig(**kwds) def test_91_bcrypt_padding(self): "test passlib correctly handles bcrypt padding bits" bcrypt = self.handler def check_warning(wlog): self.assertWarningMatches(wlog.pop(0), message_re="^encountered a bcrypt hash with incorrectly set padding bits.*", ) self.assertFalse(wlog) def check_padding(hash): "check bcrypt hash doesn't have salt padding bits set" assert hash.startswith("$2a$") and len(hash) >= 28 self.assertTrue(hash[28] in BSLAST, "padding bits set in hash: %r" % (hash,)) #=============================================================== # test generated salts #=============================================================== from passlib.handlers.bcrypt import BCHARS, BSLAST # make sure genconfig & encrypt don't return bad hashes. # bug had 15/16 chance of occurring every time salt generated. # so we call it a few different way a number of times. for i in xrange(6): check_padding(bcrypt.genconfig()) for i in xrange(3): check_padding(bcrypt.encrypt("bob", rounds=bcrypt.min_rounds)) # check passing salt to genconfig causes it to be normalized. with catch_warnings(record=True) as wlog: warnings.simplefilter("always") hash = bcrypt.genconfig(salt="."*21 + "A.") check_warning(wlog) self.assertEqual(hash, "$2a$12$" + "." * 22) hash = bcrypt.genconfig(salt="."*23) self.assertFalse(wlog) self.assertEqual(hash, "$2a$12$" + "." * 22) #=============================================================== # test handling existing hashes #=============================================================== # 2 bits of salt padding set PASS1 = "loppux" BAD1 = "$2a$12$oaQbBqq8JnSM1NHRPQGXORm4GCUMqp7meTnkft4zgSnrbhoKdDV0C" GOOD1 = "$2a$12$oaQbBqq8JnSM1NHRPQGXOOm4GCUMqp7meTnkft4zgSnrbhoKdDV0C" # all 4 bits of salt padding set PASS2 = "Passlib11" BAD2 = "$2a$12$M8mKpW9a2vZ7PYhq/8eJVcUtKxpo6j0zAezu0G/HAMYgMkhPu4fLK" GOOD2 = "$2a$12$M8mKpW9a2vZ7PYhq/8eJVOUtKxpo6j0zAezu0G/HAMYgMkhPu4fLK" # bad checksum padding PASS3 = "test" BAD3 = "$2a$04$yjDgE74RJkeqC0/1NheSSOrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIV" GOOD3 = "$2a$04$yjDgE74RJkeqC0/1NheSSOrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIS" # make sure genhash() corrects input with catch_warnings(record=True) as wlog: warnings.simplefilter("always") self.assertEqual(bcrypt.genhash(PASS1, BAD1), GOOD1) check_warning(wlog) self.assertEqual(bcrypt.genhash(PASS2, BAD2), GOOD2) check_warning(wlog) self.assertEqual(bcrypt.genhash(PASS2, GOOD2), GOOD2) self.assertFalse(wlog) self.assertEqual(bcrypt.genhash(PASS3, BAD3), GOOD3) check_warning(wlog) self.assertFalse(wlog) # make sure verify works on both bad and good hashes with catch_warnings(record=True) as wlog: warnings.simplefilter("always") self.assertTrue(bcrypt.verify(PASS1, BAD1)) check_warning(wlog) self.assertTrue(bcrypt.verify(PASS1, GOOD1)) self.assertFalse(wlog) #=============================================================== # test normhash cleans things up correctly #=============================================================== with catch_warnings(record=True) as wlog: warnings.simplefilter("always") self.assertEqual(bcrypt.normhash(BAD1), GOOD1) self.assertEqual(bcrypt.normhash(BAD2), GOOD2) self.assertEqual(bcrypt.normhash(GOOD1), GOOD1) self.assertEqual(bcrypt.normhash(GOOD2), GOOD2) self.assertEqual(bcrypt.normhash("$md5$abc"), "$md5$abc") hash.bcrypt._no_backends_msg() #call this for coverage purposes #create test cases for specific backends Pybcrypt_BCryptTest = create_backend_case(_BCryptTest, "pybcrypt") Bcryptor_BCryptTest = create_backend_case(_BCryptTest, "bcryptor") OsCrypt_BCryptTest = create_backend_case(_BCryptTest, "os_crypt") #========================================================= #bigcrypt #========================================================= from passlib.handlers.des_crypt import bigcrypt class BigCryptTest(HandlerCase): handler = bigcrypt #TODO: find an authortative source of test vectors, #these were found in docs and messages on the web. known_correct_hashes = [ ("passphrase", "qiyh4XPJGsOZ2MEAyLkfWqeQ"), ("This is very long passwd", "f8.SVpL2fvwjkAnxn8/rgTkwvrif6bjYB5c"), ] known_unidentified_hashes = [ #one char short "qiyh4XPJGsOZ2MEAyLkfWqe" ] #omit des_crypt from known other, it looks like bigcrypt known_other_hashes = [row for row in HandlerCase.known_other_hashes if row[0] != "des_crypt"] #========================================================= #bsdi crypt #========================================================= class _BSDiCryptTest(HandlerCase): "test BSDiCrypt algorithm" handler = hash.bsdi_crypt known_correct_hashes = [ (" ", "_K1..crsmZxOLzfJH8iw"), ("my", '_KR/.crsmykRplHbAvwA'), #<- to detect old 12-bit rounds bug ("my socra", "_K1..crsmf/9NzZr1fLM"), ("my socrates", '_K1..crsmOv1rbde9A9o'), ("my socrates note", "_K1..crsm/2qeAhdISMA"), ] known_unidentified_hashes = [ #bad char in otherwise correctly formatted hash "_K1.!crsmZxOLzfJH8iw" ] OsCrypt_BSDiCryptTest = create_backend_case(_BSDiCryptTest, "os_crypt") Builtin_BSDiCryptTest = create_backend_case(_BSDiCryptTest, "builtin") #========================================================= #crypt16 #========================================================= from passlib.handlers.des_crypt import crypt16 class Crypt16Test(HandlerCase): handler = crypt16 secret_chars = 16 #TODO: find an authortative source of test vectors #instead of just msgs around the web # (eg http://seclists.org/bugtraq/1999/Mar/76) known_correct_hashes = [ ("passphrase", "qi8H8R7OM4xMUNMPuRAZxlY."), ("printf", "aaCjFz4Sh8Eg2QSqAReePlq6"), ("printf", "AA/xje2RyeiSU0iBY3PDwjYo"), ("LOLOAQICI82QB4IP", "/.FcK3mad6JwYt8LVmDqz9Lc"), ("LOLOAQICI", "/.FcK3mad6JwYSaRHJoTPzY2"), ("LOLOAQIC", "/.FcK3mad6JwYelhbtlysKy6"), ("L", "/.CIu/PzYCkl6elhbtlysKy6"), ] #========================================================= #des crypt #========================================================= from passlib.handlers.des_crypt import des_crypt class _DesCryptTest(HandlerCase): "test des-crypt algorithm" handler = des_crypt secret_chars = 8 known_correct_hashes = [ #secret, example hash which matches secret ('', 'OgAwTx2l6NADI'), (' ', '/Hk.VPuwQTXbc'), ('test', 'N1tQbOFcM5fpg'), ('Compl3X AlphaNu3meric', 'um.Wguz3eVCx2'), ('4lpHa N|_|M3r1K W/ Cur5Es: #$%(*)(*%#', 'sNYqfOyauIyic'), ('AlOtBsOl', 'cEpWz5IUCShqM'), (u'hell\u00D6', 'saykDgk3BPZ9E'), ] known_unidentified_hashes = [ #bad char in otherwise correctly formatted hash '!gAwTx2l6NADI', ] def test_invalid_secret_chars(self): self.assertRaises(ValueError, self.do_encrypt, 'sec\x00t') OsCrypt_DesCryptTest = create_backend_case(_DesCryptTest, "os_crypt") Builtin_DesCryptTest = create_backend_case(_DesCryptTest, "builtin") #========================================================= #django #========================================================= class _DjangoHelper(object): def test_django_reference(self): "run known correct hashes through Django's check_password()" if not self.known_correct_hashes: return self.skipTest("no known correct hashes specified") from passlib.tests.test_ext_django import has_django1 if not has_django1: return self.skipTest("Django not installed") from django.contrib.auth.models import check_password for secret, hash in self.all_correct_hashes: self.assertTrue(check_password(secret, hash)) self.assertFalse(check_password('x' + secret, hash)) class DjangoDisabledTest(HandlerCase): "test django_disabled" #NOTE: this class behaves VERY differently from a normal password hash, #so we subclass & disable a number of the default tests. #TODO: combine these features w/ unix_fallback and other disabled handlers. handler = hash.django_disabled handler_type = "disabled" def test_20_verify_positive(self): for secret, result in [ ("password", "!"), ("", "!"), ]: self.assertFalse(self.do_verify(secret, result)) def test_50_encrypt_plain(self): "test encrypt() basic behavior" secret = UPASS_USD result = self.do_encrypt(secret) self.assertEqual(result, "!") self.assertTrue(not self.do_verify(secret, result)) class DjangoDesCryptTest(HandlerCase, _DjangoHelper): "test django_des_crypt" handler = hash.django_des_crypt secret_chars = 8 known_correct_hashes = [ #ensures only first two digits of salt count. ("password", 'crypt$c2$c2M87q...WWcU'), ("password", 'crypt$c2e86$c2M87q...WWcU'), ("passwordignoreme", 'crypt$c2.AZ$c2M87q...WWcU'), #ensures utf-8 used for unicode (UPASS_USD, 'crypt$c2e86$c2hN1Bxd6ZiWs'), (UPASS_TABLE, 'crypt$0.aQs$0.wB.TT0Czvlo'), (u"hell\u00D6", "crypt$sa$saykDgk3BPZ9E"), #prevent regression of issue 22 ("foo", 'crypt$MNVY.9ajgdvDQ$MNVY.9ajgdvDQ'), ] known_unidentified_hashes = [ 'sha1$aa$bb', ] known_malformed_hashes = [ # checksum too short 'crypt$c2$c2M87q', # salt must be >2 'crypt$$c2M87q...WWcU', 'crypt$f$c2M87q...WWcU', # this format duplicates salt inside checksum, # reject any where the two copies don't match 'crypt$ffe86$c2M87q...WWcU', ] class DjangoSaltedMd5Test(HandlerCase, _DjangoHelper): "test django_salted_md5" handler = hash.django_salted_md5 known_correct_hashes = [ #test extra large salt ("password", 'md5$123abcdef$c8272612932975ee80e8a35995708e80'), #test unicode uses utf-8 (UPASS_USD, 'md5$c2e86$92105508419a81a6babfaecf876a2fa0'), (UPASS_TABLE, 'md5$d9eb8$01495b32852bffb27cf5d4394fe7a54c'), ] known_unidentified_hashes = [ 'sha1$aa$bb', ] known_malformed_hashes = [ # checksum too short 'md5$aa$bb', ] class DjangoSaltedSha1Test(HandlerCase, _DjangoHelper): "test django_salted_sha1" handler = hash.django_salted_sha1 known_correct_hashes = [ #test extra large salt ("password",'sha1$123abcdef$e4a1877b0e35c47329e7ed7e58014276168a37ba'), #test unicode uses utf-8 (UPASS_USD, 'sha1$c2e86$0f75c5d7fbd100d587c127ef0b693cde611b4ada'), (UPASS_TABLE, 'sha1$6d853$ef13a4d8fb57aed0cb573fe9c82e28dc7fd372d4'), #generic password ("MyPassword", 'sha1$54123$893cf12e134c3c215f3a76bd50d13f92404a54d3'), ] known_unidentified_hashes = [ 'md5$aa$bb', ] known_malformed_hashes = [ # checksum too short 'sha1$c2e86$0f75', ] #========================================================= #fshp #========================================================= class FSHPTest(HandlerCase): "test fshp algorithm" handler = hash.fshp known_correct_hashes = [ #secret, example hash which matches secret #test vectors from FSHP reference implementation ('test', '{FSHP0|0|1}qUqP5cyxm6YcTAhz05Hph5gvu9M='), ('test', '{FSHP1|8|4096}MTIzNDU2NzjTdHcmoXwNc0f' 'f9+ArUHoN0CvlbPZpxFi1C6RDM/MHSA==' ), ('OrpheanBeholderScryDoubt', '{FSHP1|8|4096}GVSUFDAjdh0vBosn1GUhz' 'GLHP7BmkbCZVH/3TQqGIjADXpc+6NCg3g==' ), ('ExecuteOrder66', '{FSHP3|16|8192}0aY7rZQ+/PR+Rd5/I9ss' 'RM7cjguyT8ibypNaSp/U1uziNO3BVlg5qPU' 'ng+zHUDQC3ao/JbzOnIBUtAeWHEy7a2vZeZ' '7jAwyJJa2EqOsq4Io=' ), ] known_unidentified_hashes = [ #bad char in otherwise correctly formatted hash '{FSHX0|0|1}qUqP5cyxm6YcTAhz05Hph5gvu9M=', 'FSHP0|0|1}qUqP5cyxm6YcTAhz05Hph5gvu9M=', ] known_malformed_hashes = [ #wrong salt size '{FSHP0|1|1}qUqP5cyxm6YcTAhz05Hph5gvu9M=', #bad rounds '{FSHP0|0|A}qUqP5cyxm6YcTAhz05Hph5gvu9M=', ] #========================================================= #hex digests #========================================================= from passlib.handlers import digests class HexMd4Test(HandlerCase): handler = digests.hex_md4 known_correct_hashes = [ ("password", '8a9d093f14f8701df17732b2bb182c74')] class HexMd5Test(HandlerCase): handler = digests.hex_md5 known_correct_hashes = [ ("password", '5f4dcc3b5aa765d61d8327deb882cf99')] class HexSha1Test(HandlerCase): handler = digests.hex_sha1 known_correct_hashes = [ ("password", '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8')] class HexSha256Test(HandlerCase): handler = digests.hex_sha256 known_correct_hashes = [ ("password", '5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8')] class HexSha512Test(HandlerCase): handler = digests.hex_sha512 known_correct_hashes = [ ("password", 'b109f3bbbc244eb82441917ed06d618b9008dd09b3befd1b5e07394c706a8bb980b1d7785e5976ec049b46df5f1326af5a2ea6d103fd07c95385ffab0cacbc86')] #========================================================= #ldap hashes #========================================================= from passlib.handlers import ldap_digests class LdapMd5Test(HandlerCase): handler = ldap_digests.ldap_md5 known_correct_hashes = [ ("helloworld", '{MD5}/F4DjTilcDIIVEHn/nAQsA==')] class LdapSha1Test(HandlerCase): handler = ldap_digests.ldap_sha1 known_correct_hashes = [ ("helloworld", '{SHA}at+xg6SiyUovktq1redipHiJpaE=')] class LdapSaltedMd5Test(HandlerCase): handler = ldap_digests.ldap_salted_md5 known_correct_hashes = [ ("testing1234", '{SMD5}UjFY34os/pnZQ3oQOzjqGu4yeXE=')] class LdapSaltedSha1Test(HandlerCase): handler = ldap_digests.ldap_salted_sha1 known_correct_hashes = [ ("testing123", '{SSHA}0c0blFTXXNuAMHECS4uxrj3ZieMoWImr'), ("secret", "{SSHA}0H+zTv8o4MR4H43n03eCsvw1luG8LdB7"), ] class LdapPlaintextTest(HandlerCase): handler = ldap_digests.ldap_plaintext known_correct_hashes = [ ("password", 'password') ] known_unidentified_hashes = [ "{FOO}bar" ] known_other_hashes = [ ("ldap_md5", "{MD5}/F4DjTilcDIIVEHn/nAQsA==")] #NOTE: since the ldap_{crypt} handlers are all wrappers, # don't need separate test. have just one for end-to-end testing purposes. class _LdapMd5CryptTest(HandlerCase): handler = ldap_digests.ldap_md5_crypt known_correct_hashes = [ ('', '{CRYPT}$1$dOHYPKoP$tnxS1T8Q6VVn3kpV8cN6o.'), (' ', '{CRYPT}$1$m/5ee7ol$bZn0kIBFipq39e.KDXX8I0'), ('test', '{CRYPT}$1$ec6XvcoW$ghEtNK2U1MC5l.Dwgi3020'), ('Compl3X AlphaNu3meric', '{CRYPT}$1$nX1e7EeI$ljQn72ZUgt6Wxd9hfvHdV0'), ('4lpHa N|_|M3r1K W/ Cur5Es: #$%(*)(*%#', '{CRYPT}$1$jQS7o98J$V6iTcr71CGgwW2laf17pi1'), ('test', '{CRYPT}$1$SuMrG47N$ymvzYjr7QcEQjaK5m1PGx1'), ] known_malformed_hashes = [ #bad char in otherwise correct hash '{CRYPT}$1$dOHYPKoP$tnxS1T8Q6VVn3kpV8cN6o!', ] OsCrypt_LdapMd5CryptTest = create_backend_case(_LdapMd5CryptTest, "os_crypt") Builtin_LdapMd5CryptTest = create_backend_case(_LdapMd5CryptTest, "builtin") #========================================================= #ldap_pbkdf2_{digest} #========================================================= from passlib.handlers import pbkdf2 as pk2 #NOTE: since these are all wrappers for the pbkdf2_{digest} hasehs, # they don't extensive separate testing. class LdapPbkdf2Test(TestCase): def test_wrappers(self): "test ldap pbkdf2 wrappers" self.assertTrue( pk2.ldap_pbkdf2_sha1.verify( "password", '{PBKDF2}1212$OB.dtnSEXZK8U5cgxU/GYQ$y5LKPOplRmok7CZp/aqVDVg8zGI', ) ) self.assertTrue( pk2.ldap_pbkdf2_sha256.verify( "password", '{PBKDF2-SHA256}1212$4vjV83LKPjQzk31VI4E0Vw$hsYF68OiOUPdDZ1Fg' '.fJPeq1h/gXXY7acBp9/6c.tmQ' ) ) self.assertTrue( pk2.ldap_pbkdf2_sha512.verify( "password", '{PBKDF2-SHA512}1212$RHY0Fr3IDMSVO/RSZyb5ow$eNLfBK.eVozomMr.1gYa1' '7k9B7KIK25NOEshvhrSX.esqY3s.FvWZViXz4KoLlQI.BzY/YTNJOiKc5gBYFYGww' ) ) #========================================================= #md5 crypt #========================================================= from passlib.handlers.md5_crypt import md5_crypt, raw_md5_crypt class _Md5CryptTest(HandlerCase): handler = md5_crypt known_correct_hashes = [ #NOTE: would need to patch HandlerCase to coerce hashes #to_hash_str() for this first one to work under py3. ## ('', b('$1$dOHYPKoP$tnxS1T8Q6VVn3kpV8cN6o.')), ('', '$1$dOHYPKoP$tnxS1T8Q6VVn3kpV8cN6o.'), (' ', '$1$m/5ee7ol$bZn0kIBFipq39e.KDXX8I0'), ('test', '$1$ec6XvcoW$ghEtNK2U1MC5l.Dwgi3020'), ('Compl3X AlphaNu3meric', '$1$nX1e7EeI$ljQn72ZUgt6Wxd9hfvHdV0'), ('4lpHa N|_|M3r1K W/ Cur5Es: #$%(*)(*%#', '$1$jQS7o98J$V6iTcr71CGgwW2laf17pi1'), ('test', '$1$SuMrG47N$ymvzYjr7QcEQjaK5m1PGx1'), (b('test'), '$1$SuMrG47N$ymvzYjr7QcEQjaK5m1PGx1'), ] known_malformed_hashes = [ #bad char in otherwise correct hash '$1$dOHYPKoP$tnxS1T8Q6VVn3kpV8cN6o!', ] def test_raw(self): self.assertEqual(raw_md5_crypt(u's',u's'*16), u'YgmLTApYTv12qgTwBoj8i/') OsCrypt_Md5CryptTest = create_backend_case(_Md5CryptTest, "os_crypt") Builtin_Md5CryptTest = create_backend_case(_Md5CryptTest, "builtin") #========================================================= #mysql 323 & 41 #========================================================= from passlib.handlers.mysql import mysql323, mysql41 class Mysql323Test(HandlerCase): handler = mysql323 known_correct_hashes = [ ('mypass', '6f8c114b58f2ce9e'), ] known_unidentified_hashes = [ #bad char in otherwise correct hash '6z8c114b58f2ce9e', ] def test_whitespace(self): "check whitespace is ignored per spec" h = self.do_encrypt("mypass") h2 = self.do_encrypt("my pass") self.assertEqual(h, h2) class Mysql41Test(HandlerCase): handler = mysql41 known_correct_hashes = [ ('mypass', '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4'), ] known_unidentified_hashes = [ #bad char in otherwise correct hash '*6Z8989366EAF75BB670AD8EA7A7FC1176A95CEF4', ] #========================================================= #NTHASH for unix #========================================================= from passlib.handlers.nthash import nthash class NTHashTest(HandlerCase): handler = nthash known_correct_hashes = [ ('passphrase', '$3$$7f8fe03093cc84b267b109625f6bbf4b'), ('passphrase', '$NT$7f8fe03093cc84b267b109625f6bbf4b'), ] known_malformed_hashes = [ #bad char in otherwise correct hash '$3$$7f8fe03093cc84b267b109625f6bbfxb', ] def test_idents(self): handler = self.handler kwds = dict(checksum='7f8fe03093cc84b267b109625f6bbf4b', ident="3", strict=True) handler(**kwds) kwds['ident'] = None self.assertRaises(ValueError, handler, **kwds) del kwds['strict'] kwds['ident'] = 'Q' self.assertRaises(ValueError, handler, **kwds) #========================================================= #oracle 10 & 11 #========================================================= from passlib.handlers.oracle import oracle10, oracle11 class Oracle10Test(HandlerCase): handler = oracle10 known_correct_hashes = [ # ((secret,user),hash) (('tiger', 'scott'), 'F894844C34402B67'), ((u'ttTiGGeR', u'ScO'), '7AA1A84E31ED7771'), (("d_syspw", "SYSTEM"), '1B9F1F9A5CB9EB31'), (("strat_passwd", "strat_user"), 'AEBEDBB4EFB5225B'), #TODO: get more test vectors (especially ones which properly test unicode / non-ascii) #existing vectors taken from - http://www.petefinnigan.com/default/default_password_list.htm ] known_unidentified_hashes = [ #bad 'z' char in otherwise correct hash 'F894844C34402B6Z', ] def test_user(self): "check user kwd is required for encrypt/verify" self.assertRaises(TypeError, self.handler.encrypt, 'mypass') self.assertRaises(ValueError, self.handler.encrypt, 'mypass', None) self.assertRaises(TypeError, self.handler.verify, 'mypass', 'CC60FA650C497E52') #NOTE: all of the methods below are merely to override # the default test harness in order to insert a default username # when encrypt/verify/etc are called. def create_mismatch(self, secret): if isinstance(secret, tuple): secret, user = secret return 'x' + secret, user else: return 'x' + secret def do_encrypt(self, secret, **kwds): if isinstance(secret, tuple): secret, user = secret else: user = 'default' assert 'user' not in kwds kwds['user'] = user return self.handler.encrypt(secret, **kwds) def do_verify(self, secret, hash): if isinstance(secret, tuple): secret, user = secret else: user = 'default' return self.handler.verify(secret, hash, user=user) def do_genhash(self, secret, config): if isinstance(secret, tuple): secret, user = secret else: user = 'default' return self.handler.genhash(secret, config, user=user) class Oracle11Test(HandlerCase): handler = oracle11 known_correct_hashes = [ ("SHAlala", "S:2BFCFDF5895014EE9BB2B9BA067B01E0389BB5711B7B5F82B7235E9E182C"), #TODO: find more test vectors ] #========================================================= #pbkdf2 hashes #========================================================= from passlib.handlers import pbkdf2 as pk2 class AtlassianPbkdf2Sha1Test(HandlerCase): handler = pk2.atlassian_pbkdf2_sha1 known_correct_hashes = [ ("admin", '{PKCS5S2}c4xaeTQM0lUieMS3V5voiexyX9XhqC2dBd5ecVy60IPksHChwoTAVYFrhsgoq8/p'), (u'\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2', "{PKCS5S2}cE9Yq6Am5tQGdHSHhky2XLeOnURwzaLBG2sur7FHKpvy2u0qDn6GcVGRjlmJoIUy"), ] known_malformed_hashes = [ #bad char '{PKCS5S2}c4xaeTQM0lUieMS3V5voiexyX9XhqC2dBd5ecVy60IPksHChwoTAVYFrhsgoq!/p' #bad size, missing padding '{PKCS5S2}c4xaeTQM0lUieMS3V5voiexyX9XhqC2dBd5ecVy60IPksHChwoTAVYFrhsgoq8/' #bad size, with correct padding '{PKCS5S2}c4xaeTQM0lUieMS3V5voiexyX9XhqC2dBd5ecVy60IPksHChwoTAVYFrhsgoq8/=' ] class Pbkdf2Sha1Test(HandlerCase): handler = pk2.pbkdf2_sha1 known_correct_hashes = [ ("password", '$pbkdf2$1212$OB.dtnSEXZK8U5cgxU/GYQ$y5LKPOplRmok7CZp/aqVDVg8zGI'), (u'\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2', '$pbkdf2$1212$THDqatpidANpadlLeTeOEg$HV3oi1k5C5LQCgG1BMOL.BX4YZc'), ] class Pbkdf2Sha256Test(HandlerCase): handler = pk2.pbkdf2_sha256 known_correct_hashes = [ ("password", '$pbkdf2-sha256$1212$4vjV83LKPjQzk31VI4E0Vw$hsYF68OiOUPdDZ1Fg.fJPeq1h/gXXY7acBp9/6c.tmQ' ), (u'\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2', '$pbkdf2-sha256$1212$3SABFJGDtyhrQMVt1uABPw$WyaUoqCLgvz97s523nF4iuOqZNbp5Nt8do/cuaa7AiI' ), ] class Pbkdf2Sha512Test(HandlerCase): handler = pk2.pbkdf2_sha512 known_correct_hashes = [ ("password", '$pbkdf2-sha512$1212$RHY0Fr3IDMSVO/RSZyb5ow$eNLfBK.eVozomMr.1gYa1' '7k9B7KIK25NOEshvhrSX.esqY3s.FvWZViXz4KoLlQI.BzY/YTNJOiKc5gBYFYGww' ), (u'\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2', '$pbkdf2-sha512$1212$KkbvoKGsAIcF8IslDR6skQ$8be/PRmd88Ps8fmPowCJt' 'tH9G3vgxpG.Krjt3KT.NP6cKJ0V4Prarqf.HBwz0dCkJ6xgWnSj2ynXSV7MlvMa8Q' ), ] class CtaPbkdf2Sha1Test(HandlerCase): handler = pk2.cta_pbkdf2_sha1 known_correct_hashes = [ #test vectors from original implementation (u"hashy the \N{SNOWMAN}", '$p5k2$1000$ZxK4ZBJCfQg=$jJZVscWtO--p1-xIZl6jhO2LKR0='), #additional test vectors ("password", "$p5k2$1$$h1TDLGSw9ST8UMAPeIE13i0t12c="), (u'\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2', "$p5k2$4321$OTg3NjU0MzIx$jINJrSvZ3LXeIbUdrJkRpN62_WQ="), ] class DlitzPbkdf2Sha1Test(HandlerCase): handler = pk2.dlitz_pbkdf2_sha1 known_correct_hashes = [ #test vectors from original implementation ('cloadm', '$p5k2$$exec$r1EWMCMk7Rlv3L/RNcFXviDefYa0hlql'), ('gnu', '$p5k2$c$u9HvcT4d$Sd1gwSVCLZYAuqZ25piRnbBEoAesaa/g'), ('dcl', '$p5k2$d$tUsch7fU$nqDkaxMDOFBeJsTSfABsyn.PYUXilHwL'), ('spam', '$p5k2$3e8$H0NX9mT/$wk/sE8vv6OMKuMaqazCJYDSUhWY9YB2J'), (u'\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2', '$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ'), ] class GrubPbkdf2Sha512Test(HandlerCase): handler = pk2.grub_pbkdf2_sha512 known_correct_hashes = [ #test vectors generated from cmd line tool #salt=32 bytes (u'\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2', 'grub.pbkdf2.sha512.10000.BCAC1CEC5E4341C8C511C529' '7FA877BE91C2817B32A35A3ECF5CA6B8B257F751.6968526A' '2A5B1AEEE0A29A9E057336B48D388FFB3F600233237223C21' '04DE1752CEC35B0DD1ED49563398A282C0F471099C2803FBA' '47C7919CABC43192C68F60'), #salt=64 bytes ('toomanysecrets', 'grub.pbkdf2.sha512.10000.9B436BB6978682363D5C449B' 'BEAB322676946C632208BC1294D51F47174A9A3B04A7E4785' '986CD4EA7470FAB8FE9F6BD522D1FC6C51109A8596FB7AD48' '7C4493.0FE5EF169AFFCB67D86E2581B1E251D88C777B98BA' '2D3256ECC9F765D84956FC5CA5C4B6FD711AA285F0A04DCF4' '634083F9A20F4B6F339A52FBD6BED618E527B'), ] #========================================================= #PHPass Portable Crypt #========================================================= from passlib.handlers.phpass import phpass class PHPassTest(HandlerCase): handler = phpass known_correct_hashes = [ ('', '$P$7JaFQsPzJSuenezefD/3jHgt5hVfNH0'), ('compL3X!', '$P$FiS0N5L672xzQx1rt1vgdJQRYKnQM9/'), ('test12345', '$P$9IQRaTwmfeRo7ud9Fh4E2PdI0S3r.L0'), #from the source ] known_malformed_hashes = [ #bad char in otherwise correct hash '$P$9IQRaTwmfeRo7ud9Fh4E2PdI0S3r!L0', ] def test_idents(self): handler = self.handler kwds = dict(checksum='eRo7ud9Fh4E2PdI0S3r.L0', salt='IQRaTwmf', rounds=9, ident="P", strict=True) handler(**kwds) kwds['ident'] = None self.assertRaises(ValueError, handler, **kwds) del kwds['strict'] kwds['ident'] = 'Q' self.assertRaises(ValueError, handler, **kwds) #========================================================= #plaintext #========================================================= from passlib.handlers.misc import plaintext class PlaintextTest(HandlerCase): handler = plaintext known_correct_hashes = [ ('',''), ('password', 'password'), ] known_other_hashes = [] #all strings are identified as belonging to this scheme accepts_empty_hash = True #========================================================= #postgres_md5 #========================================================= from passlib.handlers.postgres import postgres_md5 class PostgresMD5CryptTest(HandlerCase): handler = postgres_md5 known_correct_hashes = [ # ((secret,user),hash) (('mypass', 'postgres'), 'md55fba2ea04fd36069d2574ea71c8efe9d'), (('mypass', 'root'), 'md540c31989b20437833f697e485811254b'), (("testpassword",'testuser'), 'md5d4fc5129cc2c25465a5370113ae9835f'), ] known_unidentified_hashes = [ #bad 'z' char in otherwise correct hash 'md54zc31989b20437833f697e485811254b', ] #NOTE: used to support secret=(password, user) format, but removed it for now. ##def test_tuple_mode(self): ## "check tuple mode works for encrypt/verify" ## self.assertEqual(self.handler.encrypt(('mypass', 'postgres')), ## 'md55fba2ea04fd36069d2574ea71c8efe9d') ## self.assertEqual(self.handler.verify(('mypass', 'postgres'), ## 'md55fba2ea04fd36069d2574ea71c8efe9d'), True) def test_user(self): "check user kwd is required for encrypt/verify" self.handler.encrypt("mypass", u'user') self.assertRaises(TypeError, self.handler.encrypt, 'mypass') self.assertRaises(ValueError, self.handler.encrypt, 'mypass', None) self.assertRaises(TypeError, self.handler.verify, 'mypass', 'md55fba2ea04fd36069d2574ea71c8efe9d') def create_mismatch(self, secret): if isinstance(secret, tuple): secret, user = secret return 'x' + secret, user else: return 'x' + secret def do_encrypt(self, secret, **kwds): if isinstance(secret, tuple): secret, user = secret else: user = 'default' assert 'user' not in kwds kwds['user'] = user return self.handler.encrypt(secret, **kwds) def do_verify(self, secret, hash): if isinstance(secret, tuple): secret, user = secret else: user = 'default' return self.handler.verify(secret, hash, user=user) def do_genhash(self, secret, config): if isinstance(secret, tuple): secret, user = secret else: user = 'default' return self.handler.genhash(secret, config, user=user) #========================================================= # (netbsd's) sha1 crypt #========================================================= class _SHA1CryptTest(HandlerCase): handler = hash.sha1_crypt known_correct_hashes = [ ("password", "$sha1$19703$iVdJqfSE$v4qYKl1zqYThwpjJAoKX6UvlHq/a"), ("password", "$sha1$21773$uV7PTeux$I9oHnvwPZHMO0Nq6/WgyGV/tDJIH"), ] known_malformed_hashes = [ #bad char in otherwise correct hash '$sha1$21773$u!7PTeux$I9oHnvwPZHMO0Nq6/WgyGV/tDJIH', #zero padded rounds '$sha1$01773$uV7PTeux$I9oHnvwPZHMO0Nq6/WgyGV/tDJIH', ] OsCrypt_SHA1CryptTest = create_backend_case(_SHA1CryptTest, "os_crypt") Builtin_SHA1CryptTest = create_backend_case(_SHA1CryptTest, "builtin") #========================================================= #roundup #========================================================= #NOTE: all roundup hashes use PrefixWrapper, # so there's nothing natively to test. # so we just have a few quick cases... from passlib.handlers import roundup class RoundupTest(TestCase): def _test_pair(self, h, secret, hash): self.assertTrue(h.verify(secret, hash)) self.assertFalse(h.verify('x'+secret, hash)) def test_pairs(self): self._test_pair( roundup.ldap_hex_sha1, "sekrit", '{SHA}8d42e738c7adee551324955458b5e2c0b49ee655') self._test_pair( roundup.ldap_hex_md5, "sekrit", '{MD5}ccbc53f4464604e714f69dd11138d8b5') self._test_pair( ldap_digests.ldap_des_crypt, "sekrit", '{CRYPT}nFia0rj2TT59A') self._test_pair( roundup.roundup_plaintext, "sekrit", '{plaintext}sekrit') self._test_pair( pk2.ldap_pbkdf2_sha1, "sekrit", '{PBKDF2}5000$7BvbBq.EZzz/O0HuwX3iP.nAG3s$g3oPnFFaga2BJaX5PoPRljl4XIE') #========================================================= #sha256-crypt #========================================================= from passlib.handlers.sha2_crypt import sha256_crypt, raw_sha_crypt class _SHA256CryptTest(HandlerCase): handler = sha256_crypt known_correct_hashes = [ ('', '$5$rounds=10428$uy/jIAhCetNCTtb0$YWvUOXbkqlqhyoPMpN8BMe.ZGsGx2aBvxTvDFI613c3'), (' ', '$5$rounds=10376$I5lNtXtRmf.OoMd8$Ko3AI1VvTANdyKhBPavaRjJzNpSatKU6QVN9uwS9MH.'), ('test', '$5$rounds=11858$WH1ABM5sKhxbkgCK$aTQsjPkz0rBsH3lQlJxw9HDTDXPKBxC0LlVeV69P.t1'), ('Compl3X AlphaNu3meric', '$5$rounds=10350$o.pwkySLCzwTdmQX$nCMVsnF3TXWcBPOympBUUSQi6LGGloZoOsVJMGJ09UB'), ('4lpHa N|_|M3r1K W/ Cur5Es: #$%(*)(*%#', '$5$rounds=11944$9dhlu07dQMRWvTId$LyUI5VWkGFwASlzntk1RLurxX54LUhgAcJZIt0pYGT7'), (u'with unic\u00D6de', '$5$rounds=1000$IbG0EuGQXw5EkMdP$LQ5AfPf13KufFsKtmazqnzSGZ4pxtUNw3woQ.ELRDF4'), ] known_malformed_hashes = [ #bad char in otherwise correct hash '$5$rounds=10428$uy/:jIAhCetNCTtb0$YWvUOXbkqlqhyoPMpN8BMeZGsGx2aBvxTvDFI613c3', #zero-padded rounds '$5$rounds=010428$uy/jIAhCetNCTtb0$YWvUOXbkqlqhyoPMpN8BMe.ZGsGx2aBvxTvDFI613c3', ] #NOTE: these test cases taken from official specification at http://www.akkadia.org/drepper/SHA-crypt.txt known_correct_configs = [ #config, secret, result ( "$5$saltstring", "Hello world!", "$5$saltstring$5B8vYYiY.CVt1RlTTf8KbXBH3hsxY/GNooZaBBGWEc5" ), ( "$5$rounds=10000$saltstringsaltstring", "Hello world!", "$5$rounds=10000$saltstringsaltst$3xv.VbSHBb41AL9AvLeujZkZRBAwqFMz2." "opqey6IcA" ), ( "$5$rounds=5000$toolongsaltstring", "This is just a test", "$5$rounds=5000$toolongsaltstrin$Un/5jzAHMgOGZ5.mWJpuVolil07guHPvOW8" "mGRcvxa5" ), ( "$5$rounds=1400$anotherlongsaltstring", "a very much longer text to encrypt. This one even stretches over more" "than one line.", "$5$rounds=1400$anotherlongsalts$Rx.j8H.h8HjEDGomFU8bDkXm3XIUnzyxf12" "oP84Bnq1" ), ( "$5$rounds=77777$short", "we have a short salt string but not a short password", "$5$rounds=77777$short$JiO1O3ZpDAxGJeaDIuqCoEFysAe1mZNJRs3pw0KQRd/" ), ( "$5$rounds=123456$asaltof16chars..", "a short string", "$5$rounds=123456$asaltof16chars..$gP3VQ/6X7UUEW3HkBn2w1/Ptq2jxPyzV/" "cZKmF/wJvD" ), ( "$5$rounds=10$roundstoolow", "the minimum number is still observed", "$5$rounds=1000$roundstoolow$yfvwcWrQ8l/K0DAWyuPMDNHpIVlTQebY9l/gL97" "2bIC" ), ] def filter_known_config_warnings(self): warnings.filterwarnings("ignore", "sha256_crypt does not allow less than 1000 rounds: 10", UserWarning) def test_raw(self): #run some tests on raw backend func to ensure it works right self.assertEqual( raw_sha_crypt(b('secret'), b('salt')*10, 1, hashlib.md5), (b('\x1f\x96\x1cO\x11\xa9h\x12\xc4\xf3\x9c\xee\xf5\x93\xf3\xdd'), b('saltsaltsaltsalt'), 1000) ) self.assertRaises(ValueError, raw_sha_crypt, b('secret'), b('$'), 1, hashlib.md5) OsCrypt_SHA256CryptTest = create_backend_case(_SHA256CryptTest, "os_crypt") Builtin_SHA256CryptTest = create_backend_case(_SHA256CryptTest, "builtin") #========================================================= #test sha512-crypt #========================================================= from passlib.handlers.sha2_crypt import sha512_crypt class _SHA512CryptTest(HandlerCase): handler = sha512_crypt known_correct_hashes = [ ('', '$6$rounds=11021$KsvQipYPWpr93wWP$v7xjI4X6vyVptJjB1Y02vZC5SaSijBkGmq1uJhPr3cvqvvkd42Xvo48yLVPFt8dvhCsnlUgpX.//Cxn91H4qy1'), (' ', '$6$rounds=11104$ED9SA4qGmd57Fq2m$q/.PqACDM/JpAHKmr86nkPzzuR5.YpYa8ZJJvI8Zd89ZPUYTJExsFEIuTYbM7gAGcQtTkCEhBKmp1S1QZwaXx0'), ('test', '$6$rounds=11531$G/gkPn17kHYo0gTF$Kq.uZBHlSBXyzsOJXtxJruOOH4yc0Is13uY7yK0PvAvXxbvc1w8DO1RzREMhKsc82K/Jh8OquV8FZUlreYPJk1'), ('Compl3X AlphaNu3meric', '$6$rounds=10787$wakX8nGKEzgJ4Scy$X78uqaX1wYXcSCtS4BVYw2trWkvpa8p7lkAtS9O/6045fK4UB2/Jia0Uy/KzCpODlfVxVNZzCCoV9s2hoLfDs/'), ('4lpHa N|_|M3r1K W/ Cur5Es: #$%(*)(*%#', '$6$rounds=11065$5KXQoE1bztkY5IZr$Jf6krQSUKKOlKca4hSW07MSerFFzVIZt/N3rOTsUgKqp7cUdHrwV8MoIVNCk9q9WL3ZRMsdbwNXpVk0gVxKtz1'), ] known_malformed_hashes = [ #zero-padded rounds '$6$rounds=011021$KsvQipYPWpr93wWP$v7xjI4X6vyVptJjB1Y02vZC5SaSijBkGmq1uJhPr3cvqvvkd42Xvo48yLVPFt8dvhCsnlUgpX.//Cxn91H4qy1', #bad char in otherwise correct hash '$6$rounds=11021$KsvQipYPWpr9:wWP$v7xjI4X6vyVptJjB1Y02vZC5SaSijBkGmq1uJhPr3cvqvvkd42Xvo48yLVPFt8dvhCsnlUgpX.//Cxn91H4qy1', ] #NOTE: these test cases taken from official specification at http://www.akkadia.org/drepper/SHA-crypt.txt known_correct_configs = [ #config, secret, result ("$6$saltstring", "Hello world!", "$6$saltstring$svn8UoSVapNtMuq1ukKS4tPQd8iKwSMHWjl/O817G3uBnIFNjnQJu" "esI68u4OTLiBFdcbYEdFCoEOfaS35inz1" ), ( "$6$rounds=10000$saltstringsaltstring", "Hello world!", "$6$rounds=10000$saltstringsaltst$OW1/O6BYHV6BcXZu8QVeXbDWra3Oeqh0sb" "HbbMCVNSnCM/UrjmM0Dp8vOuZeHBy/YTBmSK6H9qs/y3RnOaw5v." ), ( "$6$rounds=5000$toolongsaltstring", "This is just a test", "$6$rounds=5000$toolongsaltstrin$lQ8jolhgVRVhY4b5pZKaysCLi0QBxGoNeKQ" "zQ3glMhwllF7oGDZxUhx1yxdYcz/e1JSbq3y6JMxxl8audkUEm0" ), ( "$6$rounds=1400$anotherlongsaltstring", "a very much longer text to encrypt. This one even stretches over more" "than one line.", "$6$rounds=1400$anotherlongsalts$POfYwTEok97VWcjxIiSOjiykti.o/pQs.wP" "vMxQ6Fm7I6IoYN3CmLs66x9t0oSwbtEW7o7UmJEiDwGqd8p4ur1" ), ( "$6$rounds=77777$short", "we have a short salt string but not a short password", "$6$rounds=77777$short$WuQyW2YR.hBNpjjRhpYD/ifIw05xdfeEyQoMxIXbkvr0g" "ge1a1x3yRULJ5CCaUeOxFmtlcGZelFl5CxtgfiAc0" ), ( "$6$rounds=123456$asaltof16chars..", "a short string", "$6$rounds=123456$asaltof16chars..$BtCwjqMJGx5hrJhZywWvt0RLE8uZ4oPwc" "elCjmw2kSYu.Ec6ycULevoBK25fs2xXgMNrCzIMVcgEJAstJeonj1" ), ( "$6$rounds=10$roundstoolow", "the minimum number is still observed", "$6$rounds=1000$roundstoolow$kUMsbe306n21p9R.FRkW3IGn.S9NPN0x50YhH1x" "hLsPuWGsUSklZt58jaTfF4ZEQpyUNGc0dqbpBYYBaHHrsX." ), ] def filter_known_config_warnings(self): warnings.filterwarnings("ignore", "sha512_crypt does not allow less than 1000 rounds: 10", UserWarning) OsCrypt_SHA512CryptTest = create_backend_case(_SHA512CryptTest, "os_crypt") Builtin_SHA512CryptTest = create_backend_case(_SHA512CryptTest, "builtin") #========================================================= #sun md5 crypt #========================================================= from passlib.handlers.sun_md5_crypt import sun_md5_crypt, raw_sun_md5_crypt class SunMD5CryptTest(HandlerCase): handler = sun_md5_crypt known_correct_hashes = [ #TODO: this scheme needs some real test vectors, # especially due to the "bare salt" issue. #-------------------------------------- #sample hashes culled from web messages #-------------------------------------- #http://forums.halcyoninc.com/showthread.php?t=258 ("Gpcs3_adm", "$md5$zrdhpMlZ$$wBvMOEqbSjU.hu5T2VEP01"), #http://www.c0t0d0s0.org/archives/4453-Less-known-Solaris-features-On-passwords-Part-2-Using-stronger-password-hashing.html ("aa12345678", "$md5$vyy8.OVF$$FY4TWzuauRl4.VQNobqMY."), #http://www.cuddletech.com/blog/pivot/entry.php?id=778 ("this", "$md5$3UqYqndY$$6P.aaWOoucxxq.l00SS9k0"), #http://compgroups.net/comp.unix.solaris/password-file-in-linux-and-solaris-8-9 ("passwd", "$md5$RPgLF6IJ$WTvAlUJ7MqH5xak2FMEwS/"), #------------------------------- #potential sample hashes - all have issues #------------------------------- #source: http://solaris-training.com/301_HTML/docs/deepdiv.pdf page 27 #FIXME: password unknown #"$md5,rounds=8000$kS9FT1JC$$mnUrRO618lLah5iazwJ9m1" #source: http://www.visualexams.com/310-303.htm #XXX: this has 9 salt chars unlike all other hashes. is that valid? #FIXME: password unknown #"$md5,rounds=2006$2amXesSj5$$kCF48vfPsHDjlKNXeEw7V." ] known_correct_configs = [ #(config, secret, hash) #--------------------------- #test salt string handling # #these tests attempt to verify that passlib is handling #the "bare salt" issue (see sun md5 crypt docs) #in a sane manner #--------------------------- #config with "$" suffix, hash strings with "$$" suffix, # should all be treated the same, with one "$" added to salt digest. ("$md5$3UqYqndY$", "this", "$md5$3UqYqndY$$6P.aaWOoucxxq.l00SS9k0"), ("$md5$3UqYqndY$$x", "this", "$md5$3UqYqndY$$6P.aaWOoucxxq.l00SS9k0"), #config with no suffix, hash strings with "$" suffix, # should all be treated the same, and no suffix added to salt digest. #NOTE: this is just a guess re: config w/ no suffix, # but otherwise there's no sane way to encode bare_salt=False # within config string. ("$md5$RPgLF6IJ", "passwd", "$md5$RPgLF6IJ$WTvAlUJ7MqH5xak2FMEwS/"), ("$md5$RPgLF6IJ$x", "passwd", "$md5$RPgLF6IJ$WTvAlUJ7MqH5xak2FMEwS/"), ] known_malformed_hashes = [ #bad char in otherwise correct hash "$md5$RPgL!6IJ$WTvAlUJ7MqH5xak2FMEwS/", #2+ "$" at end of salt in config #NOTE: not sure what correct behavior is, so forbidding format for now. "$md5$3UqYqndY$$", #3+ "$" at end of salt in hash #NOTE: not sure what correct behavior is, so forbidding format for now. "$md5$RPgLa6IJ$$$WTvAlUJ7MqH5xak2FMEwS/", ] #========================================================= #unix fallback #========================================================= from passlib.handlers.misc import unix_fallback class UnixFallbackTest(HandlerCase): #NOTE: this class behaves VERY differently from a normal password hash, #so we subclass & disable a number of the default tests. #TODO: combine some of these features w/ django_disabled and other fallback handlers. handler = unix_fallback known_correct_hashes = [ ("passwordwc",""), ] known_other_hashes = [] accepts_empty_hash = True #NOTE: to ease testing, this sets enable_wildcard iff the string 'wc' is in the secret def do_verify(self, secret, hash): return self.handler.verify(secret, hash, enable_wildcard='wc' in secret) def test_50_encrypt_plain(self): "test encrypt() basic behavior" secret = u"\u20AC\u00A5$" result = self.do_encrypt(secret) self.assertEqual(result, "!") self.assertTrue(not self.do_verify(secret, result)) def test_wildcard(self): "test enable_wildcard flag" h = self.handler self.assertTrue(h.verify('password','', enable_wildcard=True)) self.assertFalse(h.verify('password','')) for c in ("!*x"): self.assertFalse(h.verify('password',c, enable_wildcard=True)) self.assertFalse(h.verify('password',c)) #========================================================= #EOF #========================================================= passlib-1.5.3/passlib/tests/sample_config_1s.cfg0000644000175000017500000000035611643466373023015 0ustar biscuitbiscuit00000000000000[passlib] schemes = des_crypt, md5_crypt, bsdi_crypt, sha512_crypt default = md5_crypt all.vary_rounds = 10%% bsdi_crypt.max_rounds = 30000 bsdi_crypt.default_rounds = 25000 sha512_crypt.max_rounds = 50000 sha512_crypt.min_rounds = 40000 passlib-1.5.3/passlib/tests/_test_bad_register.py0000644000175000017500000000102411643466373023316 0ustar biscuitbiscuit00000000000000"helper for method in test_registry.py" from passlib.registry import register_crypt_handler import passlib.utils.handlers as uh class dummy_bad(uh.StaticHandler): name = "dummy_bad" class alt_dummy_bad(uh.StaticHandler): name = "dummy_bad" #NOTE: if passlib.tests is being run from symlink (eg via gaeunit), # this module may be imported a second time as test._test_bad_registry. # we don't want it to do anything in that case. if __name__.startswith("passlib.tests"): register_crypt_handler(alt_dummy_bad) passlib-1.5.3/passlib/tests/__init__.py0000644000175000017500000000002411643466373021224 0ustar biscuitbiscuit00000000000000"""passlib tests""" passlib-1.5.3/passlib/tests/test_apps.py0000644000175000017500000001057111643466373021477 0ustar biscuitbiscuit00000000000000"""test passlib.apps""" #========================================================= #imports #========================================================= from __future__ import with_statement #core import logging; log = logging.getLogger(__name__) #site #pkg from passlib import apps, hash as hashmod from passlib.tests.utils import TestCase #module #========================================================= #test predefined app contexts #========================================================= class AppsTest(TestCase): "perform general tests to make sure contexts work" #NOTE: these tests are not really comprehensive, # since they would do little but duplicate # the presets in apps.py # # they mainly try to ensure no typos # or dynamic behavior foul-ups. def test_custom_app_context(self): ctx = apps.custom_app_context self.assertEqual(ctx.policy.schemes(), ["sha512_crypt", "sha256_crypt"]) for hash in [ ('$6$rounds=41128$VoQLvDjkaZ6L6BIE$4pt.1Ll1XdDYduEwEYPCMOBiR6W6' 'znsyUEoNlcVXpv2gKKIbQolgmTGe6uEEVJ7azUxuc8Tf7zV9SD2z7Ij751'), ('$5$rounds=31817$iZGmlyBQ99JSB5n6$p4E.pdPBWx19OajgjLRiOW0itGny' 'xDGgMlDcOsfaI17'), ]: self.assertTrue(ctx.verify("test", hash)) def test_django_context(self): ctx = apps.django_context for hash in [ 'sha1$0d082$cdb462ae8b6be8784ef24b20778c4d0c82d5957f', 'md5$b887a$37767f8a745af10612ad44c80ff52e92', 'crypt$95a6d$95x74hLDQKXI2', '098f6bcd4621d373cade4e832627b4f6', ]: self.assertTrue(ctx.verify("test", hash)) self.assertEqual(ctx.identify("!"), "django_disabled") self.assertFalse(ctx.verify("test", "!")) def test_ldap_nocrypt_context(self): ctx = apps.ldap_nocrypt_context for hash in [ '{SSHA}cPusOzd6d5n3OjSVK3R329ZGCNyFcC7F', 'test', ]: self.assertTrue(ctx.verify("test", hash)) self.assertIs(ctx.identify('{CRYPT}$5$rounds=31817$iZGmlyBQ99JSB5' 'n6$p4E.pdPBWx19OajgjLRiOW0itGnyxDGgMlDcOsfaI17'), None) def test_ldap_context(self): ctx = apps.ldap_context for hash in [ ('{CRYPT}$5$rounds=31817$iZGmlyBQ99JSB5n6$p4E.pdPBWx19OajgjLRiOW0' 'itGnyxDGgMlDcOsfaI17'), '{SSHA}cPusOzd6d5n3OjSVK3R329ZGCNyFcC7F', 'test', ]: self.assertTrue(ctx.verify("test", hash)) def test_ldap_mysql_context(self): ctx = apps.mysql_context for hash in [ '*94BDCEBE19083CE2A1F959FD02F964C7AF4CFC29', '378b243e220ca493', ]: self.assertTrue(ctx.verify("test", hash)) def test_postgres_context(self): ctx = apps.postgres_context hash = 'md55d9c68c6c50ed3d02a2fcf54f63993b6' self.assertTrue(ctx.verify("test", hash, user='user')) def test_phppass_context(self): ctx = apps.phpass_context for hash in [ '$P$8Ja1vJsKa5qyy/b3mCJGXM7GyBnt6..', '$H$8b95CoYQnQ9Y6fSTsACyphNh5yoM02.', '_cD..aBxeRhYFJvtUvsI', ]: self.assertTrue(ctx.verify("test", hash)) h1 = '$2a$10$Ljj0Kgu7Ddob9xWoqzn0ae.uNfxPRofowWdksk.6jCUHKTGYLD.QG' if hashmod.bcrypt.has_backend(): self.assertTrue(ctx.verify("test", h1)) self.assertEqual(ctx.policy.get_handler().name, "bcrypt") else: self.assertEqual(ctx.identify(h1), "bcrypt") self.assertEqual(ctx.policy.get_handler().name, "phpass") def test_phpbb3_context(self): ctx = apps.phpbb3_context for hash in [ '$P$8Ja1vJsKa5qyy/b3mCJGXM7GyBnt6..', '$H$8b95CoYQnQ9Y6fSTsACyphNh5yoM02.', ]: self.assertTrue(ctx.verify("test", hash)) self.assertTrue(ctx.encrypt("test").startswith("$H$")) def test_roundup_context(self): ctx = apps.roundup_context for hash in [ '{PBKDF2}9849$JMTYu3eOUSoFYExprVVqbQ$N5.gV.uR1.BTgLSvi0qyPiRlGZ0', '{SHA}a94a8fe5ccb19ba61c4c0873d391e987982fbbd3', '{CRYPT}dptOmKDriOGfU', '{plaintext}test', ]: self.assertTrue(ctx.verify("test", hash)) #========================================================= #eof #========================================================= passlib-1.5.3/passlib/tests/genconfig.py0000644000175000017500000002060211643466373021430 0ustar biscuitbiscuit00000000000000"""passlib config generation script this script is a work in progress to develop a script which generates rounds configuration parameters suitable for a particular host & deployment requirements. right now it just consists of a function which experimentally determines the optimal range of rounds values for a given hash, based on the desired time it should take. """ #========================================================= #imports #========================================================= #core from math import log as logb import logging import time import sys #site #pkg from passlib.registry import get_crypt_handler #local log = logging.getLogger(__name__) #========================================================= # #========================================================= class HashTimer(object): """helper which determines number of rounds required for hash to take desired amount of time. usage:: >>> timer = HashTimer("sha512_crypt") >>> timer.find_rounds(.5) .. note:: This function is not very exact, and generates results that are only approximately the same each time (w/in about 5% usually). Furthermore, to generate useful values, it should be run when the system has an average load to get an accurate measurement. """ log = logging.getLogger(__name__ + ".HashTimer") def __init__(self, name, samples=1): # #get handler, extract boundary information # self.handler = handler = get_crypt_handler(name) if 'rounds' not in handler.setting_kwds: raise ValueError("scheme does not support rounds: %r" % (handler.name,)) self.min_rounds = getattr(handler, "min_rounds", 2) self.max_rounds = getattr(handler, "max_rounds", (1<<32)-1) rc = self.rounds_cost = getattr(handler, "rounds_cost", "linear") # #set up functions that vary based on rounds cost function # if rc == "linear": def get_rps(rounds, delta): return rounds/delta def guess_rounds(rps, target): return int(rps*target+.5) erradj = 2 elif rc == "log2": def get_rps(rounds, delta): return (2**rounds)/delta def guess_rounds(rps, target): return int(logb(rps*target,2)+.5) erradj = 1.1 else: raise NotImplementedError("unknown rounds cost function: %r" % (rc,)) self.get_rps = get_rps self.guess_rounds = guess_rounds self.erradj = erradj # #init cache # self.samples = samples self.cache = {} self.srange = range(samples) def time_encrypt(self, rounds): "check how long encryption for a given number of rounds will take" cache = self.cache if rounds in cache: return cache[rounds] encrypt = self.handler.encrypt srange = self.srange cur = time.time start = cur() for x in srange: encrypt("too many secrets", rounds=rounds) stop = cur() delta = (stop-start)/self.samples cache[rounds] = delta return delta def find_rounds(self, target, over=False, under=False): """find optimal rounds range for hash :arg target: time hashing a password should take :param over: if True, returns minimum rounds taking *at least* target seconds. :param under: if True, returns maximum rounds taking *at most* target seconds. if neither over / under is set, returns rounds taking closest to target seconds. :returns: returns number of rounds closest to taking about ``target`` seconds to hash a password. """ if target <= 0: raise ValueError("target must be > 0") log = self.log name = self.handler.name get_rps = self.get_rps time_encrypt = self.time_encrypt log.info("%s: finding rounds for target time: %f", name, target) # #check if useful lower & upper bounds already exist in cache # lower = upper = None for rounds, delta in self.cache.iteritems(): if delta < target: if lower is None or rounds > lower: lower = rounds else: if upper is None or rounds < upper: upper = rounds # #if bounds not found in cache, run open-ended search for starting bounds # if lower is None: lower = max(1,self.min_rounds) if upper is None: guess_rounds = self.guess_rounds max_rounds = self.max_rounds target_above = target*self.erradj #NOTE: we aim a little high as hack to deal w/ measuring error rounds = lower while True: delta = time_encrypt(rounds) rps = get_rps(rounds, delta) log.debug("%s: ranging target: checked %r -> %fs (%f r/s)", name, rounds, delta, rps) if delta < target: lower = rounds if rounds == max_rounds: log.warning("%s: target time out of range: hash would require > max_rounds (%d) in order to take %fs", name, max_rounds, target) return rounds rounds = min(max(guess_rounds(rps, target_above), rounds+1), max_rounds) else: upper = rounds break # #perform binary search till we find match # while lower+1 %fs (%f r/s)", name, lower, upper, next, delta, rps) if delta < target: lower = next else: upper = next # #now 'lower' is largest value which takes less than target seconds, #and 'upper' is smallest value which takes greater than target seconds. #so we pick based on over/under flags, or fallback to whichever one is closest # if over: return upper elif under: return lower else: if target-cache[lower] < cache[upper]-target: return lower else: return upper def find_rounds_range(self, target_high, target_low=None, over=False, under=False): "find min/max rounds which will cause scheme to take specified range of times" if target_low is None: target_low = target_high * .75 elif target_low > target_high: target_high = target_low rounds_high = self.find_rounds(target_high, under=not over, over=over) rounds_low = self.find_rounds(target_low, over=not under, under=under) if rounds_low > rounds_high: #NOTE: this happens sometimes w/ rounds_cost=log2... #if nothing hits w/in range, rounds_low will be 1+ rounds_high #we just return correctly ordered range rounds_low, rounds_high = rounds_high, rounds_low return rounds_low, rounds_high def estimate_rps(self): "return estimated rounds per second based on cached results" cache = self.cache if not cache: raise RuntimeError("should not be called until cache populated by find_rounds()") get_rps = self.get_rps rps = sum(r*get_rps(r,d) for r,d in cache.iteritems())/sum(cache) if rps > 1000: #for almost all cases, we'd return integer rps = int(rps) return rps #========================================================= #main #========================================================= def main(*args): from bps.logs import setup_std_logging setup_std_logging(level="debug", dev=True) timer = HashTimer("sha256_crypt") print timer.find_rounds_range(.5) print timer.estimate_rps() #TODO: give script ability to generate timings for a range of schemes, and minimum / maximum times. if __name__ == "__main__": sys.exit(main(sys.argv[1:])) #========================================================= #eof #========================================================= passlib-1.5.3/passlib/tests/utils.py0000644000175000017500000014014511643753301020624 0ustar biscuitbiscuit00000000000000"""helpers for passlib unittests""" #========================================================= #imports #========================================================= from __future__ import with_statement #core import atexit import logging; log = logging.getLogger(__name__) import re import os import sys import tempfile try: import unittest2 as unittest ut_version = 2 except ImportError: import unittest # Py2k # if sys.version_info < (2,7): # Py3k # #if sys.version_info < (3,2): # end Py3k # ut_version = 1 else: ut_version = 2 import warnings from warnings import warn #site if ut_version < 2: #used to provide replacement skipTest() method from nose.plugins.skip import SkipTest #pkg from passlib import registry, utils from passlib.utils import classproperty, handlers as uh, \ has_rounds_info, has_salt_info, MissingBackendError, \ rounds_cost_values, b, bytes, native_str, NoneType #local __all__ = [ #util funcs 'enable_option', 'Params', 'set_file', 'get_file', #unit testing 'TestCase', 'HandlerCase', 'enable_backend_case', 'create_backend_case', #flags 'gae_env', ] #figure out if we're running under GAE... #some tests (eg FS related) should be skipped. #XXX: is there better way to do this? try: import google.appengine except ImportError: gae_env = False else: gae_env = True #========================================================= #option flags #========================================================= DEFAULT_TESTS = "" tests = set( v.strip() for v in os.environ.get("PASSLIB_TESTS", DEFAULT_TESTS).lower().split(",") ) def enable_option(*names): """check if a given test should be included based on the env var. test flags: all-backends test all backends, even the inactive ones cover enable minor tweaks to maximize coverage testing all run all tests """ return 'all' in tests or any(name in tests for name in names) #========================================================= #misc utility funcs #========================================================= class Params(object): "helper to represent params for function call" @classmethod def norm(cls, value): if isinstance(value, cls): return value if isinstance(value, (list,tuple)): return cls(*value) return cls(**value) def __init__(self, *args, **kwds): self.args = args self.kwds = kwds def render(self, offset=0): """render parenthesized parameters""" txt = '' for a in self.args[offset:]: txt += "%r, " % (a,) kwds = self.kwds for k in sorted(kwds): txt += "%s=%r, " % (k, kwds[k]) if txt.endswith(", "): txt = txt[:-2] return txt def set_file(path, content): "set file to specified bytes" if isinstance(content, unicode): content = content.encode("utf-8") with open(path, "wb") as fh: fh.write(content) def get_file(path): "read file as bytes" with open(path, "rb") as fh: return fh.read() #========================================================= #custom test base #========================================================= class TestCase(unittest.TestCase): """passlib-specific test case class this class mainly overriddes many of the common assert methods so to give a default message which includes the values as well as the class-specific case_prefix string. this latter bit makes the output of various test cases easier to distinguish from eachother. """ #============================================================= #make it ease for test cases to add common prefix to all descs #============================================================= #: string or method returning string - prepended to all tests in TestCase case_prefix = None #: flag to disable feature longDescription = True def shortDescription(self): "wrap shortDescription() method to prepend case_prefix" desc = super(TestCase, self).shortDescription() if desc is None: #would still like to add prefix, but munges things up. return None prefix = self.case_prefix if prefix and self.longDescription: if callable(prefix): prefix = prefix() desc = "%s: %s" % (prefix, desc) return desc #============================================================ #hack to set UT2 private skip attrs to mirror nose's __test__ attr #============================================================ if ut_version >= 2: @classproperty def __unittest_skip__(cls): return not getattr(cls, "__test__", True) @classproperty def __test__(cls): #so nose won't auto run *this* cls, but it will for subclasses return cls is not TestCase and not cls.__name__.startswith("_") #============================================================ # tweak msg formatting for some assert methods #============================================================ longMessage = True #override python default (False) def _formatMessage(self, msg, std): "override UT2's _formatMessage - only use longMessage if msg ends with ':'" if not msg: return std if not self.longMessage or not msg.endswith(":"): return msg.rstrip(":") return '%s %s' % (msg, std) #============================================================ #override some unittest1 methods to support _formatMessage #============================================================ if ut_version < 2: def assertEqual(self, real, correct, msg=None): if real != correct: std = "got %r, expected would equal %r" % (real, correct) msg = self._formatMessage(msg, std) raise self.failureException(msg) def assertNotEqual(self, real, correct, msg=None): if real == correct: std = "got %r, expected would not equal %r" % (real, correct) msg = self._formatMessage(msg, std) raise self.failureException(msg) assertEquals = assertEqual assertNotEquals = assertNotEqual #NOTE: overriding this even under UT2. #FIXME: this doesn't support the fancy context manager UT2 provides. def assertRaises(self, type, func, *args, **kwds): #NOTE: overriding this for format ability, # but ALSO adding "__msg__" kwd so we can set custom msg msg = kwds.pop("__msg__", None) try: result = func(*args, **kwds) except Exception, err: if isinstance(err, type): return True ##import traceback, sys ##print >>sys.stderr, traceback.print_exception(*sys.exc_info()) std = "function raised %r, expected %r" % (err, type) msg = self._formatMessage(msg, std) raise self.failureException(msg) std = "function returned %r, expected it to raise %r" % (result, type) msg = self._formatMessage(msg, std) raise self.failureException(msg) #=============================================================== #backport some methods from unittest2 #=============================================================== if ut_version < 2: def assertIs(self, real, correct, msg=None): if real is not correct: std = "got %r, expected would be %r" % (real, correct) msg = self._formatMessage(msg, std) raise self.failureException(msg) def assertIsNot(self, real, correct, msg=None): if real is correct: std = "got %r, expected would not be %r" % (real, correct) msg = self._formatMessage(msg, std) raise self.failureException(msg) def assertIsInstance(self, obj, klass, msg=None): if not isinstance(obj, klass): std = "got %r, expected instance of %r" % (obj, klass) msg = self._formatMessage(msg, std) raise self.failureException(msg) def skipTest(self, reason): raise SkipTest(reason) def assertAlmostEqual(self, first, second, places=None, msg=None, delta=None): """Fail if the two objects are unequal as determined by their difference rounded to the given number of decimal places (default 7) and comparing to zero, or by comparing that the between the two objects is more than the given delta. Note that decimal places (from zero) are usually not the same as significant digits (measured from the most signficant digit). If the two objects compare equal then they will automatically compare almost equal. """ if first == second: # shortcut return if delta is not None and places is not None: raise TypeError("specify delta or places not both") if delta is not None: if abs(first - second) <= delta: return standardMsg = '%s != %s within %s delta' % (repr(first), repr(second), repr(delta)) else: if places is None: places = 7 if round(abs(second-first), places) == 0: return standardMsg = '%s != %s within %r places' % (repr(first), repr(second), places) msg = self._formatMessage(msg, standardMsg) raise self.failureException(msg) if not hasattr(unittest.TestCase, "assertRegexpMatches"): #added in 2.7/UT2 and 3.1 def assertRegexpMatches(self, text, expected_regex, msg=None): """Fail the test unless the text matches the regular expression.""" if isinstance(expected_regex, basestring): assert expected_regex, "expected_regex must not be empty." expected_regex = re.compile(expected_regex) if not expected_regex.search(text): msg = msg or "Regex didn't match" msg = '%s: %r not found in %r' % (msg, expected_regex.pattern, text) raise self.failureException(msg) #============================================================ #add some custom methods #============================================================ def assertFunctionResults(self, func, cases): """helper for running through function calls. func should be the function to call. cases should be list of Param instances, where first position argument is expected return value, and remaining args and kwds are passed to function. """ for elem in cases: elem = Params.norm(elem) correct = elem.args[0] result = func(*elem.args[1:], **elem.kwds) msg = "error for case %r:" % (elem.render(1),) self.assertEqual(result, correct, msg) def assertWarningMatches(self, warning, message=None, message_re=None, category=None, ##filename=None, filename_re=None, ##lineno=None, msg=None, ): "check if WarningMessage instance (as returned by catch_warnings) matches parameters" #determine if we have WarningMessage object, #and ensure 'warning' contains only warning instances. if hasattr(warning, "category"): wmsg = warning warning = warning.message else: wmsg = None #tests that can use a warning instance or WarningMessage object if message: self.assertEqual(str(warning), message, msg) if message_re: self.assertRegexpMatches(str(warning), message_re, msg) if category: self.assertIsInstance(warning, category, msg) #commented out until needed... ###tests that require a WarningMessage object ##if filename or filename_re: ## if not wmsg: ## raise TypeError("can't read filename from warning object") ## real = wmsg.filename ## if real.endswith(".pyc") or real.endswith(".pyo"): ## #FIXME: should use a stdlib call to resolve this back ## # to original module's path ## real = real[:-1] ## if filename: ## self.assertEqual(real, filename, msg) ## if filename_re: ## self.assertRegexpMatches(real, filename_re, msg) ##if lineno: ## if not wmsg: ## raise TypeError("can't read lineno from warning object") ## self.assertEqual(wmsg.lineno, lineno, msg) #============================================================ #eoc #============================================================ #========================================================= #other unittest helpers #========================================================= class HandlerCase(TestCase): """base class for testing password hash handlers (esp passlib.utils.handlers subclasses) In order to use this to test a handler, create a subclass will all the appropriate attributes filled as listed in the example below, and run the subclass via unittest. .. todo:: Document all of the options HandlerCase offers. .. note:: This is subclass of :class:`unittest.TestCase` (or :class:`unittest2.TestCase` if available). """ #========================================================= #attrs to be filled in by subclass for testing specific handler #========================================================= #: specify handler object here (required) handler = None #: maximum number of chars which hash will include in checksum # override this only if hash doesn't use all chars (the default) secret_chars = -1 #: list of (secret,hash) pairs which handler should verify as matching known_correct_hashes = [] #: list of (config, secret, hash) triples which handler should genhash & verify known_correct_configs = [] #: hashes so malformed they aren't even identified properly known_unidentified_hashes = [] #: hashes which are malformed - they should identify() as True, but cause error when passed to genhash/verify known_malformed_hashes = [] #: list of (handler name, hash) pairs for other algorithm's hashes, that handler shouldn't identify as belonging to it # this list should generally be sufficient (if handler name in list, that entry will be skipped) known_other_hashes = [ ('des_crypt', '6f8c114b58f2c'), ('md5_crypt', '$1$dOHYPKoP$tnxS1T8Q6VVn3kpV8cN6o.'), ('sha512_crypt', "$6$rounds=123456$asaltof16chars..$BtCwjqMJGx5hrJhZywWvt0RLE8uZ4oPwc" "elCjmw2kSYu.Ec6ycULevoBK25fs2xXgMNrCzIMVcgEJAstJeonj1"), ] #: flag if scheme accepts empty string as hash (rare) accepts_empty_hash = False #: if handler uses multiple backends, explicitly set this one when running tests. backend = None #: hack used by create_backend() to signal we should monkeypatch # safe_os_crypt() to use handler+this backend, # only used when backend == "os_crypt" _patch_crypt_backend = None #========================================================= #alg interface helpers - allows subclass to overide how # default tests invoke the handler (eg for context_kwds) #========================================================= def do_encrypt(self, secret, **kwds): "call handler's encrypt method with specified options" return self.handler.encrypt(secret, **kwds) def do_verify(self, secret, hash): "call handler's verify method" return self.handler.verify(secret, hash) def do_identify(self, hash): "call handler's identify method" return self.handler.identify(hash) def do_genconfig(self, **kwds): "call handler's genconfig method with specified options" return self.handler.genconfig(**kwds) def do_genhash(self, secret, config): "call handler's genhash method with specified options" return self.handler.genhash(secret, config) def create_mismatch(self, secret): "return other secret which won't match" #NOTE: this is subclassable mainly for some algorithms #which accept non-strings in secret if isinstance(secret, unicode): return u'x' + secret else: return b('x') + secret #========================================================= #internal class attrs #========================================================= @classproperty def __test__(cls): #so nose won't auto run *this* cls, but it will for subclasses return cls is not HandlerCase and not cls.__name__.startswith("_") #optional prefix to prepend to name of test method as it's called, #useful when multiple handler test classes being run. #default behavior should be sufficient def case_prefix(self): name = self.handler.name if self.handler else self.__class__.__name__ get_backend = getattr(self.handler, "get_backend", None) #set by some of the builtin handlers if get_backend: name += " (%s backend)" % (get_backend(),) return name @classproperty def all_correct_hashes(cls): hashes = cls.known_correct_hashes configs = cls.known_correct_configs if configs: hashes = hashes + [ (secret,hash) for config,secret,hash in configs if (secret,hash) not in hashes ] return hashes #========================================================= #setup / cleanup #========================================================= _orig_backend = None #backup of original backend _orig_os_crypt = None #backup of original utils.os_crypt def setUp(self): h = self.handler backend = self.backend if backend: if not hasattr(h, "set_backend"): raise RuntimeError("handler doesn't support multiple backends") self._orig_backend = h.get_backend() alt_backend = None if (backend == "os_crypt" and not h.has_backend("os_crypt")): alt_backend = _has_other_backends(h, "os_crypt") if alt_backend: #monkeypatch utils.safe_os_crypt to use specific handler+backend #this allows use to test as much of the hash's code path #as possible, even if current OS doesn't provide crypt() support #for the hash. self._orig_os_crypt = utils.os_crypt def crypt_stub(secret, hash): tmp = h.get_backend() try: h.set_backend(alt_backend) hash = h.genhash(secret, hash) finally: h.set_backend(tmp) # Py2k # if isinstance(hash, unicode): hash = hash.encode("ascii") # end Py2k # return hash utils.os_crypt = crypt_stub h.set_backend(backend) def tearDown(self): if self._orig_os_crypt: utils.os_crypt = self._orig_os_crypt if self._orig_backend: self.handler.set_backend(self._orig_backend) #========================================================= #attributes #========================================================= def test_00_required_attributes(self): "test required handler attributes are defined" handler = self.handler def ga(name): return getattr(handler, name, None) name = ga("name") self.assertTrue(name, "name not defined:") self.assertIsInstance(name, native_str, "name must be native str") self.assertTrue(name.lower() == name, "name not lower-case:") self.assertTrue(re.match("^[a-z0-9_]+$", name), "name must be alphanum + underscore: %r" % (name,)) settings = ga("setting_kwds") self.assertTrue(settings is not None, "setting_kwds must be defined:") self.assertIsInstance(settings, tuple, "setting_kwds must be a tuple:") context = ga("context_kwds") self.assertTrue(context is not None, "context_kwds must be defined:") self.assertIsInstance(context, tuple, "context_kwds must be a tuple:") def test_01_optional_salt_attributes(self): "validate optional salt attributes" cls = self.handler if not has_salt_info(cls): raise self.skipTest("handler doesn't provide salt info") AssertionError = self.failureException #check max_salt_size mx_set = (cls.max_salt_size is not None) if mx_set and cls.max_salt_size < 1: raise AssertionError("max_salt_chars must be >= 1") #check min_salt_size if cls.min_salt_size < 0: raise AssertionError("min_salt_chars must be >= 0") if mx_set and cls.min_salt_size > cls.max_salt_size: raise AssertionError("min_salt_chars must be <= max_salt_chars") #check default_salt_size if cls.default_salt_size < cls.min_salt_size: raise AssertionError("default_salt_size must be >= min_salt_size") if mx_set and cls.default_salt_size > cls.max_salt_size: raise AssertionError("default_salt_size must be <= max_salt_size") #check for 'salt_size' keyword if 'salt_size' not in cls.setting_kwds and \ (not mx_set or cls.min_salt_size < cls.max_salt_size): #NOTE: for now, only bothering to issue warning if default_salt_size isn't maxed out if (not mx_set or cls.default_salt_size < cls.max_salt_size): warn("%s: hash handler supports range of salt sizes, but doesn't offer 'salt_size' setting" % (cls.name,)) #check salt_chars & default_salt_chars if cls.salt_chars: if not cls.default_salt_chars: raise AssertionError("default_salt_chars must not be empty") if any(c not in cls.salt_chars for c in cls.default_salt_chars): raise AssertionError("default_salt_chars must be subset of salt_chars: %r not in salt_chars" % (c,)) else: if not cls.default_salt_chars: raise AssertionError("default_salt_chars MUST be specified if salt_chars is empty") def test_02_optional_rounds_attributes(self): "validate optional rounds attributes" cls = self.handler if not has_rounds_info(cls): raise self.skipTest("handler lacks rounds attributes") AssertionError = self.failureException #check max_rounds if cls.max_rounds is None: raise AssertionError("max_rounds not specified") if cls.max_rounds < 1: raise AssertionError("max_rounds must be >= 1") #check min_rounds if cls.min_rounds < 0: raise AssertionError("min_rounds must be >= 0") if cls.min_rounds > cls.max_rounds: raise AssertionError("min_rounds must be <= max_rounds") #check default_rounds if cls.default_rounds is not None: if cls.default_rounds < cls.min_rounds: raise AssertionError("default_rounds must be >= min_rounds") if cls.default_rounds > cls.max_rounds: raise AssertionError("default_rounds must be <= max_rounds") #check rounds_cost if cls.rounds_cost not in rounds_cost_values: raise AssertionError("unknown rounds cost constant: %r" % (cls.rounds_cost,)) def test_03_HasManyIdents(self): "check configuration of HasManyIdents-derived classes" cls = self.handler if not isinstance(cls, type) or not issubclass(cls, uh.HasManyIdents): raise self.skipTest("handler doesn't derive from HasManyIdents") #check settings self.assertTrue('ident' in cls.setting_kwds) #check ident_values list for value in cls.ident_values: self.assertIsInstance(value, unicode, "cls.ident_values must be unicode:") self.assertTrue(len(cls.ident_values)>1, "cls.ident_values must have 2+ elements:") #check default_ident value self.assertIsInstance(cls.default_ident, unicode, "cls.default_ident must be unicode:") self.assertTrue(cls.default_ident in cls.ident_values, "cls.default_ident must specify member of cls.ident_values") #check optional aliases list if cls.ident_aliases: for alias, ident in cls.ident_aliases.iteritems(): self.assertIsInstance(alias, unicode, "cls.ident_aliases keys must be unicode:") #XXX: allow ints? self.assertIsInstance(ident, unicode, "cls.ident_aliases values must be unicode:") self.assertTrue(ident in cls.ident_values, "cls.ident_aliases must map to cls.ident_values members: %r" % (ident,)) RESERVED_BACKEND_NAMES = [ "any", "default", None ] def test_04_backend_handler(self): "check behavior of multiple-backend handlers" h = self.handler if not hasattr(h, "set_backend"): raise self.skipTest("handler has single backend") #preserve current backend orig = h.get_backend() try: #run through all backends handler supports for backend in h.backends: self.assertFalse(backend in self.RESERVED_BACKEND_NAMES, "invalid backend name: %r" % (backend,)) #check has_backend() returns bool value r = h.has_backend(backend) if r is True: #check backend can be loaded h.set_backend(backend) self.assertEqual(h.get_backend(), backend) elif r is False: #check backend CAN'T be loaded self.assertRaises(MissingBackendError, h.set_backend, backend) else: #failure eg: used classmethod instead of classproperty in _has_backend_xxx raise TypeError("has_backend(%r) returned invalid value: %r" % (backend, r,)) finally: h.set_backend(orig) #========================================================= #identify() #========================================================= def test_10_identify_hash(self): "test identify() against scheme's own hashes" for secret, hash in self.known_correct_hashes: self.assertEqual(self.do_identify(hash), True, "hash=%r:" % (hash,)) for config, secret, hash in self.known_correct_configs: self.assertEqual(self.do_identify(hash), True, "hash=%r:" % (hash,)) def test_11_identify_config(self): "test identify() against scheme's own config strings" if not self.known_correct_configs: raise self.skipTest("no config strings provided") for config, secret, hash in self.known_correct_configs: self.assertEqual(self.do_identify(config), True, "config=%r:" % (config,)) def test_12_identify_unidentified(self): "test identify() against scheme's own hashes that are mangled beyond identification" if not self.known_unidentified_hashes: raise self.skipTest("no unidentified hashes provided") for hash in self.known_unidentified_hashes: self.assertEqual(self.do_identify(hash), False, "hash=%r:" % (hash,)) def test_13_identify_malformed(self): "test identify() against scheme's own hashes that are mangled but identifiable" if not self.known_malformed_hashes: raise self.skipTest("no malformed hashes provided") for hash in self.known_malformed_hashes: self.assertEqual(self.do_identify(hash), True, "hash=%r:" % (hash,)) def test_14_identify_other(self): "test identify() against other schemes' hashes" for name, hash in self.known_other_hashes: self.assertEqual(self.do_identify(hash), name == self.handler.name, "scheme=%r, hash=%r:" % (name, hash)) def test_15_identify_none(self): "test identify() against None / empty string" self.assertEqual(self.do_identify(None), False) self.assertEqual(self.do_identify(b('')), self.accepts_empty_hash) self.assertEqual(self.do_identify(u''), self.accepts_empty_hash) #========================================================= #verify() #========================================================= def test_20_verify_positive(self): "test verify() against known-correct secret/hash pairs" self.assertTrue(self.known_correct_hashes or self.known_correct_configs, "test must define at least one of known_correct_hashes or known_correct_configs") for secret, hash in self.known_correct_hashes: self.assertEqual(self.do_verify(secret, hash), True, "known correct hash (secret=%r, hash=%r):" % (secret,hash)) for config, secret, hash in self.known_correct_configs: self.assertEqual(self.do_verify(secret, hash), True, "known correct hash (secret=%r, hash=%r):" % (secret,hash)) def test_21_verify_other(self): "test verify() throws error against other algorithm's hashes" for name, hash in self.known_other_hashes: if name == self.handler.name: continue self.assertRaises(ValueError, self.do_verify, 'fakesecret', hash, __msg__="scheme=%r, hash=%r:" % (name, hash)) def test_22_verify_unidentified(self): "test verify() throws error against known-unidentified hashes" if not self.known_unidentified_hashes: raise self.skipTest("no unidentified hashes provided") for hash in self.known_unidentified_hashes: self.assertRaises(ValueError, self.do_verify, 'stub', hash, __msg__="hash=%r:" % (hash,)) def test_23_verify_malformed(self): "test verify() throws error against known-malformed hashes" if not self.known_malformed_hashes: raise self.skipTest("no malformed hashes provided") for hash in self.known_malformed_hashes: self.assertRaises(ValueError, self.do_verify, 'stub', hash, __msg__="hash=%r:" % (hash,)) def test_24_verify_none(self): "test verify() throws error against hash=None/empty string" #find valid hash so that doesn't mask error self.assertRaises(ValueError, self.do_verify, 'stub', None, __msg__="hash=None:") if self.accepts_empty_hash: self.do_verify("stub", u"") self.do_verify("stub", b("")) else: self.assertRaises(ValueError, self.do_verify, 'stub', u'', __msg__="hash='':") self.assertRaises(ValueError, self.do_verify, 'stub', b(''), __msg__="hash='':") #========================================================= #genconfig() #========================================================= def test_30_genconfig_salt(self): "test genconfig() generates new salt" if 'salt' not in self.handler.setting_kwds: raise self.skipTest("handler doesn't have salt") c1 = self.do_genconfig() c2 = self.do_genconfig() self.assertIsInstance(c1, native_str, "genconfig() must return native str:") self.assertIsInstance(c2, native_str, "genconfig() must return native str:") self.assertNotEqual(c1,c2) def test_31_genconfig_minsalt(self): "test genconfig() honors min salt chars" handler = self.handler if not has_salt_info(handler): raise self.skipTest("handler doesn't provide salt info") cs = handler.salt_chars cc = cs[0:1] mn = handler.min_salt_size c1 = self.do_genconfig(salt=cc * mn) if mn > 0: self.assertRaises(ValueError, self.do_genconfig, salt=cc*(mn-1)) def test_32_genconfig_maxsalt(self): "test genconfig() honors max salt chars" handler = self.handler if not has_salt_info(handler): raise self.skipTest("handler doesn't provide salt info") cs = handler.salt_chars cc = cs[0:1] mx = handler.max_salt_size if mx is None: #make sure salt is NOT truncated, #use a really large salt for testing salt = cc * 1024 c1 = self.do_genconfig(salt=salt) c2 = self.do_genconfig(salt=salt + cc) self.assertNotEqual(c1,c2) else: #make sure salt is truncated exactly where it should be. salt = cc * mx c1 = self.do_genconfig(salt=salt) c2 = self.do_genconfig(salt=salt + cc) self.assertEqual(c1,c2) #if min_salt supports it, check smaller than mx is NOT truncated if handler.min_salt_size < mx: c3 = self.do_genconfig(salt=salt[:-1]) self.assertNotEqual(c1,c3) def test_33_genconfig_saltchars(self): "test genconfig() honors salt_chars" handler = self.handler if not has_salt_info(handler): raise self.skipTest("handler doesn't provide salt info") mx = handler.max_salt_size mn = handler.min_salt_size cs = handler.salt_chars raw = isinstance(cs, bytes) #make sure all listed chars are accepted chunk = 32 if mx is None else mx for i in xrange(0,len(cs),chunk): salt = cs[i:i+chunk] if len(salt) < mn: salt = (salt*(mn//len(salt)+1))[:chunk] self.do_genconfig(salt=salt) #check some invalid salt chars, make sure they're rejected source = u'\x00\xff' if raw: source = source.encode("latin-1") chunk = max(mn, 1) for c in source: if c not in cs: self.assertRaises(ValueError, self.do_genconfig, salt=c*chunk, __msg__="invalid salt char %r:" % (c,)) #========================================================= #genhash() #========================================================= filter_known_config_warnings = None def test_40_genhash_config(self): "test genhash() against known config strings" if not self.known_correct_configs: raise self.skipTest("no config strings provided") fk = self.filter_known_config_warnings if fk: ctx = catch_warnings() ctx.__enter__() fk() for config, secret, hash in self.known_correct_configs: result = self.do_genhash(secret, config) self.assertEqual(result, hash, "config=%r,secret=%r:" % (config,secret)) if fk: ctx.__exit__(None,None,None) def test_41_genhash_hash(self): "test genhash() against known hash strings" if not self.known_correct_hashes: raise self.skipTest("no correct hashes provided") handler = self.handler for secret, hash in self.known_correct_hashes: result = self.do_genhash(secret, hash) self.assertEqual(result, hash, "secret=%r:" % (secret,)) def test_42_genhash_genconfig(self): "test genhash() against genconfig() output" handler = self.handler config = handler.genconfig() hash = self.do_genhash("stub", config) self.assertTrue(handler.identify(hash)) def test_43_genhash_none(self): "test genhash() against hash=None" handler = self.handler config = handler.genconfig() if config is None: raise self.skipTest("handler doesnt use config strings") self.assertRaises(ValueError, handler.genhash, 'secret', None) #========================================================= #encrypt() #========================================================= def test_50_encrypt_plain(self): "test encrypt() basic behavior" #check it handles unicode password secret = u"\u20AC\u00A5$" result = self.do_encrypt(secret) self.assertIsInstance(result, native_str, "encrypt must return native str:") self.assertTrue(self.do_identify(result)) self.assertTrue(self.do_verify(secret, result)) #check it handles bytes password as well secret = b('\xe2\x82\xac\xc2\xa5$') result = self.do_encrypt(secret) self.assertIsInstance(result, native_str, "encrypt must return native str:") self.assertTrue(self.do_identify(result)) self.assertTrue(self.do_verify(secret, result)) def test_51_encrypt_none(self): "test encrypt() refused secret=None" self.assertRaises(TypeError, self.do_encrypt, None) def test_52_encrypt_salt(self): "test encrypt() generates new salt" if 'salt' not in self.handler.setting_kwds: raise self.skipTest("handler doesn't have salt") #test encrypt() h1 = self.do_encrypt("stub") h2 = self.do_encrypt("stub") self.assertNotEqual(h1, h2) # optional helper used by test_53_external_verifiers iter_external_verifiers = None def test_53_external_verifiers(self): "test encrypt() output verifies against external libs" # this makes sure our output can be verified by external libs, # to avoid repeat of things like issue 25. handler = self.handler possible = False if self.iter_external_verifiers: helpers = list(self.iter_external_verifiers()) possible = True else: helpers = [] # provide default "os_crypt" helper if hasattr(handler, "has_backend") and \ 'os_crypt' in handler.backends and \ not hasattr(handler, "orig_prefix"): possible = True if handler.has_backend("os_crypt"): def check_crypt(secret, hash): self.assertEqual(utils.os_crypt(secret, hash), hash, "os_crypt(%r,%r):" % (secret, hash)) helpers.append(check_crypt) if not helpers: if possible: raise self.skipTest("no external libs available") else: raise self.skipTest("not applicable") # generate a single hash, and verify it using all helpers. secret = 't\xc3\xa1\xd0\x91\xe2\x84\x93\xc9\x99' hash = self.do_encrypt(secret) for helper in helpers: helper(secret, hash) #========================================================= #test max password size #========================================================= def test_60_secret_chars(self): "test secret_chars limit" sc = self.secret_chars base = "too many secrets" #16 chars alt = 'x' #char that's not in base string if sc > 0: #hash only counts the first characters #eg: bcrypt, des-crypt #create & hash something of exactly sc+1 chars secret = (base * (1+sc//16))[:sc+1] assert len(secret) == sc+1 hash = self.do_encrypt(secret) #check sc value isn't too large #by verifying that sc-1'th char affects hash self.assertTrue(not self.do_verify(secret[:-2] + alt + secret[-1], hash), "secret_chars value is too large") #check sc value isn't too small #by verifying adding sc'th char doesn't affect hash self.assertTrue(self.do_verify(secret[:-1] + alt, hash)) else: #hash counts all characters #eg: md5-crypt self.assertEqual(sc, -1) #NOTE: this doesn't do an exhaustive search to verify algorithm #doesn't have some cutoff point, it just tries #1024-character string, and alters the last char. #as long as algorithm doesn't clip secret at point <1024, #the new secret shouldn't verify. secret = base * 64 hash = self.do_encrypt(secret) self.assertTrue(not self.do_verify(secret[:-1] + alt, hash)) #========================================================= #eoc #========================================================= #========================================================= #backend test helpers #========================================================= def _enable_backend_case(handler, backend): "helper to check if testcase should be enabled for the specified backend" assert backend in handler.backends, "unknown backend: %r" % (backend,) if enable_option("all-backends") or _is_default_backend(handler, backend): if handler.has_backend(backend): return True, None if backend == "os_crypt" and utils.safe_os_crypt: if enable_option("cover") and _has_other_backends(handler, "os_crypt"): #in this case, HandlerCase will monkeypatch os_crypt #to use another backend, just so we can test os_crypt fully. return True, None else: return False, "hash not supported by os crypt()" else: return False, "backend not available" else: return False, "only default backend being tested" def _is_default_backend(handler, name): "check if backend is the default for handler" try: orig = handler.get_backend() except MissingBackendError: return False try: return handler.set_backend("default") == name finally: handler.set_backend(orig) def _has_other_backends(handler, ignore): "helper to check if alternate backend is available" for name in handler.backends: if name != ignore and handler.has_backend(name): return name return None def create_backend_case(base, name, module="passlib.tests.test_drivers"): "create a test case for specific backend of a multi-backend handler" #get handler, figure out if backend should be tested handler = base.handler assert hasattr(handler, "backends"), "handler must support uh.HasManyBackends protocol" enable, reason = _enable_backend_case(handler, name) #UT1 doesn't support skipping whole test cases, #so we just return None. if not enable and ut_version < 2: return None #make classname match what it's stored under, to be tidy cname = name.title().replace("_","") + "_" + base.__name__.lstrip("_") #create subclass of 'base' which uses correct backend subcase = type( cname, (base,), dict( case_prefix = "%s (%s backend)" % (handler.name, name), backend = name, __module__=module, ) ) if not enable: subcase = unittest.skip(reason)(subcase) return subcase #========================================================= #misc helpers #========================================================= class dummy_handler_in_registry(object): "context manager that inserts dummy handler in registry" def __init__(self, name): self.name = name self.dummy = type('dummy_' + name, (uh.GenericHandler,), dict( name=name, setting_kwds=(), )) def __enter__(self): registry._unload_handler_name(self.name, locations=False) registry.register_crypt_handler(self.dummy) assert registry.get_crypt_handler(self.name) is self.dummy return self.dummy def __exit__(self, *exc_info): registry._unload_handler_name(self.name, locations=False) #========================================================= #helper for creating temp files - all cleaned up when prog exits #========================================================= tmp_files = [] def _clean_tmp_files(): for path in tmp_files: if os.path.exists(path): os.remove(path) atexit.register(_clean_tmp_files) def mktemp(*args, **kwds): fd, path = tempfile.mkstemp(*args, **kwds) tmp_files.append(path) os.close(fd) return path #========================================================= #make sure catch_warnings() is available #========================================================= try: from warnings import catch_warnings except ImportError: #catch_warnings wasn't added until py26. #this adds backported copy from py26's stdlib #so we can use it under py25. class WarningMessage(object): """Holds the result of a single showwarning() call.""" _WARNING_DETAILS = ("message", "category", "filename", "lineno", "file", "line") def __init__(self, message, category, filename, lineno, file=None, line=None): local_values = locals() for attr in self._WARNING_DETAILS: setattr(self, attr, local_values[attr]) self._category_name = category.__name__ if category else None def __str__(self): return ("{message : %r, category : %r, filename : %r, lineno : %s, " "line : %r}" % (self.message, self._category_name, self.filename, self.lineno, self.line)) class catch_warnings(object): """A context manager that copies and restores the warnings filter upon exiting the context. The 'record' argument specifies whether warnings should be captured by a custom implementation of warnings.showwarning() and be appended to a list returned by the context manager. Otherwise None is returned by the context manager. The objects appended to the list are arguments whose attributes mirror the arguments to showwarning(). The 'module' argument is to specify an alternative module to the module named 'warnings' and imported under that name. This argument is only useful when testing the warnings module itself. """ def __init__(self, record=False, module=None): """Specify whether to record warnings and if an alternative module should be used other than sys.modules['warnings']. For compatibility with Python 3.0, please consider all arguments to be keyword-only. """ self._record = record self._module = sys.modules['warnings'] if module is None else module self._entered = False def __repr__(self): args = [] if self._record: args.append("record=True") if self._module is not sys.modules['warnings']: args.append("module=%r" % self._module) name = type(self).__name__ return "%s(%s)" % (name, ", ".join(args)) def __enter__(self): if self._entered: raise RuntimeError("Cannot enter %r twice" % self) self._entered = True self._filters = self._module.filters self._module.filters = self._filters[:] self._showwarning = self._module.showwarning if self._record: log = [] def showwarning(*args, **kwargs): log.append(WarningMessage(*args, **kwargs)) self._module.showwarning = showwarning return log else: return None def __exit__(self, *exc_info): if not self._entered: raise RuntimeError("Cannot exit %r without entering first" % self) self._module.filters = self._filters self._module.showwarning = self._showwarning #========================================================= #EOF #========================================================= passlib-1.5.3/passlib/tests/test_win32.py0000644000175000017500000000260311643466373021473 0ustar biscuitbiscuit00000000000000"""tests for passlib.win32 -- (c) Assurance Technologies 2003-2009""" #========================================================= #imports #========================================================= #core from binascii import hexlify #site #pkg from passlib.tests.utils import TestCase #module import passlib.win32 as mod #========================================================= # #========================================================= class UtilTest(TestCase): "test util funcs in passlib.win32" ##test hashes from http://msdn.microsoft.com/en-us/library/cc245828(v=prot.10).aspx ## among other places def test_lmhash(self): for secret, hash in [ ("OLDPASSWORD", u"c9b81d939d6fd80cd408e6b105741864"), ("NEWPASSWORD", u'09eeab5aa415d6e4d408e6b105741864'), ("welcome", u"c23413a8a1e7665faad3b435b51404ee"), ]: result = mod.raw_lmhash(secret, hex=True) self.assertEqual(result, hash) def test_nthash(self): for secret, hash in [ ("OLDPASSWORD", u"6677b2c394311355b54f25eec5bfacf5"), ("NEWPASSWORD", u"256781a62031289d3c2c98c14f1efc8c"), ]: result = mod.raw_nthash(secret, hex=True) self.assertEqual(result, hash) #========================================================= #EOF #========================================================= passlib-1.5.3/passlib/apache.py0000644000175000017500000004352111643466373017555 0ustar biscuitbiscuit00000000000000"""passlib.apache - apache password support""" #========================================================= #imports #========================================================= from __future__ import with_statement #core from hashlib import md5 import logging; log = logging.getLogger(__name__) import os import sys #site #libs from passlib.context import CryptContext from passlib.utils import render_bytes, bjoin, bytes, b, to_unicode, to_bytes #pkg #local __all__ = [ ] BCOLON = b(":") #========================================================= #common helpers #========================================================= DEFAULT_ENCODING = "utf-8" if sys.version_info >= (3,0) else None class _CommonFile(object): "helper for HtpasswdFile / HtdigestFile" #XXX: would like to add 'path' keyword to load() / save(), # but that makes .mtime somewhat meaningless. # to simplify things, should probably deprecate mtime & force=False # options. #XXX: would also like to make _load_string available via public interface, # such as via 'content' keyword in load() method. # in short, need to clean up the htpasswd api a little bit in 1.6. # keeping _load_string private for now, cause just using it for UTing. #NOTE: 'path' is a property instead of attr, # so that .mtime is wiped whenever path is changed. _path = None def _get_path(self): return self._path def _set_path(self, path): if path != self._path: self.mtime = 0 self._path = path path = property(_get_path, _set_path) @classmethod def _from_string(cls, content, **kwds): #NOTE: not public yet, just using it for unit tests. self = cls(**kwds) self._load_string(content) return self def __init__(self, path=None, autoload=True, encoding=DEFAULT_ENCODING, ): if encoding and u":\n".encode(encoding) != b(":\n"): #rest of file assumes ascii bytes, and uses ":" as separator. raise ValueError, "encoding must be 7-bit ascii compatible" self.encoding = encoding self.path = path ##if autoload == "exists": ## autoload = bool(path and os.path.exists(path)) if autoload and path: self.load() ##elif raw: ## self._load_lines(raw.split("\n")) else: self._entry_order = [] self._entry_map = {} def _load_string(self, content): """UT helper for loading from string to be improved/made public in later release. :param content: if specified, should be a bytes object. passwords will be loaded directly from this string, and any files will be ignored. """ if isinstance(content, unicode): content = content.encode(self.encoding or 'utf-8') self.mtime = 0 #XXX: replace this with iterator? lines = content.splitlines() self._load_lines(lines) return True def load(self, force=True): """load entries from file :param force: if ``True`` (the default), always loads state from file. if ``False``, only loads state if file has been modified since last load. :raises IOError: if file not found :returns: ``False`` if ``force=False`` and no load performed; otherwise ``True``. """ path = self.path if not path: raise RuntimeError("no load path specified") if not force and self.mtime and self.mtime == os.path.getmtime(path): return False with open(path, "rb") as fh: self.mtime = os.path.getmtime(path) self._load_lines(fh) return True def _load_lines(self, lines): pl = self._parse_line entry_order = self._entry_order = [] entry_map = self._entry_map = {} for line in lines: #XXX: found mention that "#" comment lines may be supported by htpasswd, # should verify this. key, value = pl(line) if key in entry_map: #XXX: should we use data from first entry, or last entry? # going w/ first entry for now. continue entry_order.append(key) entry_map[key] = value #subclass: _parse_line(line) -> (key, hash) def _iter_lines(self): "iterator yielding lines of database" rl = self._render_line entry_order = self._entry_order entry_map = self._entry_map assert len(entry_order) == len(entry_map), "internal error in entry list" return (rl(key, entry_map[key]) for key in entry_order) def save(self): "save entries to file" if not self.path: raise RuntimeError("no save path specified") with open(self.path, "wb") as fh: fh.writelines(self._iter_lines()) self.mtime = os.path.getmtime(self.path) def to_string(self): "export whole database as a byte string" return bjoin(self._iter_lines()) #subclass: _render_line(entry) -> line def _update_key(self, key, value): entry_map = self._entry_map if key in entry_map: entry_map[key] = value return True else: self._entry_order.append(key) entry_map[key] = value return False def _delete_key(self, key): entry_map = self._entry_map if key in entry_map: del entry_map[key] self._entry_order.remove(key) return True else: return False invalid_chars = b(":\n\r\t\x00") def _norm_user(self, user): "encode user to bytes, validate against format requirements" return self._norm_ident(user, errname="user") def _norm_realm(self, realm): "encode realm to bytes, validate against format requirements" return self._norm_ident(realm, errname="realm") def _norm_ident(self, ident, errname="user/realm"): ident = self._encode_ident(ident, errname) if len(ident) > 255: raise ValueError("%s must be at most 255 characters: %r" % (errname, ident)) if any(c in self.invalid_chars for c in ident): raise ValueError("%s contains invalid characters: %r" % (errname, ident,)) return ident def _encode_ident(self, ident, errname="user/realm"): "ensure identifier is bytes encoded using specified encoding, or rejected" encoding = self.encoding if encoding: if isinstance(ident, unicode): return ident.encode(encoding) raise TypeError("%s must be unicode, not %s" % (errname, type(ident))) else: if isinstance(ident, bytes): return ident raise TypeError("%s must be bytes, not %s" % (errname, type(ident))) def _decode_ident(self, ident, errname="user/realm"): "decode an identifier (if encoding is specified, else return encoded bytes)" assert isinstance(ident, bytes) encoding = self.encoding if encoding: return ident.decode(encoding) else: return ident #FIXME: htpasswd doc sez passwords limited to 255 chars under Windows & MPE, # longer ones are truncated. may be side-effect of those platforms # supporting plaintext. we don't currently check for this. #========================================================= #htpasswd editing #========================================================= #FIXME: apr_md5_crypt technically the default only for windows, netware and tpf. #TODO: find out if htpasswd's "crypt" mode is crypt *call* or just des_crypt implementation. htpasswd_context = CryptContext([ "apr_md5_crypt", #man page notes supported everywhere, default on Windows, Netware, TPF "des_crypt", #man page notes server does NOT support this on Windows, Netware, TPF "ldap_sha1", #man page notes only for transitioning <-> ldap "plaintext" # man page notes server ONLY supports this on Windows, Netware, TPF ]) class HtpasswdFile(_CommonFile): """class for reading & writing Htpasswd files. :arg path: path to htpasswd file to load from / save to (required) :param default: optionally specify default scheme to use when encoding new passwords. Must be one of ``None``, ``"apr_md5_crypt"``, ``"des_crypt"``, ``"ldap_sha1"``, ``"plaintext"``. If no value is specified, this class currently uses ``apr_md5_crypt`` when creating new passwords. :param autoload: if ``True`` (the default), :meth:`load` will be automatically called by constructor. Set to ``False`` to disable automatic loading (primarily used when creating new htdigest file). :param encoding: optionally specify encoding used for usernames. if set to ``None``, user names must be specified as bytes, and will be returned as bytes. if set to an encoding, user names must be specified as unicode, and will be returned as unicode. when stored, then will use the specified encoding. for backwards compatibility with passlib 1.4, this defaults to ``None`` under Python 2, and ``utf-8`` under Python 3. .. note:: this is not the encoding for the entire file, just for the usernames within the file. this must be an encoding which is compatible with 7-bit ascii (which is used by rest of file). :param context: :class:`~passlib.context.CryptContext` instance used to handle hashes in this file. .. warning:: this should usually be left at the default, though it can be overridden to implement non-standard hashes within the htpasswd file. Loading & Saving ================ .. automethod:: load .. automethod:: save .. automethod:: to_string Inspection ================ .. automethod:: users .. automethod:: verify Modification ================ .. automethod:: update .. automethod:: delete .. note:: All of the methods in this class enforce some data validation on the ``user`` parameter: they will raise a :exc:`ValueError` if the string contains one of the forbidden characters ``:\\r\\n\\t\\x00``, or is longer than 255 characters. """ def __init__(self, path=None, default=None, context=htpasswd_context, **kwds): self.context = context if default: self.context = self.context.replace(default=default) super(HtpasswdFile, self).__init__(path, **kwds) def _parse_line(self, line): #should be user, hash return line.rstrip().split(BCOLON) def _render_line(self, user, hash): return render_bytes("%s:%s\n", user, hash) def users(self): "return list of all users in file" return map(self._decode_ident, self._entry_order) def update(self, user, password): """update password for user; adds user if needed. :returns: ``True`` if existing user was updated, ``False`` if user added. """ user = self._norm_user(user) hash = self.context.encrypt(password) return self._update_key(user, hash) def delete(self, user): """delete user's entry. :returns: ``True`` if user deleted, ``False`` if user not found. """ user = self._norm_user(user) return self._delete_key(user) def verify(self, user, password): """verify password for specified user. :returns: * ``None`` if user not found * ``False`` if password does not match * ``True`` if password matches. """ user = self._norm_user(user) hash = self._entry_map.get(user) if hash is None: return None else: return self.context.verify(password, hash) #TODO: support migration from deprecated hashes #========================================================= #htdigest editing #========================================================= class HtdigestFile(_CommonFile): """class for reading & writing Htdigest files :arg path: path to htpasswd file to load from / save to (required) :param autoload: if ``True`` (the default), :meth:`load` will be automatically called by constructor. Set to ``False`` to disable automatic loading (primarily used when creating new htdigest file). :param encoding: optionally specify encoding used for usernames / realms. if set to ``None``, user names & realms must be specified as bytes, and will be returned as bytes. if set to an encoding, user names & realms must be specified as unicode, and will be returned as unicode. when stored, then will use the specified encoding. for backwards compatibility with passlib 1.4, this defaults to ``None`` under Python 2, and ``utf-8`` under Python 3. .. note:: this is not the encoding for the entire file, just for the usernames & realms within the file. this must be an encoding which is compatible with 7-bit ascii (which is used by rest of file). Loading & Saving ================ .. automethod:: load .. automethod:: save .. automethod:: to_string Inspection ========== .. automethod:: realms .. automethod:: users .. automethod:: find .. automethod:: verify Modification ============ .. automethod:: update .. automethod:: delete .. automethod:: delete_realm .. note:: All of the methods in this class enforce some data validation on the ``user`` and ``realm`` parameters: they will raise a :exc:`ValueError` if either string contains one of the forbidden characters ``:\\r\\n\\t\\x00``, or is longer than 255 characters. """ #XXX: don't want password encoding to change if user account encoding does. # but also *can't* use unicode itself. setting this to utf-8 for now, # until it causes problems - in which case stopgap of setting this attr # per-instance can be used. password_encoding = "utf-8" #XXX: provide rename() & rename_realm() ? def _parse_line(self, line): user, realm, hash = line.rstrip().split(BCOLON) return (user, realm), hash def _render_line(self, key, hash): return render_bytes("%s:%s:%s\n", key[0], key[1], hash) #TODO: would frontend to calc_digest be useful? ##def encrypt(self, password, user, realm): ## user = self._norm_user(user) ## realm = self._norm_realm(realm) ## hash = self._calc_digest(user, realm, password) ## if self.encoding: ## #decode hash if in unicode mode ## hash = hash.decode("ascii") ## return hash def _calc_digest(self, user, realm, password): "helper to calculate digest" if isinstance(password, unicode): password = password.encode(self.password_encoding) #NOTE: encode('ascii') is noop under py2, required under py3 return md5(render_bytes("%s:%s:%s", user, realm, password)).hexdigest().encode("ascii") def realms(self): "return all realms listed in file" return map(self._decode_ident, set(key[1] for key in self._entry_order)) def users(self, realm): "return list of all users within specified realm" realm = self._norm_realm(realm) return map(self._decode_ident, (key[0] for key in self._entry_order if key[1] == realm)) def update(self, user, realm, password): """update password for user under specified realm; adding user if needed :returns: ``True`` if existing user was updated, ``False`` if user added. """ user = self._norm_user(user) realm = self._norm_realm(realm) key = (user,realm) hash = self._calc_digest(user, realm, password) return self._update_key(key, hash) def delete(self, user, realm): """delete user's entry for specified realm. :returns: ``True`` if user deleted, ``False`` if user not found in realm. """ user = self._norm_user(user) realm = self._norm_realm(realm) return self._delete_key((user,realm)) def delete_realm(self, realm): """delete all users for specified realm :returns: number of users deleted """ realm = self._norm_realm(realm) keys = [ key for key in self._entry_map if key[1] == realm ] for key in keys: self._delete_key(key) return len(keys) def find(self, user, realm): """return digest hash for specified user+realm; returns ``None`` if not found :returns: htdigest hash or None :rtype: bytes or None """ user = self._norm_user(user) realm = self._norm_realm(realm) hash = self._entry_map.get((user,realm)) if hash is not None and self.encoding: #decode hash if in unicode mode hash = hash.decode("ascii") return hash def verify(self, user, realm, password): """verify password for specified user + realm. :returns: * ``None`` if user not found * ``False`` if password does not match * ``True`` if password matches. """ user = self._norm_user(user) realm = self._norm_realm(realm) hash = self._entry_map.get((user,realm)) if hash is None: return None return hash == self._calc_digest(user, realm, password) #========================================================= # eof #========================================================= passlib-1.5.3/passlib/apps.py0000644000175000017500000001126511643466373017277 0ustar biscuitbiscuit00000000000000"""passlib.apps""" #========================================================= #imports #========================================================= #core import logging; log = logging.getLogger(__name__) from itertools import chain #site #libs from passlib import hash from passlib.context import LazyCryptContext from passlib.utils import sys_bits #pkg #local __all__ = [ 'custom_app_context', 'django_context', 'ldap_context', 'ldap_nocrypt_context', 'mysql_context', 'mysql4_context', 'mysql3_context', 'phpass_context', 'phpbb3_context', 'postgres_context', ] #========================================================= #for quickly bootstrapping new custom applications #========================================================= custom_app_context = LazyCryptContext( #choose some reasonbly strong schemes schemes=["sha512_crypt", "sha256_crypt"], #set some useful global options all__vary_rounds = "10%", default="sha256_crypt" if sys_bits < 64 else "sha512_crypt", #set a good starting point for rounds selection sha512_crypt__default_rounds = 40000, sha256_crypt__default_rounds = 40000, #if the admin user category is selected, make a much stronger hash, admin__sha512_crypt__default_rounds = 80000, admin__sha256_crypt__default_rounds = 80000, ) #========================================================= #django #========================================================= django_context = LazyCryptContext( schemes=[ "django_salted_sha1", "django_salted_md5", "django_des_crypt", "hex_md5", "django_disabled", ], default="django_salted_sha1", deprecated=["hex_md5"], ) #========================================================= #ldap #========================================================= std_ldap_schemes = ["ldap_salted_sha1", "ldap_salted_md5", "ldap_sha1", "ldap_md5", "ldap_plaintext" ] #create context with all std ldap schemes EXCEPT crypt ldap_nocrypt_context = LazyCryptContext(std_ldap_schemes) #create context with all possible std ldap + ldap crypt schemes def _iter_ldap_crypt_schemes(): from passlib.utils import unix_crypt_schemes return ('ldap_' + name for name in unix_crypt_schemes) def _iter_ldap_schemes(): "helper which iterates over supported std ldap schemes" return chain(std_ldap_schemes, _iter_ldap_crypt_schemes()) ldap_context = LazyCryptContext(_iter_ldap_schemes()) ###create context with all std ldap schemes + crypt schemes for localhost ##def _iter_host_ldap_schemes(): ## "helper which iterates over supported std ldap schemes" ## from passlib.handlers.ldap_digests import get_host_ldap_crypt_schemes ## return chain(std_ldap_schemes, get_host_ldap_crypt_schemes()) ##ldap_host_context = LazyCryptContext(_iter_host_ldap_schemes()) #========================================================= #mysql #========================================================= mysql3_context = LazyCryptContext(["mysql323"]) mysql4_context = LazyCryptContext(["mysql41", "mysql323"], deprecated="mysql323") mysql_context = mysql4_context #tracks latest mysql version supported #========================================================= #postgres #========================================================= postgres_context = LazyCryptContext(["postgres_md5"]) #========================================================= #phpass & variants #========================================================= def _create_phpass_policy(**kwds): "helper to make bcrypt default ONLY if it's available" from passlib.context import default_policy if hash.bcrypt.has_backend(): kwds['default'] = 'bcrypt' return default_policy.replace(**kwds) phpass_context = LazyCryptContext( schemes=["bcrypt", "phpass", "bsdi_crypt"], default="phpass", #NOTE: <-- overridden by create_policy create_policy=_create_phpass_policy, ) phpbb3_context = LazyCryptContext(["phpass"], phpass__ident="H") #TODO: support the drupal phpass variants (see phpass homepage) #========================================================= #roundup #========================================================= _std_roundup_schemes = [ "ldap_hex_sha1", "ldap_hex_md5", "ldap_des_crypt", "roundup_plaintext" ] roundup10_context = LazyCryptContext(_std_roundup_schemes) #NOTE: 'roundup15' really applies to roundup 1.4.17+ roundup_context = roundup15_context = LazyCryptContext( schemes=_std_roundup_schemes + [ "ldap_pbkdf2_sha1" ], deprecated=_std_roundup_schemes, default = "ldap_pbkdf2_sha1", ldap_pbkdf2_sha1__default_rounds = 10000, ) #========================================================= # eof #========================================================= passlib-1.5.3/passlib/__init__.py0000644000175000017500000000013011643754032020050 0ustar biscuitbiscuit00000000000000"""passlib - suite of password hashing & generation routinges""" __version__ = '1.5.3' passlib-1.5.3/passlib/ext/0000755000175000017500000000000011643754212016545 5ustar biscuitbiscuit00000000000000passlib-1.5.3/passlib/ext/django/0000755000175000017500000000000011643754212020007 5ustar biscuitbiscuit00000000000000passlib-1.5.3/passlib/ext/django/models.py0000644000175000017500000000334211643754032021646 0ustar biscuitbiscuit00000000000000"""passlib.ext.django.models .. warning:: This code is experimental and subject to change, and not officially documented in Passlib just yet (though it should work). see the Passlib documentation for details on how to use this app """ #=================================================================== #imports #=================================================================== #site from django.conf import settings #pkg from passlib.context import CryptContext, CryptPolicy from passlib.utils import is_crypt_context, bytes from passlib.ext.django.utils import DEFAULT_CTX, get_category, \ set_django_password_context #=================================================================== #main #=================================================================== def patch(): #get config ctx = getattr(settings, "PASSLIB_CONTEXT", "passlib-default") catfunc = getattr(settings, "PASSLIB_GET_CATEGORY", get_category) #parse & validate input value if not ctx: # remove any patching that was already set, just in case. set_django_password_context(None) return if ctx == "passlib-default": ctx = DEFAULT_CTX if isinstance(ctx, (unicode, bytes)): ctx = CryptPolicy.from_string(ctx) if isinstance(ctx, CryptPolicy): ctx = CryptContext(policy=ctx) if not is_crypt_context(ctx): raise TypeError("django settings.PASSLIB_CONTEXT must be CryptContext instance or config string: %r" % (ctx,)) #monkeypatch django.contrib.auth.models:User set_django_password_context(ctx, get_category=catfunc) patch() #=================================================================== #eof #=================================================================== passlib-1.5.3/passlib/ext/django/__init__.py0000644000175000017500000000046311643466373022133 0ustar biscuitbiscuit00000000000000"""passlib.ext.django - Django app to monkeypatch better password hashing into django .. warning:: This code is experimental and subject to change, and not officially documented in Passlib just yet (though it should work). see the Passlib documentation for details on how to use this app """ passlib-1.5.3/passlib/ext/django/utils.py0000644000175000017500000002014411643466373021532 0ustar biscuitbiscuit00000000000000"""passlib.ext.django.utils - helper functions for patching Django hashing .. warning:: This code is experimental and subject to change, and not officially documented in Passlib just yet (though it should work). """ #=================================================================== #imports #=================================================================== #site from warnings import warn #pkg from passlib.utils import is_crypt_context, bytes #local __all__ = [ "get_category", "set_django_password_context", ] #=================================================================== #lazy imports #=================================================================== _has_django0 = None # old 0.9 django - lacks unusable_password support _dam = None #django.contrib.auth.models reference def _import_django(): global _dam, _has_django0 if _dam is None: import django.contrib.auth.models as _dam from django import VERSION _has_django0 = VERSION < (1,0) return _dam #=================================================================== #constants #=================================================================== #: base context mirroring django's setup STOCK_CTX = """ [passlib] schemes = django_salted_sha1, django_salted_md5, django_des_crypt, hex_md5, django_disabled default = django_salted_sha1 deprecated = hex_md5 """ #: default context used by app DEFAULT_CTX = """ [passlib] schemes = sha512_crypt, pbkdf2_sha256, django_salted_sha1, django_salted_md5, django_des_crypt, hex_md5, django_disabled default = sha512_crypt deprecated = pbkdf2_sha256, django_salted_sha1, django_salted_md5, django_des_crypt, hex_md5 all__vary_rounds = 5%% sha512_crypt__default_rounds = 15000 staff__sha512_crypt__default_rounds = 25000 superuser__sha512_crypt__default_rounds = 35000 """ #=================================================================== # helpers #=================================================================== def get_category(user): """default get_category() implementation used by set_django_password_context this is the function used if ``settings.PASSLIB_GET_CONTEXT`` is not specified. it maps superusers to the ``"superuser"`` category, staff to the ``"staff"`` category, and all others to the default category. """ if user.is_superuser: return "superuser" if user.is_staff: return "staff" return None def um(func): "unwrap method (eg User.set_password -> orig func)" return func.im_func #=================================================================== # monkeypatch framework #=================================================================== # NOTE: this moneypatcher was written to be useful # outside of this module, and re-invokable, # which is why it tries so hard to maintain # sanity about it's patch state. _django_patch_state = None #dict holding refs to undo patch def set_django_password_context(context=None, get_category=get_category): """monkeypatches :mod:`!django.contrib.auth` to use specified password context. :arg context: Passlib context to use for Django password hashing. If ``None``, restores original Django functions. In order to support existing hashes, any context specified should include all the hashes in :data:`django_context` in addition to custom hashes. :param get_category: Optional function to use when mapping Django user -> CryptContext category. If a function, should have syntax ``catfunc(user) -> category|None``. If ``None``, no function is used. By default, uses a function which returns ``"superuser"`` for superusers, and ``"staff"`` for staff. This function monkeypatches the following parts of Django: * :func:`!django.contrib.auth.models.check_password` * :meth:`!django.contrib.auth.models.User.check_password` * :meth:`!django.contrib.auth.models.User.set_password` It also stores the provided context in :data:`!django.contrib.auth.models.User.password_context`, for easy access. """ global _django_patch_state, _dam, _has_django0 _import_django() state = _django_patch_state User = _dam.User # issue warning if something else monkeypatched User # while our patch was applied. if state is not None: if um(User.set_password) is not state['user_set_password']: warn("another library has patched " "django.contrib.auth.models:User.set_password") if um(User.check_password) is not state['user_check_password']: warn("another library has patched" "django.contrib.auth.models:User.check_password") if _dam.check_password is not state['models_check_password']: warn("another library has patched" "django.contrib.auth.models:check_password") #check if we should just restore original state if context is None: if state is not None: del User.password_context _dam.check_password = state['orig_models_check_password'] User.set_password = state['orig_user_set_password'] User.check_password = state['orig_user_check_password'] _django_patch_state = None return #validate inputs if not is_crypt_context(context): raise TypeError("context must be CryptContext instance or None: %r" % (type(context),)) #backup original state if this is first call if state is None: _django_patch_state = state = dict( orig_user_check_password = um(User.check_password), orig_user_set_password = um(User.set_password), orig_models_check_password = _dam.check_password, ) #prepare replacements if _has_django0: UNUSABLE_PASSWORD = "!" else: UNUSABLE_PASSWORD = _dam.UNUSABLE_PASSWORD def set_password(user, raw_password): "passlib replacement for User.set_password()" if raw_password is None: if _has_django0: # django 0.9 user.password = UNUSABLE_PASSWORD else: user.set_unusable_password() else: cat = get_category(user) if get_category else None user.password = context.encrypt(raw_password, category=cat) def check_password(user, raw_password): "passlib replacement for User.check_password()" if raw_password is None: return False hash = user.password if not hash or hash == UNUSABLE_PASSWORD: return False cat = get_category(user) if get_category else None ok, new_hash = context.verify_and_update(raw_password, hash, category=cat) if ok and new_hash is not None: user.password = new_hash user.save() return ok def raw_check_password(raw_password, enc_password): "passlib replacement for check_password()" if not enc_password or enc_password == UNUSABLE_PASSWORD: raise ValueError("no password hash specified") return context.verify(raw_password, enc_password) #set new state User.password_context = context User.set_password = state['user_set_password'] = set_password User.check_password = state['user_check_password'] = check_password _dam.check_password = state['models_check_password'] = raw_check_password state['context' ] = context state['get_category'] = get_category ##def get_django_password_context(): ## """return current django password context ## ## This returns the current :class:`~passlib.context.CryptContext` instance ## set by :func:`set_django_password_context`. ## If not context has been set, returns ``None``. ## """ ## global _django_patch_state ## if _django_patch_state: ## return _django_patch_state['context'] ## else: ## return None #=================================================================== #eof #=================================================================== passlib-1.5.3/passlib/ext/__init__.py0000644000175000017500000000000111643466373020655 0ustar biscuitbiscuit00000000000000 passlib-1.5.3/passlib/handlers/0000755000175000017500000000000011643754212017545 5ustar biscuitbiscuit00000000000000passlib-1.5.3/passlib/handlers/oracle.py0000644000175000017500000001554111643466373021402 0ustar biscuitbiscuit00000000000000"""passlib.handlers.oracle - Oracle DB Password Hashes""" #========================================================= #imports #========================================================= #core from binascii import hexlify, unhexlify from hashlib import sha1 import re import logging; log = logging.getLogger(__name__) from warnings import warn #site #libs #pkg from passlib.utils import xor_bytes, handlers as uh, bytes, to_unicode, \ to_hash_str, b from passlib.utils.des import des_encrypt_block #local __all__ = [ "oracle10g", "oracle11g" ] #========================================================= #oracle10 #========================================================= def des_cbc_encrypt(key, value, iv=b('\x00') * 8, pad=b('\x00')): """performs des-cbc encryption, returns only last block. this performs a specific DES-CBC encryption implementation as needed by the Oracle10 hash. it probably won't be useful for other purposes as-is. input value is null-padded to multiple of 8 bytes. :arg key: des key as bytes :arg value: value to encrypt, as bytes. :param iv: optional IV :param pad: optional pad byte :returns: last block of DES-CBC encryption of all ``value``'s byte blocks. """ value += pad * (-len(value) % 8) #null pad to multiple of 8 hash = iv #start things off for offset in xrange(0,len(value),8): chunk = xor_bytes(hash, value[offset:offset+8]) hash = des_encrypt_block(key, chunk) return hash #: magic string used as initial des key by oracle10 ORACLE10_MAGIC = b("\x01\x23\x45\x67\x89\xAB\xCD\xEF") class oracle10(uh.StaticHandler): """This class implements the password hash used by Oracle up to version 10g, and follows the :ref:`password-hash-api`. It has no salt and a single fixed round. The :meth:`encrypt()` and :meth:`genconfig` methods accept no optional keywords. The :meth:`encrypt()`, :meth:`genhash()`, and :meth:`verify()` methods all require the following additional contextual keywords: :param user: string containing name of oracle user account this password is associated with. """ #========================================================= #algorithm information #========================================================= name = "oracle10" setting_kwds = () context_kwds = ("user",) #========================================================= #formatting #========================================================= _pat = re.compile(ur"^[0-9a-fA-F]{16}$") @classmethod def identify(cls, hash): return uh.identify_regexp(hash, cls._pat) #========================================================= #primary interface #========================================================= @classmethod def genhash(cls, secret, config, user): if config is not None and not cls.identify(config): raise ValueError("not an oracle-10g hash") if secret is None: raise TypeError("secret must be specified") if not user: raise ValueError("user keyword must be specified for this algorithm") #FIXME: not sure how oracle handles unicode. # online docs about 10g hash indicate it puts ascii chars # in a 2-byte encoding w/ the high bytenull. # they don't say how it handles other chars, # or what encoding. # # so for now, encoding secret & user to utf-16-be, # since that fits, # and if secret/user is bytes, we assume utf-8, and decode first. # # this whole mess really needs someone w/ an oracle system, # and some answers :) def encode(value): "encode according to guess at how oracle encodes strings (see note above)" #we can't trust what original encoding was. #user should have passed us unicode in the first place. #but try decoding as utf-8 just to work for most common case. value = to_unicode(value, "utf-8") return value.upper().encode("utf-16-be") input = encode(user) + encode(secret) hash = des_cbc_encrypt(ORACLE10_MAGIC, input) hash = des_cbc_encrypt(hash, input) return to_hash_str(hexlify(hash)).upper() @classmethod def _norm_hash(cls, hash): if isinstance(hash, bytes): hash = hash.decode("ascii") return hash.upper() #========================================================= #eoc #========================================================= #========================================================= #oracle11 #========================================================= class oracle11(uh.HasSalt, uh.GenericHandler): """This class implements the Oracle11g password hash, and follows the :ref:`password-hash-api`. It supports a fixed-length salt. 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 20 hexidecimal characters. """ #========================================================= #class attrs #========================================================= #--GenericHandler-- name = "oracle11" setting_kwds = ("salt",) checksum_size = 40 checksum_chars = uh.UC_HEX_CHARS _stub_checksum = u'0' * 40 #--HasSalt-- min_salt_size = max_salt_size = 20 salt_chars = uh.UC_HEX_CHARS #========================================================= #methods #========================================================= _pat = re.compile(u"^S:(?P[0-9a-f]{40})(?P[0-9a-f]{20})$", re.I) @classmethod def identify(cls, hash): return uh.identify_regexp(hash, cls._pat) @classmethod def from_string(cls, hash): if not hash: raise ValueError("no hash provided") if isinstance(hash, bytes): hash = hash.decode("ascii") m = cls._pat.match(hash) if not m: raise ValueError("invalid oracle-11g hash") salt, chk = m.group("salt", "chk") return cls(salt=salt, checksum=chk.upper(), strict=True) def to_string(self): chk = (self.checksum or self._stub_checksum) hash = u"S:%s%s" % (chk.upper(), self.salt.upper()) return to_hash_str(hash) def calc_checksum(self, secret): if isinstance(secret, unicode): secret = secret.encode("utf-8") chk = sha1(secret + unhexlify(self.salt.encode("ascii"))).hexdigest() return to_unicode(chk, 'ascii').upper() #========================================================= #eoc #========================================================= #========================================================= #eof #========================================================= passlib-1.5.3/passlib/handlers/mysql.py0000644000175000017500000001141211643466373021273 0ustar biscuitbiscuit00000000000000"""passlib.handlers.mysql MySQL 3.2.3 / OLD_PASSWORD() This implements Mysql's OLD_PASSWORD algorithm, introduced in version 3.2.3, deprecated in version 4.1. See :mod:`passlib.handlers.mysql_41` for the new algorithm was put in place in version 4.1 This algorithm is known to be very insecure, and should only be used to verify existing password hashes. http://djangosnippets.org/snippets/1508/ MySQL 4.1.1 / NEW PASSWORD This implements Mysql new PASSWORD algorithm, introduced in version 4.1. This function is unsalted, and therefore not very secure against rainbow attacks. It should only be used when dealing with mysql passwords, for all other purposes, you should use a salted hash function. Description taken from http://dev.mysql.com/doc/refman/6.0/en/password-hashing.html """ #========================================================= #imports #========================================================= #core from hashlib import sha1 import re import logging; log = logging.getLogger(__name__) from warnings import warn #site #libs #pkg from passlib.utils import handlers as uh, to_hash_str, b, bord, bytes #local __all__ = [ 'mysql323', 'mysq41', ] #========================================================= #backend #========================================================= class mysql323(uh.StaticHandler): """This class implements the MySQL 3.2.3 password hash, and follows the :ref:`password-hash-api`. It has no salt and a single fixed round. The :meth:`encrypt()` and :meth:`genconfig` methods accept no optional keywords. """ #========================================================= #class attrs #========================================================= name = "mysql323" checksum_chars = uh.HEX_CHARS _pat = re.compile(ur"^[0-9a-f]{16}$", re.I) #========================================================= #methods #========================================================= @classmethod def identify(cls, hash): return uh.identify_regexp(hash, cls._pat) @classmethod def genhash(cls, secret, config): if config is not None and not cls.identify(config): raise ValueError("not a mysql-3.2.3 hash") #FIXME: no idea if mysql has a policy about handling unicode passwords if isinstance(secret, unicode): secret = secret.encode("utf-8") MASK_32 = 0xffffffff MASK_31 = 0x7fffffff nr1 = 0x50305735 nr2 = 0x12345671 add = 7 for c in secret: if c in b(' \t'): continue tmp = bord(c) nr1 ^= ((((nr1 & 63)+add)*tmp) + (nr1 << 8)) & MASK_32 nr2 = (nr2+((nr2 << 8) ^ nr1)) & MASK_32 add = (add+tmp) & MASK_32 hash = u"%08x%08x" % (nr1 & MASK_31, nr2 & MASK_31) return to_hash_str(hash) @classmethod def _norm_hash(cls, hash): if isinstance(hash, bytes): hash = hash.decode("ascii") return hash.lower() #========================================================= #eoc #========================================================= #========================================================= #handler #========================================================= class mysql41(uh.StaticHandler): """This class implements the MySQL 4.1 password hash, and follows the :ref:`password-hash-api`. It has no salt and a single fixed round. The :meth:`encrypt()` and :meth:`genconfig` methods accept no optional keywords. """ #========================================================= #class attrs #========================================================= name = "mysql41" _pat = re.compile(r"^\*[0-9A-F]{40}$", re.I) #========================================================= #methods #========================================================= @classmethod def identify(cls, hash): return uh.identify_regexp(hash, cls._pat) @classmethod def genhash(cls, secret, config): if config is not None and not cls.identify(config): raise ValueError("not a mysql-4.1 hash") #FIXME: no idea if mysql has a policy about handling unicode passwords if isinstance(secret, unicode): secret = secret.encode("utf-8") return '*' + sha1(sha1(secret).digest()).hexdigest().upper() @classmethod def _norm_hash(cls, hash): if isinstance(hash, bytes): hash = hash.decode("ascii") return hash.upper() #========================================================= #eoc #========================================================= #========================================================= #eof #========================================================= passlib-1.5.3/passlib/handlers/bcrypt.py0000644000175000017500000002330011643754032021420 0ustar biscuitbiscuit00000000000000"""passlib.bcrypt Implementation of OpenBSD's BCrypt algorithm. PassLib will use the py-bcrypt package if it is available, otherwise it will fall back to a slower builtin pure-python implementation. Note that rounds must be >= 10 or an error will be returned. """ #========================================================= #imports #========================================================= from __future__ import with_statement, absolute_import #core import re import logging; log = logging.getLogger(__name__) from warnings import warn #site try: from bcrypt import hashpw as pybcrypt_hashpw except ImportError: #pragma: no cover - though should run whole suite w/o pybcrypt installed pybcrypt_hashpw = None try: from bcryptor.engine import Engine as bcryptor_engine except ImportError: #pragma: no cover - though should run whole suite w/o bcryptor installed bcryptor_engine = None #libs from passlib.utils import safe_os_crypt, classproperty, handlers as uh, \ h64, to_hash_str, rng, getrandstr, bytes #pkg #local __all__ = [ "bcrypt", ] # base64 character->value mapping used by bcrypt. # this is same as as H64_CHARS, but the positions are different. BCHARS = u"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" # last bcrypt salt char should have 4 padding bits set to 0. # thus, only the following chars are allowed: BSLAST = u".Oeu" BHLAST = u'.CGKOSWaeimquy26' #========================================================= #handler #========================================================= class bcrypt(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.HasManyBackends, uh.GenericHandler): """This class implements the BCrypt password 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 22 characters, drawn from the regexp range ``[./0-9A-Za-z]``. :param rounds: Optional number of rounds to use. Defaults to 12, must be between 4 and 31, inclusive. This value is logarithmic, the actual number of iterations used will be :samp:`2**{rounds}`. :param ident: selects specific version of BCrypt hash that will be used. Typically you want to leave this alone, and let it default to ``2a``, but it can be set to ``2`` to use the older version of BCrypt. It will use the first available of three possible backends: 1. `py-bcrypt `_, if installed. 2. `bcryptor `_, if installed. 3. stdlib's :func:`crypt.crypt()`, if the host OS supports BCrypt (eg: BSD). If no backends are available at runtime, :exc:`~passlib.utils.MissingBackendError` will be raised whenever :meth:`encrypt` or :meth:`verify` are called. You can see which backend is in use by calling the :meth:`~passlib.utils.handlers.HasManyBackends.get_backend()` method. """ #========================================================= #class attrs #========================================================= #--GenericHandler-- name = "bcrypt" setting_kwds = ("salt", "rounds", "ident") checksum_size = 31 checksum_chars = BCHARS #--HasManyIdents-- default_ident = u"$2a$" ident_values = (u"$2$", u"$2a$") ident_aliases = {u"2": u"$2$", u"2a": u"$2a$"} #--HasSalt-- min_salt_size = max_salt_size = 22 salt_chars = BCHARS #NOTE: 22nd salt char must be in BSLAST, not full BCHARS #--HasRounds-- default_rounds = 12 #current passlib default min_rounds = 4 # bcrypt spec specified minimum max_rounds = 31 # 32-bit integer limit (since real_rounds=1< ascii bytes (we override this) # unicode hash -> ascii bytes (we provide ascii bytes) # returns ascii bytes # py3: can't get to install if isinstance(secret, unicode): secret = secret.encode("utf-8") hash = pybcrypt_hashpw(secret, self.to_string(native=False)) return hash[-31:].decode("ascii") def _calc_checksum_bcryptor(self, secret): #bcryptor behavior: # py2: unicode secret -> ascii bytes (we have to override) # unicode hash -> ascii bytes (we provide ascii bytes) # returns ascii bytes # py3: can't get to install if isinstance(secret, unicode): secret = secret.encode("utf-8") hash = bcryptor_engine(False).hash_key(secret, self.to_string(native=False)) return hash[-31:].decode("ascii") #========================================================= #eoc #========================================================= #========================================================= #eof #========================================================= passlib-1.5.3/passlib/handlers/sha2_crypt.py0000644000175000017500000004234611643466373022216 0ustar biscuitbiscuit00000000000000"""passlib.handlers.sha2_crypt - SHA256/512-CRYPT""" #========================================================= #imports #========================================================= #core from hashlib import sha256, sha512 import re import logging; log = logging.getLogger(__name__) from warnings import warn #site #libs from passlib.utils import h64, safe_os_crypt, classproperty, handlers as uh, \ to_hash_str, to_unicode, bytes, b, bord #pkg #local __all__ = [ "SHA256Crypt", "SHA512Crypt", ] #========================================================= #pure-python backend (shared between sha256-crypt & sha512-crypt) #========================================================= INVALID_SALT_VALUES = b("\x00$") def raw_sha_crypt(secret, salt, rounds, hash): """perform raw sha crypt :arg secret: password to encode (if unicode, encoded to utf-8) :arg salt: salt string to use (required) :arg rounds: int rounds :arg hash: hash constructor function for 256/512 variant :returns: Returns tuple of ``(unencoded checksum, normalized salt, normalized rounds)``. """ #validate secret if not isinstance(secret, bytes): raise TypeError("secret must be encoded as bytes") #validate rounds if rounds < 1000: rounds = 1000 if rounds > 999999999: #pragma: no cover rounds = 999999999 #validate salt if not isinstance(salt, bytes): raise TypeError("salt must be encoded as bytes") if any(c in salt for c in INVALID_SALT_VALUES): raise ValueError("invalid chars in salt") if len(salt) > 16: salt = salt[:16] #init helpers def extend(source, size_ref): "helper which repeats digest string until it's the same length as string" assert len(source) == chunk_size size = len(size_ref) return source * int(size/chunk_size) + source[:size % chunk_size] #calc digest B b = hash(secret) chunk_size = b.digest_size #grab this once hash is created b.update(salt) a = b.copy() #make a copy to save a little time later b.update(secret) b_result = b.digest() b_extend = extend(b_result, secret) #begin digest A #a = hash(secret) <- performed above #a.update(salt) <- performed above a.update(b_extend) #for each bit in slen, add B or SECRET value = len(secret) while value > 0: if value % 2: a.update(b_result) else: a.update(secret) value >>= 1 #finish A a_result = a.digest() #calc DP - hash of password, extended to size of password dp = hash(secret * len(secret)) dp_result = extend(dp.digest(), secret) #calc DS - hash of salt, extended to size of salt ds = hash(salt * (16+bord(a_result[0]))) ds_result = extend(ds.digest(), salt) #aka 'S' # #calc digest C #NOTE: this has been contorted a little to allow pre-computing #some of the hashes. the original algorithm was that #each round generates digest composed of: # if round%2>0 => dp else lr # if round%3>0 => ds # if round%7>0 => dp # if round%2>0 => lr else dp #where lr is digest of the last round's hash (initially = a_result) # #pre-calculate some digests to speed up odd rounds dp_hash = hash(dp_result).copy dp_ds_hash = hash(dp_result + ds_result).copy dp_dp_hash = hash(dp_result * 2).copy dp_ds_dp_hash = hash(dp_result + ds_result + dp_result).copy #pre-calculate some strings to speed up even rounds ds_dp_result = ds_result + dp_result dp_dp_result = dp_result * 2 ds_dp_dp_result = ds_result + dp_dp_result #run through rounds last_result = a_result i = 0 while i < rounds: if i % 2: if i % 3: if i % 7: c = dp_ds_dp_hash() else: c = dp_ds_hash() elif i % 7: c = dp_dp_hash() else: c = dp_hash() c.update(last_result) else: c = hash(last_result) if i % 3: if i % 7: c.update(ds_dp_dp_result) else: c.update(ds_dp_result) elif i % 7: c.update(dp_dp_result) else: c.update(dp_result) last_result = c.digest() i += 1 #return unencoded result, along w/ normalized config values return last_result, salt, rounds def raw_sha256_crypt(secret, salt, rounds): "perform raw sha256-crypt; returns encoded checksum, normalized salt & rounds" #run common crypt routine result, salt, rounds = raw_sha_crypt(secret, salt, rounds, sha256) out = h64.encode_transposed_bytes(result, _256_offsets) assert len(out) == 43, "wrong length: %r" % (out,) return out, salt, rounds _256_offsets = ( 20, 10, 0, 11, 1, 21, 2, 22, 12, 23, 13, 3, 14, 4, 24, 5, 25, 15, 26, 16, 6, 17, 7, 27, 8, 28, 18, 29, 19, 9, 30, 31, ) def raw_sha512_crypt(secret, salt, rounds): "perform raw sha512-crypt; returns encoded checksum, normalized salt & rounds" #run common crypt routine result, salt, rounds = raw_sha_crypt(secret, salt, rounds, sha512) ###encode result out = h64.encode_transposed_bytes(result, _512_offsets) assert len(out) == 86, "wrong length: %r" % (out,) return out, salt, rounds _512_offsets = ( 42, 21, 0, 1, 43, 22, 23, 2, 44, 45, 24, 3, 4, 46, 25, 26, 5, 47, 48, 27, 6, 7, 49, 28, 29, 8, 50, 51, 30, 9, 10, 52, 31, 32, 11, 53, 54, 33, 12, 13, 55, 34, 35, 14, 56, 57, 36, 15, 16, 58, 37, 38, 17, 59, 60, 39, 18, 19, 61, 40, 41, 20, 62, 63, ) #========================================================= #handler #========================================================= class sha256_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler): """This class implements the SHA256-Crypt password hash, and follows the :ref:`password-hash-api`. It supports a variable-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 0-16 characters, drawn from the regexp range ``[./0-9A-Za-z]``. :param rounds: Optional number of rounds to use. Defaults to 40000, must be between 1000 and 999999999, inclusive. :param implicit_rounds: this is an internal option which generally doesn't need to be touched. this flag determines whether the hash should omit the rounds parameter when encoding it to a string; this is only permitted by the spec for rounds=5000, and the flag is ignored otherwise. the spec requires the two different encodings be preserved as they are, instead of normalizing them. It will use the first available of two possible backends: * stdlib :func:`crypt()`, if the host OS supports SHA256-Crypt. * a pure python implementation of SHA256-Crypt built into passlib. You can see which backend is in use by calling the :meth:`get_backend()` method. """ #========================================================= #algorithm information #========================================================= #--GenericHandler-- name = "sha256_crypt" setting_kwds = ("salt", "rounds", "implicit_rounds", "salt_size") ident = u"$5$" checksum_chars = uh.H64_CHARS #--HasSalt-- min_salt_size = 0 max_salt_size = 16 #TODO: allow salt charset 0-255 except for "\x00\n:$" salt_chars = uh.H64_CHARS #--HasRounds-- default_rounds = 40000 #current passlib default min_rounds = 1000 #other bounds set by spec max_rounds = 999999999 rounds_cost = "linear" #========================================================= #init #========================================================= def __init__(self, implicit_rounds=None, **kwds): if implicit_rounds is None: implicit_rounds = True self.implicit_rounds = implicit_rounds super(sha256_crypt, self).__init__(**kwds) #========================================================= #parsing #========================================================= #: regexp used to parse hashes _pat = re.compile(ur""" ^ \$5 (\$rounds=(?P\d+))? \$ ( (?P[^:$]*) | (?P[^:$]{0,16}) \$ (?P[A-Za-z0-9./]{43})? ) $ """, re.X) @classmethod def from_string(cls, hash): if not hash: raise ValueError("no hash specified") if isinstance(hash, bytes): hash = hash.decode("ascii") m = cls._pat.match(hash) if not m: raise ValueError("invalid sha256-crypt hash") rounds, salt1, salt2, chk = m.group("rounds", "salt1", "salt2", "chk") if rounds and rounds.startswith(u"0"): raise ValueError("invalid sha256-crypt hash (zero-padded rounds)") return cls( implicit_rounds = not rounds, rounds=int(rounds) if rounds else 5000, salt=salt1 or salt2, checksum=chk, strict=bool(chk), ) def to_string(self, native=True): if self.rounds == 5000 and self.implicit_rounds: hash = u"$5$%s$%s" % (self.salt, self.checksum or u'') else: hash = u"$5$rounds=%d$%s$%s" % (self.rounds, self.salt, self.checksum or u'') return to_hash_str(hash) if native else hash #========================================================= #backend #========================================================= backends = ("os_crypt", "builtin") _has_backend_builtin = True @classproperty def _has_backend_os_crypt(cls): h = u"$5$rounds=1000$test$QmQADEXMG8POI5WDsaeho0P36yK3Tcrgboabng6bkb/" return bool(safe_os_crypt and safe_os_crypt(u"test",h)[1]==h) def _calc_checksum_builtin(self, secret): if isinstance(secret, unicode): secret = secret.encode("utf-8") checksum, salt, rounds = raw_sha256_crypt(secret, self.salt.encode("ascii"), self.rounds) assert salt == self.salt.encode("ascii"), \ "class doesn't agree w/ builtin backend: salt %r != %r" % (salt, self.salt.encode("ascii")) assert rounds == self.rounds, \ "class doesn't agree w/ builtin backend: rounds %r != %r" % (rounds, self.rounds) return checksum.decode("ascii") def _calc_checksum_os_crypt(self, secret): ok, result = safe_os_crypt(secret, self.to_string(native=False)) if ok: #NOTE: avoiding full parsing routine via from_string().checksum, # and just extracting the bit we need. assert result.startswith(u"$5$") chk = result[-43:] assert u'$' not in chk return chk else: return self._calc_checksum_builtin(secret) #========================================================= #eoc #========================================================= #========================================================= #sha 512 crypt #========================================================= class sha512_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler): """This class implements the SHA512-Crypt password hash, and follows the :ref:`password-hash-api`. It supports a variable-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 0-16 characters, drawn from the regexp range ``[./0-9A-Za-z]``. :param rounds: Optional number of rounds to use. Defaults to 40000, must be between 1000 and 999999999, inclusive. :param implicit_rounds: this is an internal option which generally doesn't need to be touched. this flag determines whether the hash should omit the rounds parameter when encoding it to a string; this is only permitted by the spec for rounds=5000, and the flag is ignored otherwise. the spec requires the two different encodings be preserved as they are, instead of normalizing them. It will use the first available of two possible backends: * stdlib :func:`crypt()`, if the host OS supports SHA512-Crypt. * a pure python implementation of SHA512-Crypt built into passlib. You can see which backend is in use by calling the :meth:`get_backend()` method. """ #========================================================= #algorithm information #========================================================= name = "sha512_crypt" ident = u"$6$" checksum_chars = uh.H64_CHARS setting_kwds = ("salt", "rounds", "implicit_rounds", "salt_size") min_salt_size = 0 max_salt_size = 16 #TODO: allow salt charset 0-255 except for "\x00\n:$" salt_chars = uh.H64_CHARS default_rounds = 40000 #current passlib default min_rounds = 1000 max_rounds = 999999999 rounds_cost = "linear" #========================================================= #init #========================================================= def __init__(self, implicit_rounds=None, **kwds): if implicit_rounds is None: implicit_rounds = True self.implicit_rounds = implicit_rounds super(sha512_crypt, self).__init__(**kwds) #========================================================= #parsing #========================================================= #: regexp used to parse hashes _pat = re.compile(ur""" ^ \$6 (\$rounds=(?P\d+))? \$ ( (?P[^:$\n]*) | (?P[^:$\n]{0,16}) ( \$ (?P[A-Za-z0-9./]{86})? )? ) $ """, re.X) @classmethod def from_string(cls, hash): if not hash: raise ValueError("no hash specified") if isinstance(hash, bytes): hash = hash.decode("ascii") m = cls._pat.match(hash) if not m: raise ValueError("invalid sha512-crypt hash") rounds, salt1, salt2, chk = m.group("rounds", "salt1", "salt2", "chk") if rounds and rounds.startswith("0"): raise ValueError("invalid sha512-crypt hash (zero-padded rounds)") return cls( implicit_rounds = not rounds, rounds=int(rounds) if rounds else 5000, salt=salt1 or salt2, checksum=chk, strict=bool(chk), ) def to_string(self, native=True): if self.rounds == 5000 and self.implicit_rounds: hash = u"$6$%s$%s" % (self.salt, self.checksum or u'') else: hash = u"$6$rounds=%d$%s$%s" % (self.rounds, self.salt, self.checksum or u'') return to_hash_str(hash) if native else hash #========================================================= #backend #========================================================= backends = ("os_crypt", "builtin") _has_backend_builtin = True @classproperty def _has_backend_os_crypt(cls): h = u"$6$rounds=1000$test$2M/Lx6MtobqjLjobw0Wmo4Q5OFx5nVLJvmgseatA6oMnyWeBdRDx4DU.1H3eGmse6pgsOgDisWBGI5c7TZauS0" return bool(safe_os_crypt and safe_os_crypt(u"test",h)[1]==h) #NOTE: testing w/ HashTimer shows 64-bit linux's crypt to be ~2.6x faster than builtin (627253 vs 238152 rounds/sec) def _calc_checksum_builtin(self, secret): if isinstance(secret, unicode): secret = secret.encode("utf-8") checksum, salt, rounds = raw_sha512_crypt(secret, self.salt.encode("ascii"), self.rounds) assert salt == self.salt.encode("ascii"), \ "class doesn't agree w/ builtin backend: salt %r != %r" % (salt, self.salt.encode("ascii")) assert rounds == self.rounds, \ "class doesn't agree w/ builtin backend: rounds %r != %r" % (rounds, self.rounds) return checksum.decode("ascii") def _calc_checksum_os_crypt(self, secret): ok, result = safe_os_crypt(secret, self.to_string(native=False)) if ok: #NOTE: avoiding full parsing routine via from_string().checksum, # and just extracting the bit we need. assert result.startswith(u"$6$") chk = result[-86:] assert u'$' not in chk return chk else: return self._calc_checksum_builtin(secret) #========================================================= #eoc #========================================================= #========================================================= #eof #========================================================= passlib-1.5.3/passlib/handlers/nthash.py0000644000175000017500000000640011643466373021414 0ustar biscuitbiscuit00000000000000"""passlib.handlers.nthash - unix-crypt compatible nthash passwords""" #========================================================= #imports #========================================================= #core import re import logging; log = logging.getLogger(__name__) from warnings import warn #site #libs from passlib.utils import handlers as uh, to_unicode, to_hash_str, to_bytes, bytes from passlib.utils.md4 import md4 #pkg #local __all__ = [ "NTHash", ] #========================================================= #handler #========================================================= class nthash(uh.HasManyIdents, uh.GenericHandler): """This class implements the NT Password hash in a manner compatible with the :ref:`modular-crypt-format`, and follows the :ref:`password-hash-api`. It has no salt and a single fixed round. The :meth:`encrypt()` and :meth:`genconfig` methods accept no optional keywords. """ #TODO: verify where $NT$ is being used. ##:param ident: ##This handler supports two different :ref:`modular-crypt-format` identifiers. ##It defaults to ``3``, but users may specify the alternate ``NT`` identifier ##which is used in some contexts. #========================================================= #class attrs #========================================================= #--GenericHandler-- name = "nthash" setting_kwds = ("ident",) checksum_chars = uh.LC_HEX_CHARS _stub_checksum = u"0" * 32 #--HasManyIdents-- default_ident = u"$3$$" ident_values = (u"$3$$", u"$NT$") ident_aliases = {u"3": u"$3$$", u"NT": u"$NT$"} #========================================================= #formatting #========================================================= @classmethod def from_string(cls, hash): if not hash: raise ValueError("no hash specified") if isinstance(hash, bytes): hash = hash.decode("ascii") for ident in cls.ident_values: if hash.startswith(ident): break else: raise ValueError("invalid nthash") chk = hash[len(ident):] return cls(ident=ident, checksum=chk, strict=True) def to_string(self): hash = self.ident + (self.checksum or self._stub_checksum) return to_hash_str(hash) #========================================================= #primary interface #========================================================= def calc_checksum(self, secret): return self.raw_nthash(secret, hex=True) @staticmethod def raw_nthash(secret, hex=False): """encode password using md4-based NTHASH algorithm :returns: returns string of raw bytes if ``hex=False``, returns digest as hexidecimal unicode if ``hex=True``. """ secret = to_unicode(secret, "utf-8") hash = md4(secret.encode("utf-16le")) if hex: return to_unicode(hash.hexdigest(), 'ascii') else: return hash.digest() #========================================================= #eoc #========================================================= #========================================================= #eof #========================================================= passlib-1.5.3/passlib/handlers/ldap_digests.py0000644000175000017500000002016011643466373022570 0ustar biscuitbiscuit00000000000000"""passlib.handlers.digests - plain hash digests """ #========================================================= #imports #========================================================= #core from base64 import b64encode, b64decode from hashlib import md5, sha1 import logging; log = logging.getLogger(__name__) import re from warnings import warn #site #libs from passlib.utils import handlers as uh, unix_crypt_schemes, b, bytes, to_hash_str #pkg #local __all__ = [ "ldap_plaintext", "ldap_md5", "ldap_sha1", "ldap_salted_md5", "ldap_salted_sha1", ##"get_active_ldap_crypt_schemes", "ldap_des_crypt", "ldap_bsdi_crypt", "ldap_md5_crypt", "ldap_sha1_crypt" "ldap_bcrypt", "ldap_sha256_crypt", "ldap_sha512_crypt", ] #========================================================= #ldap helpers #========================================================= #reference - http://www.openldap.org/doc/admin24/security.html class _Base64DigestHelper(uh.StaticHandler): "helper for ldap_md5 / ldap_sha1" #XXX: could combine this with hex digests in digests.py ident = None #required - prefix identifier _hash_func = None #required - hash function _pat = None #required - regexp to recognize hash checksum_chars = uh.PADDED_B64_CHARS @classmethod def identify(cls, hash): return uh.identify_regexp(hash, cls._pat) @classmethod def genhash(cls, secret, hash): if secret is None: raise TypeError("no secret provided") if isinstance(secret, unicode): secret = secret.encode("utf-8") if hash is not None and not cls.identify(hash): raise ValueError("not a %s hash" % (cls.name,)) chk = cls._hash_func(secret).digest() hash = cls.ident + b64encode(chk).decode("ascii") return to_hash_str(hash) class _SaltedBase64DigestHelper(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): "helper for ldap_salted_md5 / ldap_salted_sha1" setting_kwds = ("salt",) checksum_chars = uh.PADDED_B64_CHARS ident = None #required - prefix identifier _hash_func = None #required - hash function _pat = None #required - regexp to recognize hash _stub_checksum = None #required - default checksum to plug in min_salt_size = max_salt_size = 4 @classmethod def identify(cls, hash): return uh.identify_regexp(hash, cls._pat) @classmethod def from_string(cls, hash): if not hash: raise ValueError("no hash specified") if isinstance(hash, bytes): hash = hash.decode('ascii') m = cls._pat.match(hash) if not m: raise ValueError("not a %s hash" % (cls.name,)) data = b64decode(m.group("tmp").encode("ascii")) chk, salt = data[:-4], data[-4:] return cls(checksum=chk, salt=salt, strict=True) def to_string(self): data = (self.checksum or self._stub_checksum) + self.salt hash = self.ident + b64encode(data).decode("ascii") return to_hash_str(hash) def calc_checksum(self, secret): if secret is None: raise TypeError("no secret provided") if isinstance(secret, unicode): secret = secret.encode("utf-8") return self._hash_func(secret + self.salt).digest() #========================================================= #implementations #========================================================= class ldap_md5(_Base64DigestHelper): """This class stores passwords using LDAP's plain MD5 format, and follows the :ref:`password-hash-api`. The :meth:`encrypt()` and :meth:`genconfig` methods have no optional keywords. """ name = "ldap_md5" setting_kwds = () ident = u"{MD5}" _hash_func = md5 _pat = re.compile(ur"^\{MD5\}(?P[+/a-zA-Z0-9]{22}==)$") class ldap_sha1(_Base64DigestHelper): """This class stores passwords using LDAP's plain SHA1 format, and follows the :ref:`password-hash-api`. The :meth:`encrypt()` and :meth:`genconfig` methods have no optional keywords. """ name = "ldap_sha1" setting_kwds = () ident = u"{SHA}" _hash_func = sha1 _pat = re.compile(ur"^\{SHA\}(?P[+/a-zA-Z0-9]{27}=)$") class ldap_salted_md5(_SaltedBase64DigestHelper): """This class stores passwords using LDAP's salted MD5 format, and follows the :ref:`password-hash-api`. It supports a 4-byte salt. The :meth:`encrypt()` and :meth:`genconfig` methods accept the following optional keyword: :param salt: Optional salt string. If not specified, one will be autogenerated (this is recommended). If specified, it must be a 4 byte string; each byte may have any value from 0x00 .. 0xff. """ name = "ldap_salted_md5" ident = u"{SMD5}" _hash_func = md5 _pat = re.compile(ur"^\{SMD5\}(?P[+/a-zA-Z0-9]{27}=)$") _stub_checksum = b('\x00') * 16 class ldap_salted_sha1(_SaltedBase64DigestHelper): """This class stores passwords using LDAP's salted SHA1 format, and follows the :ref:`password-hash-api`. It supports a 4-byte salt. The :meth:`encrypt()` and :meth:`genconfig` methods accept the following optional keyword: :param salt: Optional salt string. If not specified, one will be autogenerated (this is recommended). If specified, it must be a 4 byte string; each byte may have any value from 0x00 .. 0xff. """ name = "ldap_salted_sha1" ident = u"{SSHA}" _hash_func = sha1 _pat = re.compile(ur"^\{SSHA\}(?P[+/a-zA-Z0-9]{32})$") _stub_checksum = b('\x00') * 20 class ldap_plaintext(uh.StaticHandler): """This class stores passwords in plaintext, and follows the :ref:`password-hash-api`. This class acts much like the generic :class:`!passlib.hash.plaintext` handler, except that it will identify a hash only if it does NOT begin with the ``{XXX}`` identifier prefix used by RFC2307 passwords. Unicode passwords will be encoded using utf-8. """ name = "ldap_plaintext" _2307_pat = re.compile(ur"^\{\w+\}.*$") @classmethod def identify(cls, hash): if not hash: return False if isinstance(hash, bytes): try: hash = hash.decode("utf-8") except UnicodeDecodeError: return False #NOTE: identifies all strings EXCEPT those which match... return cls._2307_pat.match(hash) is None @classmethod def genhash(cls, secret, hash): if hash is not None and not cls.identify(hash): raise ValueError("not a valid ldap_plaintext hash") if secret is None: raise TypeError("secret must be string") return to_hash_str(secret, "utf-8") @classmethod def _norm_hash(cls, hash): if isinstance(hash, bytes): #XXX: current code uses utf-8 # if existing hashes use something else, # probably have to modify this code to allow hash_encoding # to be specified as an option. hash = hash.decode("utf-8") return hash #========================================================= #{CRYPT} wrappers #========================================================= # the following are wrappers around the base crypt algorithms, # which add the ldap required {CRYPT} prefix ldap_crypt_schemes = [ 'ldap_' + name for name in unix_crypt_schemes ] def _init_ldap_crypt_handlers(): #XXX: it's not nice to play in globals like this, # but don't want to write all all these handlers g = globals() for wname in unix_crypt_schemes: name = 'ldap_' + wname g[name] = uh.PrefixWrapper(name, wname, prefix=u"{CRYPT}", lazy=True) del g _init_ldap_crypt_handlers() ##_lcn_host = None ##def get_host_ldap_crypt_schemes(): ## global _lcn_host ## if _lcn_host is None: ## from passlib.hosts import host_context ## schemes = host_context.policy.schemes() ## _lcn_host = [ ## "ldap_" + name ## for name in unix_crypt_names ## if name in schemes ## ] ## return _lcn_host #========================================================= #eof #========================================================= passlib-1.5.3/passlib/handlers/phpass.py0000644000175000017500000001044511643753301021417 0ustar biscuitbiscuit00000000000000"""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, bsdi_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, handlers as uh, bytes, b, to_unicode, to_hash_str #pkg #local __all__ = [ "phpass", ] #========================================================= #phpass #========================================================= class phpass(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.GenericHandler): """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 #========================================================= #--GenericHandler-- name = "phpass" setting_kwds = ("salt", "rounds", "ident") checksum_chars = uh.H64_CHARS #--HasSalt-- min_salt_size = max_salt_size = 8 salt_chars = uh.H64_CHARS #--HasRounds-- default_rounds = 9 min_rounds = 7 max_rounds = 30 rounds_cost = "log2" _strict_rounds_bounds = True #--HasManyIdents-- default_ident = u"$P$" ident_values = [u"$P$", u"$H$"] ident_aliases = {u"P":u"$P$", u"H":u"$H$"} #========================================================= #formatting #========================================================= #$P$9IQRaTwmfeRo7ud9Fh4E2PdI0S3r.L0 # $P$ # 9 # IQRaTwmf # eRo7ud9Fh4E2PdI0S3r.L0 @classmethod def from_string(cls, hash): if not hash: raise ValueError("no hash specified") if isinstance(hash, bytes): hash = hash.decode('ascii') for ident in cls.ident_values: if hash.startswith(ident): break else: raise ValueError("invalid phpass portable hash") data = hash[len(ident):] rounds, salt, chk = data[0], data[1:9], data[9:] return cls( ident=ident, rounds=h64.decode_int6(rounds.encode("ascii")), salt=salt, checksum=chk, strict=bool(chk), ) def to_string(self): hash = u"%s%s%s%s" % (self.ident, h64.encode_int6(self.rounds).decode("ascii"), self.salt, self.checksum or u'') return to_hash_str(hash) #========================================================= #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< 8: salt = salt[:8] #primary hash = secret+id+salt+... h = md5(secret) h.update(B_APR_MAGIC if apr else B_MD5_MAGIC) h.update(salt) # primary hash - add len(secret) chars of tmp hash, # where temp hash is md5(secret+salt+secret) tmp = md5(secret + salt + secret).digest() assert len(tmp) == 16 slen = len(secret) h.update(tmp * (slen//16) + tmp[:slen % 16]) # primary hash - add null chars & first char of secret !?! # # this may have historically been a bug, # where they meant to use tmp[0] instead of '\x00', # but the code memclear'ed the buffer, # and now all implementations have to use this. # # sha-crypt replaced this step with # something more useful, anyways idx = len(secret) evenchar = secret[:1] while idx > 0: h.update(B_NULL if idx & 1 else evenchar) idx >>= 1 result = h.digest() #next: # do 1000 rounds of md5 to make things harder. # each round we do digest of round-specific content, # where content is formed from concatenation of... # secret if round % 2 else result # salt if round % 3 # secret if round % 7 # result if round % 2 else secret # #NOTE: # instead of doing this directly, this implementation # pre-computes all the combinations of strings & md5 hash objects # that will be needed, in order to perform round operations as fast as possible # (so that each round consists of one hash create/copy + 1 update + 1 digest) # #TODO: might be able to optimize even further by removing need for tests, since # if/then pattern is easily predicatble - # pattern is 7-0-1-0-3-0 (where 1 bit = mult 2, 2 bit = mult 3, 3 bit = mult 7) secret_secret = secret*2 salt_secret = salt+secret salt_secret_secret = salt + secret*2 secret_hash = md5(secret).copy secret_secret_hash = md5(secret_secret).copy secret_salt_hash = md5(secret+salt).copy secret_salt_secret_hash = md5(secret+salt_secret).copy for idx in xrange(1000): if idx & 1: if idx % 3: if idx % 7: h = secret_salt_secret_hash() else: h = secret_salt_hash() elif idx % 7: h = secret_secret_hash() else: h = secret_hash() h.update(result) else: h = md5(result) if idx % 3: if idx % 7: h.update(salt_secret_secret) else: h.update(salt_secret) elif idx % 7: h.update(secret_secret) else: h.update(secret) result = h.digest() #encode resulting hash return h64.encode_transposed_bytes(result, _chk_offsets).decode("ascii") _chk_offsets = ( 12,6,0, 13,7,1, 14,8,2, 15,9,3, 5,10,4, 11, ) #========================================================= #handler #========================================================= class _Md5Common(uh.HasSalt, uh.GenericHandler): "common code for md5_crypt and apr_md5_crypt" #========================================================= #algorithm information #========================================================= #--GenericHandler-- #name in subclass setting_kwds = ("salt", "salt_size") #ident in subclass checksum_size = 22 checksum_chars = uh.H64_CHARS #--HasSalt-- min_salt_size = 0 max_salt_size = 8 salt_chars = uh.H64_CHARS #========================================================= #internal helpers #========================================================= @classmethod def from_string(cls, hash): salt, chk = uh.parse_mc2(hash, cls.ident, cls.name) return cls(salt=salt, checksum=chk, strict=bool(chk)) def to_string(self): return uh.render_mc2(self.ident, self.salt, self.checksum) #========================================================= #primary interface #========================================================= #calc_checksum in subclass #========================================================= #eoc #========================================================= #========================================================= #handler #========================================================= class md5_crypt(uh.HasManyBackends, _Md5Common): """This class implements the MD5-Crypt password hash, and follows the :ref:`password-hash-api`. It supports a variable-length salt. 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 0-8 characters, drawn from the regexp range ``[./0-9A-Za-z]``. It will use the first available of two possible backends: * stdlib :func:`crypt()`, if the host OS supports MD5-Crypt. * a pure python implementation of MD5-Crypt built into passlib. You can see which backend is in use by calling the :meth:`get_backend()` method. """ #========================================================= #algorithm information #========================================================= name = "md5_crypt" ident = u"$1$" #========================================================= #primary interface #========================================================= #FIXME: can't find definitive policy on how md5-crypt handles non-ascii. # all backends currently coerce -> utf-8 backends = ("os_crypt", "builtin") _has_backend_builtin = True @classproperty def _has_backend_os_crypt(cls): h = u'$1$test$pi/xDtU5WFVRqYS6BMU8X/' return bool(safe_os_crypt and safe_os_crypt(u"test",h)[1]==h) def _calc_checksum_builtin(self, secret): return raw_md5_crypt(secret, self.salt) def _calc_checksum_os_crypt(self, secret): ok, hash = safe_os_crypt(secret, self.ident + self.salt) if ok: return hash[-22:] else: return self._calc_checksum_builtin(secret) #========================================================= #eoc #========================================================= #========================================================= #apache variant of md5-crypt #========================================================= class apr_md5_crypt(_Md5Common): """This class implements the Apr-MD5-Crypt password hash, and follows the :ref:`password-hash-api`. It supports a variable-length salt. 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 0-8 characters, drawn from the regexp range ``[./0-9A-Za-z]``. """ #========================================================= #algorithm information #========================================================= name = "apr_md5_crypt" ident = u"$apr1$" #========================================================= #primary interface #========================================================= def calc_checksum(self, secret): return raw_md5_crypt(secret, self.salt, apr=True) #========================================================= #eoc #========================================================= #========================================================= #eof #========================================================= passlib-1.5.3/passlib/handlers/sun_md5_crypt.py0000644000175000017500000003116611643466373022731 0ustar biscuitbiscuit00000000000000"""passlib.handlers.sun_md5_crypt - Sun's Md5 Crypt, used on Solaris .. warning:: This implementation may not reproduce the original Solaris behavior in some border cases. See documentation for details. """ #========================================================= #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, handlers as uh, to_hash_str, to_unicode, bytes, b, bord #pkg #local __all__ = [ "sun_md5_crypt", ] #========================================================= #backend #========================================================= #constant data used by alg - Hamlet act 3 scene 1 + null char # exact bytes as in http://www.ibiblio.org/pub/docs/books/gutenberg/etext98/2ws2610.txt # from Project Gutenberg. MAGIC_HAMLET = b( "To be, or not to be,--that is the question:--\n" "Whether 'tis nobler in the mind to suffer\n" "The slings and arrows of outrageous fortune\n" "Or to take arms against a sea of troubles,\n" "And by opposing end them?--To die,--to sleep,--\n" "No more; and by a sleep to say we end\n" "The heartache, and the thousand natural shocks\n" "That flesh is heir to,--'tis a consummation\n" "Devoutly to be wish'd. To die,--to sleep;--\n" "To sleep! perchance to dream:--ay, there's the rub;\n" "For in that sleep of death what dreams may come,\n" "When we have shuffled off this mortal coil,\n" "Must give us pause: there's the respect\n" "That makes calamity of so long life;\n" "For who would bear the whips and scorns of time,\n" "The oppressor's wrong, the proud man's contumely,\n" "The pangs of despis'd love, the law's delay,\n" "The insolence of office, and the spurns\n" "That patient merit of the unworthy takes,\n" "When he himself might his quietus make\n" "With a bare bodkin? who would these fardels bear,\n" "To grunt and sweat under a weary life,\n" "But that the dread of something after death,--\n" "The undiscover'd country, from whose bourn\n" "No traveller returns,--puzzles the will,\n" "And makes us rather bear those ills we have\n" "Than fly to others that we know not of?\n" "Thus conscience does make cowards of us all;\n" "And thus the native hue of resolution\n" "Is sicklied o'er with the pale cast of thought;\n" "And enterprises of great pith and moment,\n" "With this regard, their currents turn awry,\n" "And lose the name of action.--Soft you now!\n" "The fair Ophelia!--Nymph, in thy orisons\n" "Be all my sins remember'd.\n\x00" #<- apparently null at end of C string is included (test vector won't pass otherwise) ) #NOTE: these sequences are pre-calculated iteration ranges used by X & Y loops w/in rounds function below xr = range(7) _XY_ROUNDS = [ tuple((i,i,i+3) for i in xr), #xrounds 0 tuple((i,i+1,i+4) for i in xr), #xrounds 1 tuple((i,i+8,(i+11)&15) for i in xr), #yrounds 0 tuple((i,(i+9)&15, (i+12)&15) for i in xr), #yrounds 1 ] del xr def raw_sun_md5_crypt(secret, rounds, salt): "given secret & salt, return encoded sun-md5-crypt checksum" global MAGIC_HAMLET assert isinstance(secret, bytes) assert isinstance(salt, bytes) #validate rounds if rounds <= 0: rounds = 0 real_rounds = 4096 + rounds #NOTE: spec seems to imply max 'rounds' is 2**32-1 #generate initial digest to start off round 0. #NOTE: algorithm 'salt' includes full config string w/ trailing "$" result = md5(secret + salt).digest() assert len(result) == 16 #NOTE: many things have been inlined to speed up the loop as much as possible, # so that this only barely resembles the algorithm as described in the docs. # * all accesses to a given bit have been inlined using the formula # rbitval(bit) = (rval((bit>>3) & 15) >> (bit & 7)) & 1 # * the calculation of coinflip value R has been inlined # * the conditional division of coinflip value V has been inlined as a shift right of 0 or 1. # * the i, i+3, etc iterations are precalculated in lists. # * the round-based conditional division of x & y is now performed # by choosing an appropriate precalculated list, so only the 7 used bits # are actually calculated X_ROUNDS_0, X_ROUNDS_1, Y_ROUNDS_0, Y_ROUNDS_1 = _XY_ROUNDS #NOTE: % appears to be *slightly* slower than &, so we prefer & if possible round = 0 while round < real_rounds: #convert last result byte string to list of byte-ints for easy access rval = [ bord(c) for c in result ].__getitem__ #build up X bit by bit x = 0 xrounds = X_ROUNDS_1 if (rval((round>>3) & 15)>>(round & 7)) & 1 else X_ROUNDS_0 for i, ia, ib in xrounds: a = rval(ia) b = rval(ib) v = rval((a >> (b % 5)) & 15) >> ((b>>(a&7)) & 1) x |= ((rval((v>>3)&15)>>(v&7))&1) << i #build up Y bit by bit y = 0 yrounds = Y_ROUNDS_1 if (rval(((round+64)>>3) & 15)>>(round & 7)) & 1 else Y_ROUNDS_0 for i, ia, ib in yrounds: a = rval(ia) b = rval(ib) v = rval((a >> (b % 5)) & 15) >> ((b>>(a&7)) & 1) y |= ((rval((v>>3)&15)>>(v&7))&1) << i #extract x'th and y'th bit, xoring them together to yeild "coin flip" coin = ((rval(x>>3) >> (x&7)) ^ (rval(y>>3) >> (y&7))) & 1 #construct hash for this round h = md5(result) if coin: h.update(MAGIC_HAMLET) h.update(unicode(round).encode("ascii")) result = h.digest() round += 1 #encode output return h64.encode_transposed_bytes(result, _chk_offsets) #NOTE: same offsets as md5_crypt _chk_offsets = ( 12,6,0, 13,7,1, 14,8,2, 15,9,3, 5,10,4, 11, ) #========================================================= #handler #========================================================= class sun_md5_crypt(uh.HasRounds, uh.HasSalt, uh.GenericHandler): """This class implements the Sun-MD5-Crypt password hash, and follows the :ref:`password-hash-api`. It supports a variable-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, a salt will be autogenerated (this is recommended). If specified, it must be drawn from the regexp range ``[./0-9A-Za-z]``. :param salt_size: If no salt is specified, this parameter can be used to specify the size (in characters) of the autogenerated salt. It currently defaults to 8. :param rounds: Optional number of rounds to use. Defaults to 5000, must be between 0 and 4294963199, inclusive. :param bare_salt: Optional flag used to enable an alternate salt digest behavior used by some hash strings in this scheme. This flag can be ignored by most users. Defaults to ``False``. (see :ref:`smc-bare-salt` for details). """ #========================================================= #class attrs #========================================================= name = "sun_md5_crypt" setting_kwds = ("salt", "rounds", "bare_salt", "salt_size") checksum_chars = uh.H64_CHARS #NOTE: docs say max password length is 255. #release 9u2 #NOTE: not sure if original crypt has a salt size limit, # all instances that have been seen use 8 chars. default_salt_size = 8 min_salt_size = 0 max_salt_size = None salt_chars = uh.H64_CHARS default_rounds = 5000 #current passlib default min_rounds = 0 max_rounds = 4294963199 ##2**32-1-4096 #XXX: ^ not sure what it does if past this bound... does 32 int roll over? rounds_cost = "linear" _strict_rounds_bounds = True #========================================================= #instance attrs #========================================================= bare_salt = False #flag to indicate legacy hashes that lack "$$" suffix #========================================================= #constructor #========================================================= def __init__(self, bare_salt=False, **kwds): self.bare_salt = bare_salt super(sun_md5_crypt, self).__init__(**kwds) #========================================================= #internal helpers #========================================================= @classmethod def identify(cls, hash): return uh.identify_prefix(hash, (u"$md5$", u"$md5,")) @classmethod def from_string(cls, hash): if not hash: raise ValueError("no hash specified") if isinstance(hash, bytes): hash = hash.decode("ascii") # #detect if hash specifies rounds value. #if so, parse and validate it. #by end, set 'rounds' to int value, and 'tail' containing salt+chk # if hash.startswith(u"$md5$"): rounds = 0 salt_idx = 5 elif hash.startswith(u"$md5,rounds="): idx = hash.find(u"$", 12) if idx == -1: raise ValueError("invalid sun-md5-crypt hash (unexpected end of rounds)") rstr = hash[12:idx] try: rounds = int(rstr) except ValueError: raise ValueError("invalid sun-md5-crypt hash (bad rounds)") if rstr != unicode(rounds): raise ValueError("invalid sun-md5-crypt hash (zero-padded rounds)") if rounds == 0: #NOTE: not sure if this is *forbidden* precisely, # but allowing it would complicate things, # and it should never occur anyways. raise ValueError("invalid sun-md5-crypt hash (explicit zero rounds)") salt_idx = idx+1 else: raise ValueError("invalid sun-md5-crypt hash (unknown prefix)") # #salt/checksum separation is kinda weird, #to deal cleanly with some backward-compatible workarounds #implemented by original implementation. # chk_idx = hash.rfind(u"$", salt_idx) if chk_idx == -1: # ''-config for $-hash salt = hash[salt_idx:] chk = None bare_salt = True elif chk_idx == len(hash)-1: if chk_idx > salt_idx and hash[-2] == u"$": raise ValueError("invalid sun-md5-crypt hash (too many $)") # $-config for $$-hash salt = hash[salt_idx:-1] chk = None bare_salt = False elif chk_idx > 0 and hash[chk_idx-1] == u"$": # $$-hash salt = hash[salt_idx:chk_idx-1] chk = hash[chk_idx+1:] bare_salt = False else: # $-hash salt = hash[salt_idx:chk_idx] chk = hash[chk_idx+1:] bare_salt = True return cls( rounds=rounds, salt=salt, checksum=chk, bare_salt=bare_salt, strict=bool(chk), ) def to_string(self, withchk=True, native=True): ss = u'' if self.bare_salt else u'$' rounds = self.rounds if rounds > 0: out = u"$md5,rounds=%d$%s%s" % (rounds, self.salt, ss) else: out = u"$md5$%s%s" % (self.salt, ss) if withchk: chk = self.checksum if chk: out = u"%s$%s" % (out, chk) return to_hash_str(out) if native else out #========================================================= #primary interface #========================================================= #TODO: if we're on solaris, check for native crypt() support. # this will require extra testing, to make sure native crypt # actually behaves correctly. # especially, when using ''-config, make sure to append '$x' to string. def calc_checksum(self, secret): #NOTE: no reference for how sun_md5_crypt handles unicode if secret is None: raise TypeError("no secret specified") if isinstance(secret, unicode): secret = secret.encode("utf-8") config = self.to_string(withchk=False,native=False).encode("ascii") return raw_sun_md5_crypt(secret, self.rounds, config).decode("ascii") #========================================================= #eoc #========================================================= #========================================================= #eof #========================================================= passlib-1.5.3/passlib/handlers/digests.py0000644000175000017500000000562311643466373021577 0ustar biscuitbiscuit00000000000000"""passlib.handlers.digests - plain hash digests """ #========================================================= #imports #========================================================= #core import hashlib import logging; log = logging.getLogger(__name__) from warnings import warn #site #libs from passlib.utils import handlers as uh, to_hash_str, bytes from passlib.utils.md4 import md4 #pkg #local __all__ = [ "create_hex_hash", "hex_md4", "hex_md5", "hex_sha1", "hex_sha256", "hex_sha512", ] #========================================================= #helpers for hexidecimal hashes #========================================================= class HexDigestHash(uh.StaticHandler): "this provides a template for supporting passwords stored as plain hexidecimal hashes" _hash_func = None #required - hash function checksum_size = None #required - size of encoded digest checksum_chars = uh.HEX_CHARS @classmethod def identify(cls, hash): if not hash: return False if isinstance(hash, bytes): try: hash = hash.decode("ascii") except UnicodeDecodeError: return False cc = cls.checksum_chars return len(hash) == cls.checksum_size and all(c in cc for c in hash) @classmethod def genhash(cls, secret, hash): if hash is not None and not cls.identify(hash): raise ValueError("not a %s hash" % (cls.name,)) if secret is None: raise TypeError("no secret provided") if isinstance(secret, unicode): secret = secret.encode("utf-8") return to_hash_str(cls._hash_func(secret).hexdigest()) @classmethod def _norm_hash(cls, hash): if isinstance(hash, bytes): hash = hash.decode("ascii") return hash.lower() def create_hex_hash(hash, digest_name): #NOTE: could set digest_name=hash.name for cpython, but not for some other platforms. h = hash() name = "hex_" + digest_name return type(name, (HexDigestHash,), dict( name=name, _hash_func=staticmethod(hash), #sometimes it's a function, sometimes not. so wrap it. checksum_size=h.digest_size*2, __doc__="""This class implements a plain hexidecimal %s hash, and follows the :ref:`password-hash-api`. It supports no optional or contextual keywords. """ % (digest_name,) )) #========================================================= #predefined handlers #========================================================= hex_md4 = create_hex_hash(md4, "md4") hex_md5 = create_hex_hash(hashlib.md5, "md5") hex_sha1 = create_hex_hash(hashlib.sha1, "sha1") hex_sha256 = create_hex_hash(hashlib.sha256, "sha256") hex_sha512 = create_hex_hash(hashlib.sha512, "sha512") #========================================================= #eof #========================================================= passlib-1.5.3/passlib/handlers/des_crypt.py0000644000175000017500000004536411643466373022137 0ustar biscuitbiscuit00000000000000"""passlib.handlers.des_crypt - traditional unix (DES) crypt and variants .. note:: for des-crypt, passlib restricts salt characters to just the hash64 charset, and salt string size to >= 2 chars; since implementations of des-crypt vary in how they handle other characters / sizes... linux linux crypt() accepts salt characters outside the hash64 charset, and maps them using the following formula (determined by examining crypt's output): chr 0..64: v = (c-(1-19)) & 63 = (c+18) & 63 chr 65..96: v = (c-(65-12)) & 63 = (c+11) & 63 chr 97..127: v = (c-(97-38)) & 63 = (c+5) & 63 chr 128..255: same as c-128 invalid salt chars are mirrored back in the resulting hash. if the salt is too small, it uses a NUL char for the remaining character (which is treated the same as the char ``G``) when decoding the 12 bit salt. however, it outputs a hash string containing the single salt char twice, resulting in a corrupted hash. netbsd netbsd crypt() uses a 128-byte lookup table, which is only initialized for the hash64 values. the remaining values < 128 are implicitly zeroed, and values > 128 access past the array bounds (but seem to return 0). if the salt string is too small, it reads the NULL char (and continues past the end for bsdi crypt, though the buffer is usually large enough and NULLed). salt strings are output as provided, except for any NULs, which are converted to ``.``. openbsd, freebsd openbsd crypt() strictly defines the hash64 values as normal, and all other char values as 0. salt chars are reported as provided. if the salt or rounds string is too small, it'll read past the end, resulting in unpredictable values, though it'll terminate it's encoding of the output at the first null. this will generally result in a corrupted hash. """ #========================================================= #imports #========================================================= #core import re import logging; log = logging.getLogger(__name__) from warnings import warn #site #libs from passlib.utils import h64, classproperty, safe_os_crypt, b, bytes, \ to_hash_str, handlers as uh, bord from passlib.utils.des import mdes_encrypt_int_block #pkg #local __all__ = [ "des_crypt", "bsdi_crypt", "bigcrypt", "crypt16", ] #========================================================= #pure-python backend #========================================================= def _crypt_secret_to_key(secret): "crypt helper which converts lower 7 bits of first 8 chars of secret -> 56-bit des key, padded to 64 bits" return sum( (bord(c) & 0x7f) << (57-8*i) for i, c in enumerate(secret[:8]) ) def raw_crypt(secret, salt): "pure-python fallback if stdlib support not present" assert len(salt) == 2 #NOTE: technically could accept non-standard salts & single char salt, #but no official spec. try: salt_value = h64.decode_int12(salt) except ValueError: #pragma: no cover - always caught by class raise ValueError("invalid chars in salt") #FIXME: ^ this will throws error if bad salt chars are used # whereas linux crypt does something (inexplicable) with it #convert first 8 bytes of secret string into an integer key_value = _crypt_secret_to_key(secret) #run data through des using input of 0 result = mdes_encrypt_int_block(key_value, 0, salt_value, 25) #run h64 encode on result return h64.encode_dc_int64(result) def raw_ext_crypt(secret, rounds, salt): "ext_crypt() helper which returns checksum only" #decode salt try: salt_value = h64.decode_int24(salt) except ValueError: #pragma: no cover - always caught by class raise ValueError("invalid salt") #validate secret if b('\x00') in secret: #pragma: no cover - always caught by class #builtin linux crypt doesn't like this, so we don't either #XXX: would make more sense to raise ValueError, but want to be compatible w/ stdlib crypt raise ValueError("secret must be string without null bytes") #convert secret string into an integer key_value = _crypt_secret_to_key(secret) idx = 8 end = len(secret) while idx < end: next = idx+8 key_value = mdes_encrypt_int_block(key_value, key_value) ^ \ _crypt_secret_to_key(secret[idx:next]) idx = next #run data through des using input of 0 result = mdes_encrypt_int_block(key_value, 0, salt_value, rounds) #run h64 encode on result return h64.encode_dc_int64(result) #========================================================= #handler #========================================================= class des_crypt(uh.HasManyBackends, uh.HasSalt, uh.GenericHandler): """This class implements the des-crypt password hash, and follows the :ref:`password-hash-api`. It supports a fixed-length salt. 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 2 characters, drawn from the regexp range ``[./0-9A-Za-z]``. It will use the first available of two possible backends: * stdlib :func:`crypt()`, if the host OS supports des-crypt (most unix systems). * a pure python implementation of des-crypt You can see which backend is in use by calling the :meth:`get_backend()` method. """ #========================================================= #class attrs #========================================================= #--GenericHandler-- name = "des_crypt" setting_kwds = ("salt",) checksum_chars = uh.H64_CHARS #--HasSalt-- min_salt_size = max_salt_size = 2 salt_chars = uh.H64_CHARS #========================================================= #formatting #========================================================= #FORMAT: 2 chars of H64-encoded salt + 11 chars of H64-encoded checksum _pat = re.compile(ur""" ^ (?P[./a-z0-9]{2}) (?P[./a-z0-9]{11})? $""", re.X|re.I) @classmethod def identify(cls, hash): return uh.identify_regexp(hash, cls._pat) @classmethod def from_string(cls, hash): if not hash: raise ValueError("no hash specified") if isinstance(hash, bytes): hash = hash.decode("ascii") salt, chk = hash[:2], hash[2:] return cls(salt=salt, checksum=chk, strict=bool(chk)) def to_string(self, native=True): hash = u"%s%s" % (self.salt, self.checksum or u'') return to_hash_str(hash) if native else hash #========================================================= #backend #========================================================= backends = ("os_crypt", "builtin") _has_backend_builtin = True @classproperty def _has_backend_os_crypt(cls): h = u'abgOeLfPimXQo' return bool(safe_os_crypt and safe_os_crypt(u"test",h)[1]==h) def _calc_checksum_builtin(self, secret): #gotta do something - no official policy since des-crypt predates unicode if isinstance(secret, unicode): secret = secret.encode("utf-8") #forbidding nul chars because linux crypt (and most C implementations) won't accept it either. if b('\x00') in secret: raise ValueError("null char in secret") return raw_crypt(secret, self.salt.encode("ascii")).decode("ascii") def _calc_checksum_os_crypt(self, secret): #os_crypt() would raise less useful error null = u'\x00' if isinstance(secret, unicode) else b('\x00') if null in secret: raise ValueError("null char in secret") #NOTE: safe_os_crypt encodes unicode secret -> utf8 #no official policy since des-crypt predates unicode ok, hash = safe_os_crypt(secret, self.salt) if ok: return hash[2:] else: return self._calc_checksum_builtin(secret) #========================================================= #eoc #========================================================= #========================================================= #handler #========================================================= #FIXME: phpass code notes that even rounds values should be avoided for BSDI-Crypt, # so as not to reveal weak des keys. given the random salt, this shouldn't be # a very likely issue anyways, but should do something about default rounds generation anyways. class bsdi_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler): """This class implements the BSDi-Crypt password 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 4 characters, drawn from the regexp range ``[./0-9A-Za-z]``. :param rounds: Optional number of rounds to use. Defaults to 5000, must be between 0 and 16777215, inclusive. It will use the first available of two possible backends: * stdlib :func:`crypt()`, if the host OS supports bsdi-crypt (most BSD systems). * a pure python implementation of bsdi-crypt You can see which backend is in use by calling the :meth:`get_backend()` method. """ #========================================================= #class attrs #========================================================= #--GenericHandler-- name = "bsdi_crypt" setting_kwds = ("salt", "rounds") checksum_size = 11 checksum_chars = uh.H64_CHARS #--HasSalt-- min_salt_size = max_salt_size = 4 salt_chars = uh.H64_CHARS #--HasRounds-- default_rounds = 5001 min_rounds = 0 max_rounds = 16777215 # (1<<24)-1 rounds_cost = "linear" # NOTE: OpenBSD login.conf reports 7250 as minimum allowed rounds, # but that seems to be an OS policy, not a algorithm limitation. #========================================================= #internal helpers #========================================================= _pat = re.compile(ur""" ^ _ (?P[./a-z0-9]{4}) (?P[./a-z0-9]{4}) (?P[./a-z0-9]{11})? $""", re.X|re.I) @classmethod def identify(cls, hash): return uh.identify_regexp(hash, cls._pat) @classmethod def from_string(cls, hash): if not hash: raise ValueError("no hash specified") if isinstance(hash, bytes): hash = hash.decode("ascii") m = cls._pat.match(hash) if not m: raise ValueError("invalid ext-des-crypt hash") rounds, salt, chk = m.group("rounds", "salt", "chk") return cls( rounds=h64.decode_int24(rounds.encode("ascii")), salt=salt, checksum=chk, strict=bool(chk), ) def to_string(self, native=True): hash = u"_%s%s%s" % (h64.encode_int24(self.rounds).decode("ascii"), self.salt, self.checksum or u'') return to_hash_str(hash) if native else hash #========================================================= #backend #========================================================= backends = ("os_crypt", "builtin") _has_backend_builtin = True @classproperty def _has_backend_os_crypt(cls): h = u'_/...lLDAxARksGCHin.' return bool(safe_os_crypt and safe_os_crypt(u"test",h)[1]==h) def _calc_checksum_builtin(self, secret): if isinstance(secret, unicode): secret = secret.encode("utf-8") return raw_ext_crypt(secret, self.rounds, self.salt.encode("ascii")).decode("ascii") def _calc_checksum_os_crypt(self, secret): ok, hash = safe_os_crypt(secret, self.to_string(native=False)) if ok: return hash[9:] else: return self._calc_checksum_builtin(secret) #========================================================= #eoc #========================================================= #========================================================= # #========================================================= class bigcrypt(uh.HasSalt, uh.GenericHandler): """This class implements the BigCrypt password hash, and follows the :ref:`password-hash-api`. It supports a fixed-length salt. 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 22 characters, drawn from the regexp range ``[./0-9A-Za-z]``. """ #========================================================= #class attrs #========================================================= #--GenericHandler-- name = "bigcrypt" setting_kwds = ("salt",) checksum_chars = uh.H64_CHARS #NOTE: checksum chars must be multiple of 11 #--HasSalt-- min_salt_size = max_salt_size = 2 salt_chars = uh.H64_CHARS #========================================================= #internal helpers #========================================================= _pat = re.compile(ur""" ^ (?P[./a-z0-9]{2}) (?P[./a-z0-9]{11,})? $""", re.X|re.I) @classmethod def identify(cls, hash): if not hash: return False if isinstance(hash, bytes): try: hash = hash.decode("ascii") except UnicodeDecodeError: return False return bool(cls._pat.match(hash)) and (len(hash)-2) % 11 == 0 @classmethod def from_string(cls, hash): if not hash: raise ValueError("no hash specified") if isinstance(hash, bytes): hash = hash.decode("ascii") m = cls._pat.match(hash) if not m: raise ValueError("invalid bigcrypt hash") salt, chk = m.group("salt", "chk") return cls(salt=salt, checksum=chk, strict=bool(chk)) def to_string(self, native=True): hash = u"%s%s" % (self.salt, self.checksum or u'') return to_hash_str(hash) if native else hash @classmethod def norm_checksum(cls, value, strict=False): value = super(bigcrypt, cls).norm_checksum(value, strict=strict) if value and len(value) % 11: raise ValueError("invalid bigcrypt hash") return value #========================================================= #backend #========================================================= #TODO: check if os_crypt supports ext-des-crypt. def calc_checksum(self, secret): if isinstance(secret, unicode): secret = secret.encode("utf-8") chk = raw_crypt(secret, self.salt.encode("ascii")) idx = 8 end = len(secret) while idx < end: next = idx + 8 chk += raw_crypt(secret[idx:next], chk[-11:-9]) idx = next return chk.decode("ascii") #========================================================= #eoc #========================================================= #========================================================= # #========================================================= class crypt16(uh.HasSalt, uh.GenericHandler): """This class implements the crypt16 password hash, and follows the :ref:`password-hash-api`. It supports a fixed-length salt. 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 2 characters, drawn from the regexp range ``[./0-9A-Za-z]``. """ #========================================================= #class attrs #========================================================= #--GenericHandler-- name = "crypt16" setting_kwds = ("salt",) checksum_size = 22 checksum_chars = uh.H64_CHARS #--HasSalt-- min_salt_size = max_salt_size = 2 salt_chars = uh.H64_CHARS #========================================================= #internal helpers #========================================================= _pat = re.compile(ur""" ^ (?P[./a-z0-9]{2}) (?P[./a-z0-9]{22})? $""", re.X|re.I) @classmethod def identify(cls, hash): return uh.identify_regexp(hash, cls._pat) @classmethod def from_string(cls, hash): if not hash: raise ValueError("no hash specified") if isinstance(hash, bytes): hash = hash.decode("ascii") m = cls._pat.match(hash) if not m: raise ValueError("invalid crypt16 hash") salt, chk = m.group("salt", "chk") return cls(salt=salt, checksum=chk, strict=bool(chk)) def to_string(self, native=True): hash = u"%s%s" % (self.salt, self.checksum or u'') return to_hash_str(hash) if native else hash #========================================================= #backend #========================================================= #TODO: check if os_crypt supports ext-des-crypt. def calc_checksum(self, secret): if isinstance(secret, unicode): secret = secret.encode("utf-8") #parse salt value try: salt_value = h64.decode_int12(self.salt.encode("ascii")) except ValueError: #pragma: no cover - caught by class raise ValueError("invalid chars in salt") #convert first 8 byts of secret string into an integer, key1 = _crypt_secret_to_key(secret) #run data through des using input of 0 result1 = mdes_encrypt_int_block(key1, 0, salt_value, 20) #convert next 8 bytes of secret string into integer (key=0 if secret < 8 chars) key2 = _crypt_secret_to_key(secret[8:]) #run data through des using input of 0 result2 = mdes_encrypt_int_block(key2, 0, salt_value, 5) #done chk = h64.encode_dc_int64(result1) + h64.encode_dc_int64(result2) return chk.decode("ascii") #========================================================= #eoc #========================================================= #========================================================= #eof #========================================================= passlib-1.5.3/passlib/handlers/postgres.py0000644000175000017500000000503211643466373021775 0ustar biscuitbiscuit00000000000000"""passlib.handlers.postgres_md5 - MD5-based algorithm used by Postgres for pg_shadow table""" #========================================================= #imports #========================================================= #core from hashlib import md5 import re import logging; log = logging.getLogger(__name__) from warnings import warn #site #libs #pkg from passlib.utils import handlers as uh, to_unicode, to_hash_str, bytes, b #local __all__ = [ "postgres_md5", ] #========================================================= #handler #========================================================= class postgres_md5(uh.StaticHandler): """This class implements the Postgres MD5 Password hash, and follows the :ref:`password-hash-api`. It has no salt and a single fixed round. The :meth:`encrypt()` and :meth:`genconfig` methods accept no optional keywords. The :meth:`encrypt()`, :meth:`genhash()`, and :meth:`verify()` methods all require the following additional contextual keywords: :param user: string containing name of postgres user account this password is associated with. """ #========================================================= #algorithm information #========================================================= name = "postgres_md5" setting_kwds = () context_kwds = ("user",) #========================================================= #formatting #========================================================= _pat = re.compile(ur"^md5[0-9a-f]{32}$") @classmethod def identify(cls, hash): return uh.identify_regexp(hash, cls._pat) #========================================================= #primary interface #========================================================= @classmethod def genhash(cls, secret, config, user): if config is not None and not cls.identify(config): raise ValueError("not a postgres-md5 hash") if not user: raise ValueError("user keyword must be specified for this algorithm") if isinstance(secret, unicode): secret = secret.encode("utf-8") if isinstance(user, unicode): user = user.encode("utf-8") hash = u"md5" + to_unicode(md5(secret + user).hexdigest()) return to_hash_str(hash) #========================================================= #eoc #========================================================= #========================================================= #eof #========================================================= passlib-1.5.3/passlib/handlers/misc.py0000644000175000017500000000552511643466373021071 0ustar biscuitbiscuit00000000000000"""passlib.handlers.misc - misc generic handlers """ #========================================================= #imports #========================================================= #core import logging; log = logging.getLogger(__name__) from warnings import warn #site #libs from passlib.utils import to_hash_str, handlers as uh, bytes #pkg #local __all__ = [ "unix_fallback", "plaintext", ] #========================================================= #handler #========================================================= class unix_fallback(uh.StaticHandler): """This class provides the fallback behavior for unix shadow files, and follows the :ref:`password-hash-api`. This class does not implement a hash, but instead provides fallback behavior as found in /etc/shadow on most unix variants. If used, should be the last scheme in the context. * this class will positive identify all hash strings. * for security, newly encrypted passwords will hash to ``!``. * it rejects all passwords if the hash is NOT an empty string (``!`` or ``*`` are frequently used). * by default it rejects all passwords if the hash is an empty string, but if ``enable_wildcard=True`` is passed to verify(), all passwords will be allowed through if the hash is an empty string. """ name = "unix_fallback" context_kwds = ("enable_wildcard",) _stub_config = "!" @classmethod def identify(cls, hash): return hash is not None @classmethod def genhash(cls, secret, hash, enable_wildcard=False): if secret is None: raise TypeError("secret must be string") if hash is None: raise ValueError("no hash provided") return to_hash_str(hash) @classmethod def verify(cls, secret, hash, enable_wildcard=False): if hash is None: raise ValueError("no hash provided") return enable_wildcard and not hash class plaintext(uh.StaticHandler): """This class stores passwords in plaintext, and follows the :ref:`password-hash-api`. Unicode passwords will be encoded using utf-8. """ name = "plaintext" @classmethod def identify(cls, hash): return hash is not None @classmethod def genhash(cls, secret, hash): if secret is None: raise TypeError("secret must be string") return to_hash_str(secret, "utf-8") @classmethod def _norm_hash(cls, hash): if isinstance(hash, bytes): #XXX: current code uses utf-8 # if existing hashes use something else, # probably have to modify this code to allow hash_encoding # to be specified as an option. hash = hash.decode("utf-8") return hash #========================================================= #eof #========================================================= passlib-1.5.3/passlib/handlers/fshp.py0000644000175000017500000001466111643466373021077 0ustar biscuitbiscuit00000000000000"""passlib.handlers.fshp """ #========================================================= #imports #========================================================= #core from base64 import b64encode, b64decode import re import logging; log = logging.getLogger(__name__) from warnings import warn #site #libs from passlib.utils import handlers as uh, bytes, b, to_hash_str from passlib.utils.pbkdf2 import pbkdf1 #pkg #local __all__ = [ 'fshp', ] #========================================================= #sha1-crypt #========================================================= class fshp(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): """This class implements the FSHP password hash, and follows the :ref:`password-hash-api`. It supports a variable-length salt, and a variable number of rounds. The :meth:`encrypt()` and :meth:`genconfig` methods accept the following optional keywords: :param salt: Optional raw salt string. If not specified, one will be autogenerated (this is recommended). :param salt_size: Optional number of bytes to use when autogenerating new salts. Defaults to 16 bytes, but can be any non-negative value. :param rounds: Optional number of rounds to use. Defaults to 40000, must be between 1 and 4294967295, inclusive. :param variant: Optionally specifies variant of FSHP to use. * ``0`` - uses SHA-1 digest (deprecated). * ``1`` - uses SHA-2/256 digest (default). * ``2`` - uses SHA-2/384 digest. * ``3`` - uses SHA-2/512 digest. """ #========================================================= #class attrs #========================================================= #--GenericHandler-- name = "fshp" setting_kwds = ("salt", "salt_size", "rounds", "variant") checksum_chars = uh.PADDED_B64_CHARS #--HasRawSalt-- default_salt_size = 16 #current passlib default, FSHP uses 8 min_salt_size = 0 max_salt_size = None #--HasRounds-- default_rounds = 16384 #current passlib default, FSHP uses 4096 min_rounds = 1 #set by FSHP max_rounds = 4294967295 # 32-bit integer limit - not set by FSHP rounds_cost = "linear" #--variants-- default_variant = 1 _variant_info = { #variant: (hash, digest size) 0: ("sha1", 20), 1: ("sha256", 32), 2: ("sha384", 48), 3: ("sha512", 64), } _variant_aliases = dict( [(unicode(k),k) for k in _variant_info] + [(v[0],k) for k,v in _variant_info.items()] ) #========================================================= #instance attrs #========================================================= variant = None #========================================================= #init #========================================================= def __init__(self, variant=None, strict=False, **kwds): self.variant = self.norm_variant(variant, strict=strict) super(fshp, self).__init__(strict=strict, **kwds) @classmethod def norm_variant(cls, variant, strict=False): if variant is None: if strict: raise ValueError("no variant specified") variant = cls.default_variant if isinstance(variant, bytes): variant = variant.decode("ascii") if isinstance(variant, unicode): try: variant = cls._variant_aliases[variant] except KeyError: raise ValueError("invalid fshp variant") if not isinstance(variant, int): raise TypeError("fshp variant must be int or known alias") if variant not in cls._variant_info: raise TypeError("unknown fshp variant") return variant def norm_checksum(self, checksum, strict=False): checksum = super(fshp, self).norm_checksum(checksum, strict) if checksum is not None and len(checksum) != self._variant_info[self.variant][1]: raise ValueError, "invalid checksum length for FSHP variant" return checksum @property def _info(self): return self._variant_info[self.variant] #========================================================= #formatting #========================================================= @classmethod def identify(cls, hash): return uh.identify_prefix(hash, u"{FSHP") _fshp_re = re.compile(ur"^\{FSHP(\d+)\|(\d+)\|(\d+)\}([a-zA-Z0-9+/]+={0,3})$") @classmethod def from_string(cls, hash): if not hash: raise ValueError("no hash specified") if isinstance(hash, bytes): hash = hash.decode("ascii") m = cls._fshp_re.match(hash) if not m: raise ValueError("not a valid FSHP hash") variant, salt_size, rounds, data = m.group(1,2,3,4) variant = int(variant) salt_size = int(salt_size) rounds = int(rounds) try: data = b64decode(data.encode("ascii")) except ValueError: raise ValueError("malformed FSHP hash") salt = data[:salt_size] chk = data[salt_size:] return cls(checksum=chk, salt=salt, rounds=rounds, variant=variant, strict=True) def to_string(self): chk = self.checksum if not chk: #fill in stub checksum chk = b('\x00') * self._info[1] salt = self.salt data = b64encode(salt+chk).decode("ascii") hash = u"{FSHP%d|%d|%d}%s" % (self.variant, len(salt), self.rounds, data) return to_hash_str(hash) #========================================================= #backend #========================================================= def calc_checksum(self, secret): hash, klen = self._info if isinstance(secret, unicode): secret = secret.encode("utf-8") #NOTE: for some reason, FSHP uses pbkdf1 with password & salt reversed. # this has only a minimal impact on security, # but it is worth noting this deviation. return pbkdf1( secret=self.salt, salt=secret, rounds=self.rounds, keylen=klen, hash=hash, ) #========================================================= #eoc #========================================================= #========================================================= #eof #========================================================= passlib-1.5.3/passlib/handlers/sha1_crypt.py0000644000175000017500000001117611643466373022212 0ustar biscuitbiscuit00000000000000"""passlib.handlers.sha1_crypt """ #========================================================= #imports #========================================================= #core from hmac import new as hmac from hashlib import sha1 import re import logging; log = logging.getLogger(__name__) from warnings import warn #site #libs from passlib.utils import h64, handlers as uh, safe_os_crypt, classproperty, \ to_hash_str, to_unicode, bytes, b from passlib.utils.pbkdf2 import hmac_sha1 #pkg #local __all__ = [ ] #========================================================= #sha1-crypt #========================================================= class sha1_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler): """This class implements the SHA1-Crypt password hash, and follows the :ref:`password-hash-api`. It supports a variable-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, an 8 character one will be autogenerated (this is recommended). If specified, it must be 0-64 characters, drawn from the regexp range ``[./0-9A-Za-z]``. :param salt_size: Optional number of bytes to use when autogenerating new salts. Defaults to 8 bytes, but can be any value between 0 and 64. :param rounds: Optional number of rounds to use. Defaults to 40000, must be between 1 and 4294967295, inclusive. It will use the first available of two possible backends: * stdlib :func:`crypt()`, if the host OS supports sha1-crypt (NetBSD). * a pure python implementation of sha1-crypt You can see which backend is in use by calling the :meth:`get_backend()` method. """ #========================================================= #class attrs #========================================================= #--GenericHandler-- name = "sha1_crypt" setting_kwds = ("salt", "salt_size", "rounds") ident = u"$sha1$" checksum_size = 28 checksum_chars = uh.H64_CHARS #--HasSalt-- default_salt_size = 8 min_salt_size = 0 max_salt_size = 64 salt_chars = uh.H64_CHARS #--HasRounds-- default_rounds = 40000 #current passlib default min_rounds = 1 #really, this should be higher. max_rounds = 4294967295 # 32-bit integer limit rounds_cost = "linear" #========================================================= #formatting #========================================================= @classmethod def from_string(cls, hash): rounds, salt, chk = uh.parse_mc3(hash, cls.ident, cls.name) if rounds.startswith("0"): raise ValueError("invalid sha1-crypt hash (zero-padded rounds)") return cls( rounds=int(rounds), salt=salt, checksum=chk, strict=bool(chk), ) def to_string(self, native=True): out = u"$sha1$%d$%s" % (self.rounds, self.salt) if self.checksum: out += u"$" + self.checksum return to_hash_str(out) if native else out #========================================================= #backend #========================================================= backends = ("os_crypt", "builtin") _has_backend_builtin = True @classproperty def _has_backend_os_crypt(cls): h = u'$sha1$1$Wq3GL2Vp$C8U25GvfHS8qGHimExLaiSFlGkAe' return bool(safe_os_crypt and safe_os_crypt(u"test",h)[1]==h) def _calc_checksum_builtin(self, secret): if isinstance(secret, unicode): secret = secret.encode("utf-8") rounds = self.rounds #NOTE: this uses a different format than the hash... result = u"%s$sha1$%s" % (self.salt, rounds) result = result.encode("ascii") r = 0 while r < rounds: result = hmac_sha1(secret, result) r += 1 return h64.encode_transposed_bytes(result, self._chk_offsets).decode("ascii") _chk_offsets = [ 2,1,0, 5,4,3, 8,7,6, 11,10,9, 14,13,12, 17,16,15, 0,19,18, ] def _calc_checksum_os_crypt(self, secret): ok, hash = safe_os_crypt(secret, self.to_string(native=False)) if ok: return hash[hash.rindex("$")+1:] else: return self._calc_checksum_builtin(secret) #========================================================= #eoc #========================================================= #========================================================= #eof #========================================================= passlib-1.5.3/passlib/handlers/pbkdf2.py0000644000175000017500000004132311643466373021302 0ustar biscuitbiscuit00000000000000"""passlib.handlers.pbkdf - PBKDF2 based hashes""" #========================================================= #imports #========================================================= #core from binascii import hexlify, unhexlify from base64 import b64encode, b64decode import re import logging; log = logging.getLogger(__name__) from warnings import warn #site #libs from passlib.utils import adapted_b64_encode, adapted_b64_decode, \ handlers as uh, to_hash_str, to_unicode, bytes, b from passlib.utils.pbkdf2 import pbkdf2 #pkg #local __all__ = [ "pbkdf2_sha1", "pbkdf2_sha256", "pbkdf2_sha512", "cta_pbkdf2_sha1", "dlitz_pbkdf2_sha1", "grub_pbkdf2_sha512", ] #========================================================= # #========================================================= class Pbkdf2DigestHandler(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): "base class for various pbkdf2_{digest} algorithms" #========================================================= #class attrs #========================================================= #--GenericHandler-- setting_kwds = ("salt", "salt_size", "rounds") checksum_chars = uh.H64_CHARS #--HasSalt-- default_salt_size = 16 min_salt_size = 0 max_salt_size = 1024 #--HasRounds-- default_rounds = 6400 min_rounds = 1 max_rounds = 2**32-1 rounds_cost = "linear" #--this class-- _prf = None #subclass specified prf identifier #NOTE: max_salt_size and max_rounds are arbitrarily chosen to provide sanity check. # the underlying pbkdf2 specifies no bounds for either. #NOTE: defaults chosen to be at least as large as pbkdf2 rfc recommends... # >8 bytes of entropy in salt, >1000 rounds # increased due to time since rfc established #========================================================= #methods #========================================================= @classmethod def from_string(cls, hash): if not hash: raise ValueError("no hash specified") rounds, salt, chk = uh.parse_mc3(hash, cls.ident, cls.name) int_rounds = int(rounds) if rounds != unicode(int_rounds): #forbid zero padding, etc. raise ValueError("invalid %s hash" % (cls.name,)) raw_salt = adapted_b64_decode(salt.encode("ascii")) raw_chk = adapted_b64_decode(chk.encode("ascii")) if chk else None return cls( rounds=int_rounds, salt=raw_salt, checksum=raw_chk, strict=bool(raw_chk), ) def to_string(self, withchk=True): salt = adapted_b64_encode(self.salt).decode("ascii") if withchk and self.checksum: chk = adapted_b64_encode(self.checksum).decode("ascii") hash = u'%s%d$%s$%s' % (self.ident, self.rounds, salt, chk) else: hash = u'%s%d$%s' % (self.ident, self.rounds, salt) return to_hash_str(hash) def calc_checksum(self, secret): if isinstance(secret, unicode): secret = secret.encode("utf-8") return pbkdf2(secret, self.salt, self.rounds, self.checksum_size, self._prf) def create_pbkdf2_hash(hash_name, digest_size, ident=None): "create new Pbkdf2DigestHandler subclass for a specific hash" name = 'pbkdf2_' + hash_name if ident is None: ident = u"$pbkdf2-%s$" % (hash_name,) prf = "hmac-%s" % (hash_name,) base = Pbkdf2DigestHandler return type(name, (base,), dict( name=name, ident=ident, _prf = prf, checksum_size=digest_size, encoded_checksum_size=(digest_size*4+2)//3, __doc__="""This class implements a generic ``PBKDF2-%(prf)s``-based password hash, and follows the :ref:`password-hash-api`. It supports a variable-length salt, and a variable number of rounds. The :meth:`encrypt()` and :meth:`genconfig` methods accept the following optional keywords: :param salt: Optional salt bytes. If specified, the length must be between 0-1024 bytes. If not specified, a %(dsc)d byte salt will be autogenerated (this is recommended). :param salt_size: Optional number of bytes to use when autogenerating new salts. Defaults to 16 bytes, but can be any value between 0 and 1024. :param rounds: Optional number of rounds to use. Defaults to %(dr)d, but must be within ``range(1,1<<32)``. """ % dict(prf=prf.upper(), dsc=base.default_salt_size, dr=base.default_rounds) )) #--------------------------------------------------------- #derived handlers #--------------------------------------------------------- pbkdf2_sha1 = create_pbkdf2_hash("sha1", 20, ident=u"$pbkdf2$") pbkdf2_sha256 = create_pbkdf2_hash("sha256", 32) pbkdf2_sha512 = create_pbkdf2_hash("sha512", 64) ldap_pbkdf2_sha1 = uh.PrefixWrapper("ldap_pbkdf2_sha1", pbkdf2_sha1, "{PBKDF2}", "$pbkdf2$") ldap_pbkdf2_sha256 = uh.PrefixWrapper("ldap_pbkdf2_sha256", pbkdf2_sha256, "{PBKDF2-SHA256}", "$pbkdf2-sha256$") ldap_pbkdf2_sha512 = uh.PrefixWrapper("ldap_pbkdf2_sha512", pbkdf2_sha512, "{PBKDF2-SHA512}", "$pbkdf2-sha512$") #========================================================= #cryptacular's pbkdf2 hash #========================================================= #: bytes used by cta hash for base64 values 63 & 64 CTA_ALTCHARS = b("-_") class cta_pbkdf2_sha1(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): """This class implements Cryptacular's PBKDF2-based crypt algorithm, and follows the :ref:`password-hash-api`. It supports a variable-length salt, and a variable number of rounds. The :meth:`encrypt()` and :meth:`genconfig` methods accept the following optional keywords: :param salt: Optional salt bytes. If specified, it may be any length. If not specified, a one will be autogenerated (this is recommended). :param salt_size: Optional number of bytes to use when autogenerating new salts. Defaults to 16 bytes, but can be any value between 0 and 1024. :param rounds: Optional number of rounds to use. Defaults to 10000, must be within ``range(1,1<<32)``. """ #========================================================= #class attrs #========================================================= #--GenericHandler-- name = "cta_pbkdf2_sha1" setting_kwds = ("salt", "salt_size", "rounds") ident = u"$p5k2$" #NOTE: max_salt_size and max_rounds are arbitrarily chosen to provide sanity check. # underlying algorithm (and reference implementation) allow effectively unbounded values for both of these. #--HasSalt-- default_salt_size = 16 min_salt_size = 0 max_salt_size = 1024 #--HasROunds-- default_rounds = 10000 min_rounds = 0 max_rounds = 2**32-1 rounds_cost = "linear" #========================================================= #formatting #========================================================= #hash $p5k2$1000$ZxK4ZBJCfQg=$jJZVscWtO--p1-xIZl6jhO2LKR0= #ident $p5k2$ #rounds 1000 #salt ZxK4ZBJCfQg= #chk jJZVscWtO--p1-xIZl6jhO2LKR0= #NOTE: rounds in hex @classmethod def from_string(cls, hash): if not hash: raise ValueError("no hash specified") rounds, salt, chk = uh.parse_mc3(hash, cls.ident, cls.name) if rounds.startswith("0"): #passlib deviation: forbidding #left-padded with zeroes raise ValueError("invalid cta_pbkdf2_sha1 hash") rounds = int(rounds, 16) salt = b64decode(salt.encode("ascii"), CTA_ALTCHARS) if chk: chk = b64decode(chk.encode("ascii"), CTA_ALTCHARS) return cls( rounds=rounds, salt=salt, checksum=chk, strict=bool(chk), ) def to_string(self, withchk=True): out = u'$p5k2$%x$%s' % (self.rounds, b64encode(self.salt, CTA_ALTCHARS).decode("ascii")) if withchk and self.checksum: out = u"%s$%s" % (out, b64encode(self.checksum, CTA_ALTCHARS).decode("ascii")) return to_hash_str(out) #========================================================= #backend #========================================================= def calc_checksum(self, secret): if isinstance(secret, unicode): secret = secret.encode("utf-8") return pbkdf2(secret, self.salt, self.rounds, 20, "hmac-sha1") #========================================================= #eoc #========================================================= #========================================================= #dlitz's pbkdf2 hash #========================================================= class dlitz_pbkdf2_sha1(uh.HasRounds, uh.HasSalt, uh.GenericHandler): """This class implements Dwayne Litzenberger's PBKDF2-based crypt algorithm, and follows the :ref:`password-hash-api`. It supports a variable-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 specified, it may be any length, but must use the characters in the regexp range ``[./0-9A-Za-z]``. If not specified, a 16 character salt will be autogenerated (this is recommended). :param salt_size: Optional number of bytes to use when autogenerating new salts. Defaults to 16 bytes, but can be any value between 0 and 1024. :param rounds: Optional number of rounds to use. Defaults to 10000, must be within ``range(1,1<<32)``. """ #========================================================= #class attrs #========================================================= #--GenericHandler-- name = "dlitz_pbkdf2_sha1" setting_kwds = ("salt", "salt_size", "rounds") ident = u"$p5k2$" #NOTE: max_salt_size and max_rounds are arbitrarily chosen to provide sanity check. # underlying algorithm (and reference implementation) allow effectively unbounded values for both of these. #--HasSalt-- default_salt_size = 16 min_salt_size = 0 max_salt_size = 1024 salt_chars = uh.H64_CHARS #--HasROunds-- default_rounds = 10000 min_rounds = 0 max_rounds = 2**32-1 rounds_cost = "linear" #========================================================= #formatting #========================================================= #hash $p5k2$c$u9HvcT4d$Sd1gwSVCLZYAuqZ25piRnbBEoAesaa/g #ident $p5k2$ #rounds c #salt u9HvcT4d #chk Sd1gwSVCLZYAuqZ25piRnbBEoAesaa/g #rounds in lowercase hex, no zero padding @classmethod def from_string(cls, hash): if not hash: raise ValueError("no hash specified") rounds, salt, chk = uh.parse_mc3(hash, cls.ident, cls.name) if rounds.startswith("0"): #zero not allowed, nor left-padded with zeroes raise ValueError("invalid dlitz_pbkdf2_sha1 hash") rounds = int(rounds, 16) if rounds else 400 return cls( rounds=rounds, salt=salt, checksum=chk, strict=bool(chk), ) def to_string(self, withchk=True, native=True): if self.rounds == 400: out = u'$p5k2$$%s' % (self.salt,) else: out = u'$p5k2$%x$%s' % (self.rounds, self.salt) if withchk and self.checksum: out = u"%s$%s" % (out,self.checksum) return to_hash_str(out) if native else out #========================================================= #backend #========================================================= def calc_checksum(self, secret): if isinstance(secret, unicode): secret = secret.encode("utf-8") salt = self.to_string(withchk=False, native=False).encode("ascii") result = pbkdf2(secret, salt, self.rounds, 24, "hmac-sha1") return adapted_b64_encode(result).decode("ascii") #========================================================= #eoc #========================================================= #========================================================= #crowd #========================================================= class atlassian_pbkdf2_sha1(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): """This class implements the PBKDF2 hash used by Atlassian. It supports a fixed-length salt, and a fixed number of rounds. The :meth:`encrypt()` and :meth:`genconfig` methods accept the following optional keyword: :param salt: Optional salt bytes. If specified, the length must be exactly 16 bytes. If not specified, a salt will be autogenerated (this is recommended). """ #--GenericHandler-- name = "atlassian_pbkdf2_sha1" setting_kwds =("salt",) ident = u"{PKCS5S2}" checksum_size = 32 _stub_checksum = b("\x00") * 32 #--HasRawSalt-- min_salt_size = max_salt_size = 16 @classmethod def from_string(cls, hash): if not hash: raise ValueError("no hash specified") if isinstance(hash, bytes): hash = hash.decode("ascii") ident = cls.ident if not hash.startswith(ident): raise ValueError("invalid %s hash" % (cls.name,)) data = b64decode(hash[len(ident):].encode("ascii")) salt, chk = data[:16], data[16:] return cls(salt=salt, checksum=chk, strict=True) def to_string(self): data = self.salt + (self.checksum or self._stub_checksum) hash = self.ident + b64encode(data).decode("ascii") return to_hash_str(hash) def calc_checksum(self, secret): #TODO: find out what crowd's policy is re: unicode if isinstance(secret, unicode): secret = secret.encode("utf-8") #crowd seems to use a fixed number of rounds. return pbkdf2(secret, self.salt, 10000, 32, "hmac-sha1") #========================================================= #grub #========================================================= class grub_pbkdf2_sha512(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): """This class implements Grub's pbkdf2-hmac-sha512 hash, and follows the :ref:`password-hash-api`. It supports a variable-length salt, and a variable number of rounds. The :meth:`encrypt()` and :meth:`genconfig` methods accept the following optional keywords: :param salt: Optional salt bytes. If specified, the length must be between 0-1024 bytes. If not specified, a 64 byte salt will be autogenerated (this is recommended). :param salt_size: Optional number of bytes to use when autogenerating new salts. Defaults to 64 bytes, but can be any value between 0 and 1024. :param rounds: Optional number of rounds to use. Defaults to 10000, but must be within ``range(1,1<<32)``. """ name = "grub_pbkdf2_sha512" setting_kwds = ("salt", "salt_size", "rounds") ident = u"grub.pbkdf2.sha512." #NOTE: max_salt_size and max_rounds are arbitrarily chosen to provide sanity check. # the underlying pbkdf2 specifies no bounds for either, # and it's not clear what grub specifies. default_salt_size = 64 min_salt_size = 0 max_salt_size = 1024 default_rounds = 10000 min_rounds = 1 max_rounds = 2**32-1 rounds_cost = "linear" @classmethod def from_string(cls, hash): if not hash: raise ValueError("no hash specified") rounds, salt, chk = uh.parse_mc3(hash, cls.ident, cls.name, sep=u".") int_rounds = int(rounds) if rounds != str(int_rounds): #forbid zero padding, etc. raise ValueError("invalid %s hash" % (cls.name,)) raw_salt = unhexlify(salt.encode("ascii")) raw_chk = unhexlify(chk.encode("ascii")) if chk else None return cls( rounds=int_rounds, salt=raw_salt, checksum=raw_chk, strict=bool(raw_chk), ) def to_string(self, withchk=True): salt = hexlify(self.salt).decode("ascii").upper() if withchk and self.checksum: chk = hexlify(self.checksum).decode("ascii").upper() hash = u'%s%d.%s.%s' % (self.ident, self.rounds, salt, chk) else: hash = u'%s%d.%s' % (self.ident, self.rounds, salt) return to_hash_str(hash) def calc_checksum(self, secret): #TODO: find out what grub's policy is re: unicode if isinstance(secret, unicode): secret = secret.encode("utf-8") return pbkdf2(secret, self.salt, self.rounds, 64, "hmac-sha512") #========================================================= #eof #========================================================= passlib-1.5.3/passlib/handlers/__init__.py0000644000175000017500000000015411643466373021666 0ustar biscuitbiscuit00000000000000#XXX: make this a namespace package ? #TODO: bigcrypt, crypt16, raw hex strings for md5/sha1/sha256/sha512 passlib-1.5.3/passlib/handlers/django.py0000644000175000017500000001647611643466373021407 0ustar biscuitbiscuit00000000000000"""passlib.handlers.django- Django password hash support""" #========================================================= #imports #========================================================= #core from hashlib import md5, sha1 import re import logging; log = logging.getLogger(__name__) from warnings import warn #site #libs from passlib.utils import h64, handlers as uh, b, bytes, to_unicode, to_hash_str #pkg #local __all__ = [ "django_salted_sha1", "django_salted_md5", "django_des_crypt", "django_disabled", ] #========================================================= # lazy imports #========================================================= des_crypt = None def _import_des_crypt(): global des_crypt if des_crypt is None: from passlib.hash import des_crypt return des_crypt #========================================================= #salted hashes #========================================================= class DjangoSaltedHash(uh.HasSalt, uh.GenericHandler): """base class providing common code for django hashes""" #must be specified by subclass - along w/ calc_checksum setting_kwds = ("salt", "salt_size") ident = None #must have "$" suffix _stub_checksum = None #common to most subclasses min_salt_size = 0 default_salt_size = 5 max_salt_size = None salt_chars = checksum_chars = uh.LC_HEX_CHARS @classmethod def identify(cls, hash): return uh.identify_prefix(hash, cls.ident) @classmethod def from_string(cls, hash): if not hash: raise ValueError("no hash specified") if isinstance(hash, bytes): hash = hash.decode("ascii") ident = cls.ident assert ident.endswith(u"$") if not hash.startswith(ident): raise ValueError("invalid %s hash" % (cls.name,)) _, salt, chk = hash.split(u"$") return cls(salt=salt, checksum=chk, strict=True) def to_string(self): chk = self.checksum or self._stub_checksum out = u"%s%s$%s" % (self.ident, self.salt, chk) return to_hash_str(out) class django_salted_sha1(DjangoSaltedHash): """This class implements Django's Salted SHA1 hash, and follows the :ref:`password-hash-api`. It supports a variable-length salt, and uses a single round of SHA1. The :meth:`encrypt()` and :meth:`genconfig` methods accept the following optional keywords: :param salt: Optional salt string. If not specified, a 5 character one will be autogenerated (this is recommended). If specified, may be any series of characters drawn from the regexp range ``[0-9a-f]``. :param salt_size: Optional number of characters to use when autogenerating new salts. Defaults to 5, but can be any non-negative value. """ name = "django_salted_sha1" ident = u"sha1$" checksum_size = 40 _stub_checksum = u'0' * 40 def calc_checksum(self, secret): if isinstance(secret, unicode): secret = secret.encode("utf-8") return to_unicode(sha1(self.salt.encode("ascii") + secret).hexdigest(), "ascii") class django_salted_md5(DjangoSaltedHash): """This class implements Django's Salted MD5 hash, and follows the :ref:`password-hash-api`. It supports a variable-length salt, and uses a single round of MD5. The :meth:`encrypt()` and :meth:`genconfig` methods accept the following optional keywords: :param salt: Optional salt string. If not specified, a 5 character one will be autogenerated (this is recommended). If specified, may be any series of characters drawn from the regexp range ``[0-9a-f]``. :param salt_size: Optional number of characters to use when autogenerating new salts. Defaults to 5, but can be any non-negative value. """ name = "django_salted_md5" ident = u"md5$" checksum_size = 32 _stub_checksum = u'0' * 32 def calc_checksum(self, secret): if isinstance(secret, unicode): secret = secret.encode("utf-8") return to_unicode(md5(self.salt.encode("ascii") + secret).hexdigest(), "ascii") #========================================================= #other #========================================================= class django_des_crypt(DjangoSaltedHash): """This class implements Django's :class:`des_crypt` wrapper, and follows the :ref:`password-hash-api`. It supports a fixed-length salt. 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 2 characters, drawn from the regexp range ``[./0-9A-Za-z]``. .. note:: Django only supports this on Unix systems, but it is available cross-platform under Passlib. """ name = "django_des_crypt" ident = "crypt$" checksum_chars = salt_chars = uh.H64_CHARS checksum_size = 13 min_salt_size = 2 # NOTE: checksum is full des_crypt hash, # including salt as first two digits. # these should always match first two digits # of django_des_crypt's salt... # and all remaining chars of salt are ignored. def __init__(self, **kwds): super(django_des_crypt, self).__init__(**kwds) # make sure salt embedded in checksum is a match, # else hash can *never* validate salt = self.salt chk = self.checksum if salt and chk and salt[:2] != chk[:2]: raise ValueError("invalid django_des_crypt hash: " "first two digits of salt and checksum must match") _base_stub_checksum = u'.' * 13 @property def _stub_checksum(self): "generate stub checksum dynamically, so it matches always matches salt" stub = self._base_stub_checksum if self.salt: return self.salt[:2] + stub[2:] else: return stub def calc_checksum(self, secret): # NOTE: we lazily import des_crypt, # since most django deploys won't use django_des_crypt global des_crypt if des_crypt is None: _import_des_crypt() salt = self.salt[:2] return salt + des_crypt(salt=salt).calc_checksum(secret) class django_disabled(uh.StaticHandler): """This class provides disabled password behavior for Django, and follows the :ref:`password-hash-api`. This class does not implement a hash, but instead claims the special hash string ``"!"`` which Django uses to indicate an account's password has been disabled. * newly encrypted passwords will hash to ``!``. * it rejects all passwords. """ name = "django_disabled" @classmethod def identify(cls, hash): if not hash: return False if isinstance(hash, bytes): return hash == b("!") else: return hash == u"!" @classmethod def genhash(cls, secret, config): if secret is None: raise TypeError("no secret provided") return to_hash_str(u"!") @classmethod def verify(cls, secret, hash): if not cls.identify(hash): raise ValueError("invalid django-disabled hash") return False #========================================================= #eof #========================================================= passlib-1.5.3/setup.py0000644000175000017500000001406511643466373016040 0ustar biscuitbiscuit00000000000000"""passlib setup script""" #========================================================= # init script env -- ensure cwd = root of source dir #========================================================= import os root_dir = os.path.abspath(os.path.join(__file__,"..")) os.chdir(root_dir) #========================================================= # imports #========================================================= import re import sys import time py3k = (sys.version_info[0] >= 3) try: from setuptools import setup has_distribute = True except ImportError: from distutils import setup has_distribute = False #========================================================= # init setup options #========================================================= opts = { "cmdclass": { } } args = sys.argv[1:] #========================================================= # 2to3 translation #========================================================= if py3k: # monkeypatch preprocessor into lib2to3 from passlib._setup.cond2to3 import patch2to3 patch2to3() # enable 2to3 translation in build_py if has_distribute: opts['use_2to3'] = True else: # if we can't use distribute's "use_2to3" flag, # have to override build_py command from distutils.command.build_py import build_py_2to3 as build_py opts['cmdclass']['build_py'] = build_py #========================================================= #register docdist command (not required) #========================================================= try: from passlib._setup.docdist import docdist opts['cmdclass']['docdist'] = docdist except ImportError: pass #========================================================= # version string / datestamps #========================================================= from passlib import __version__ as VERSION # if this is an hg checkout of passlib, add datestamp to version string. # XXX: could check for *absence* of PKG-INFO instead if os.path.exists(os.path.join(root_dir, "passlib.komodoproject")): # check for --for-release flag indicating this isn't a snapshot for_release = False i = 0 while i < len(args): v = args[i] if v == '--for-release': for_release = True del args[i] break elif not v.startswith("-"): break i += 1 if for_release: assert '.dev' not in VERSION and '.post' not in VERSION else: # add datestamp if doing a snapshot dstr = time.strftime("%Y%m%d") if VERSION.endswith(".dev0") or VERSION.endswith(".post0"): VERSION = VERSION[:-1] + dstr else: assert '.dev' not in VERSION and '.post' not in VERSION VERSION += ".post" + dstr # subclass build_py & sdist so they rewrite passlib/__init__.py # to have the correct version string from passlib._setup.stamp import stamp_distutils_output stamp_distutils_output(opts, VERSION) #========================================================= #static text #========================================================= SUMMARY = "comprehensive password hashing framework supporting over 20 schemes" DESCRIPTION = """\ Passlib is a password hashing library for Python 2 & 3, which provides cross-platform implementations of over 20 password hashing algorithms, as well as a framework for managing existing password hashes. It's designed to be useful for a wide range of tasks, from verifying a hash found in /etc/shadow, to providing full-strength password hashing for multi-user application. * See the `online documentation `_ for details, installation instructions, and examples. * See the `passlib homepage `_ for the latest news, more information, and additional downloads. * See the `changelog `_ for description of what's new in Passlib. All releases are signed with the gpg key `4CE1ED31 `_. """ KEYWORDS = "password secret hash security crypt md5-crypt \ sha256-crypt sha512-crypt bcrypt apache htpasswd htdigest pbkdf2" CLASSIFIERS = """\ Intended Audience :: Developers License :: OSI Approved :: BSD License Natural Language :: English Operating System :: OS Independent Programming Language :: Python :: 2.5 Programming Language :: Python :: 2.6 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Topic :: Security :: Cryptography Topic :: Software Development :: Libraries """.splitlines() is_release = False if '.dev' in VERSION: CLASSIFIERS.append("Development Status :: 3 - Alpha") elif '.post' in VERSION: CLASSIFIERS.append("Development Status :: 4 - Beta") else: is_release = True CLASSIFIERS.append("Development Status :: 5 - Production/Stable") #========================================================= #run setup #========================================================= # XXX: could omit 'passlib.setup' from eggs, but not sdist setup( #package info packages = [ "passlib", "passlib.ext", "passlib.ext.django", "passlib.handlers", "passlib.tests", "passlib.utils", "passlib._setup", ], package_data = { "passlib": ["*.cfg" ], "passlib.tests": ["*.cfg"] }, zip_safe=True, #metadata name = "passlib", version = VERSION, author = "Eli Collins", author_email = "elic@assurancetechnologies.com", license = "BSD", url = "http://passlib.googlecode.com", download_url = ("http://passlib.googlecode.com/files/passlib-" + VERSION + ".tar.gz") if is_release else None, description = SUMMARY, long_description = DESCRIPTION, keywords = KEYWORDS, classifiers = CLASSIFIERS, tests_require = 'nose >= 1.0', test_suite = 'nose.collector', #extra opts script_args=args, **opts ) #========================================================= #EOF #========================================================= passlib-1.5.3/LICENSE0000644000175000017500000000656211643466373015336 0ustar biscuitbiscuit00000000000000.. -*- restructuredtext -*- ===================== Copyrights & Licenses ===================== License for Passlib =================== Passlib is (c) `Assurance Technologies `_, and is released under the `BSD license `_:: Passlib Copyright (c) 2008-2011 Assurance Technologies, LLC. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Assurance Technologies, nor the names of the contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Licenses for incorporated software ================================== PassLib contains some code derived from the following sources: MD5-Crypt --------- The source file ``passlib/handlers/md5_crypt.py`` contains code derived from the original `FreeBSD md5-crypt implementation `_, which is available under the following license:: "THE BEER-WARE LICENSE" (Revision 42): wrote this file. As long as you retain this notice you can do whatever you want with this stuff. If we meet some day, and you think this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp converted to python May 2008 by Eli Collins DES --- The source file ``passlib/utils/des.py`` contains code derived from `UnixCrypt.java `_, a pure-java implementation of the historic unix-crypt password hash algorithm. It is available under the following license:: UnixCrypt.java 0.9 96/11/25 Copyright (c) 1996 Aki Yoshida. All rights reserved. Permission to use, copy, modify and distribute this software for non-commercial or commercial purposes and without fee is hereby granted provided that this copyright notice appears in all copies. modified April 2001 by Iris Van den Broeke, Daniel Deville modified Aug 2005 by Greg Wilkins (gregw) converted to python Jun 2009 by Eli Collins passlib-1.5.3/PKG-INFO0000644000175000017500000000375411643754212015416 0ustar biscuitbiscuit00000000000000Metadata-Version: 1.0 Name: passlib Version: 1.5.3 Summary: comprehensive password hashing framework supporting over 20 schemes Home-page: http://passlib.googlecode.com Author: Eli Collins Author-email: elic@assurancetechnologies.com License: BSD Download-URL: http://passlib.googlecode.com/files/passlib-1.5.3.tar.gz Description: Passlib is a password hashing library for Python 2 & 3, which provides cross-platform implementations of over 20 password hashing algorithms, as well as a framework for managing existing password hashes. It's designed to be useful for a wide range of tasks, from verifying a hash found in /etc/shadow, to providing full-strength password hashing for multi-user application. * See the `online documentation `_ for details, installation instructions, and examples. * See the `passlib homepage `_ for the latest news, more information, and additional downloads. * See the `changelog `_ for description of what's new in Passlib. All releases are signed with the gpg key `4CE1ED31 `_. Keywords: password secret hash security crypt md5-crypt sha256-crypt sha512-crypt bcrypt apache htpasswd htdigest pbkdf2 Platform: UNKNOWN Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2.5 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Security :: Cryptography Classifier: Topic :: Software Development :: Libraries Classifier: Development Status :: 5 - Production/Stable