summaryrefslogtreecommitdiff
path: root/docs/lib/passlib.utils.handlers.rst
blob: 6b4799a4c249f1c2176abb576b9185d2722d19da (plain)
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
212
213
214
.. 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.
    It's 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 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 <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 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 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

        # [optional] - if your hash only uses the first X characters of the password,
        #              set that value here. otherwise leave the default (-1).
        secret_size = 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()