1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
|
.. index::
pair: custom hash handler; implementing
==========================================================================
:mod:`passlib.utils.handlers` - Framework for writing password hashes
==========================================================================
.. module:: passlib.utils.handlers
:synopsis: framework for writing password hashes
.. warning::
This module is primarily used as an internal support module.
Its interface has not been finalized yet, and may be changed somewhat
between major releases of Passlib, as the internal code is cleaned up
and simplified.
.. 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 its associated mixin classes. While deriving from this class is not required,
doing so will greatly reduce the amount of additional 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
a :class:`CryptContext` constructor, or registered
globally with Passlib via the :mod:`passlib.registry` module.
.. seealso::
: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 its 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 <generic-handler-mixins>` 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 its 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 its 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 annotated 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
# [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()
|