summaryrefslogtreecommitdiff
path: root/docs/lib/passlib.context-usage.rst
blob: 4d897d47112aa09d7b5fb1fce055ddd033694abd (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
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
.. 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)
    <class 'passlib.handlers.md5_crypt.md5_crypt'>

.. _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.copy(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 accessible through various methods of the object:

    >>> from passlib.context import CryptContext
    >>> myctx = CryptContext([ "md5_crypt", "des_crypt" ], deprecated="des_crypt")

    >>> # get a list of schemes recognized in this context:
    >>> myctx.schemes()
    [ 'md5-crypt', 'bcrypt' ]

    >>> # get the default handler object:
    >>> myctx.handler("default")
    <class 'passlib.handlers.md5_crypt.md5_crypt'>

    >>> # the results of a CryptContext object can be serialized as a dict,
    >>> # suitable for passing to CryptContext's class constructor.
    >>> myctx.to_dict()
    {'schemes': ['md5_crypt, 'des_crypt'], 'deprecated': 'des_crypt'}

    >>> # or serialized to an INI-style string, suitable for passing to
    >>> # CryptContext's from_string() method.
    >>> print myctx.to_string()
    [passlib]
    schemes = md5_crypt, des_crypt
    deprecated = des_crypt

See the :class:`CryptContext` reference 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 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 % of the default (in this case,
    ; 20000 +/- 10% or 2000).
    pbkdf2_sha1__default_rounds = 20000
    pbkdf2_sha1__vary_rounds = 0.1

    ; 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

        def myapp_startup():

            #
            # ... other code ...
            #

            #
            # load configuration from some application-specified path.
            # the load() method also supports loading from a string,
            # or from dictionary, and other options.
            #
            ##user_pwd_context.load(policy_config_string)
            user_pwd_context.load_path(policy_config_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 ...