aboutsummaryrefslogtreecommitdiffstats
path: root/docs/hazmat/primitives/twofactor.rst
blob: 06be151e69188c9ca5d8f8c1532be3af12d8ce31 (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
.. hazmat::

Two-factor Authentication
=========================

.. currentmodule:: cryptography.hazmat.primitives.twofactor

This module contains algorithms related to two-factor authentication.

Currently, it contains an algorithm for generating and verifying
one time password values based on Hash-based message authentication
codes (HMAC).

.. currentmodule:: cryptography.hazmat.primitives.twofactor.hotp

.. class:: HOTP(key, length, algorithm, backend)

    .. versionadded:: 0.3

    HOTP objects take a ``key``, ``length`` and ``algorithm`` parameter. The
    ``key`` should be randomly generated bytes and is recommended to be 160
    bits in length. The ``length`` parameter controls the length of the generated
    one time password and must be >= 6 and <= 8.

    This is an implementation of :rfc:`4226`.

    .. doctest::

        >>> import os
        >>> from cryptography.hazmat.backends import default_backend
        >>> from cryptography.hazmat.primitives.twofactor.hotp import HOTP
        >>> from cryptography.hazmat.primitives.hashes import SHA1
        >>> key = os.urandom(16)
        >>> hotp = HOTP(key, 6, SHA1(), backend=default_backend())
        >>> hotp_value = hotp.generate(0)
        >>> hotp.verify(hotp_value, 0)

    :param bytes key: Per-user secret key. This value must be kept secret
                      and be at least 128 bits. It is recommended that the
                      key be 160 bits.
    :param int length: Length of generated one time password as ``int``.
    :param HashAlgorithm algorithm: A
        :class:`~cryptography.hazmat.primitives.hashes`
        provider.
    :param backend: A
        :class:`~cryptography.hazmat.backends.interfaces.HMACBackend`
        provider.
    :raises ValueError: This is raised if the provided ``key`` is shorter than 128 bits
                        or if the ``length`` parameter is not 6, 7 or 8.
    :raises UnsupportedAlgorithm: This is raised if the provided ``algorithm`` is not
                                  :class:`~cryptography.hazmat.primitives.hashes.SHA1()`,
                                  :class:`~cryptography.hazmat.primitives.hashes.SHA256()`
                                  or :class:`~cryptography.hazmat.primitives.hashes.SHA512()`.

    .. method:: generate(counter)

        :param int counter: The counter value used to generate the one time password.
        :return bytes: A one time password value.

    .. method:: verify(hotp, counter)

        :param bytes hotp: The one time password value to validate.
        :param int counter: The counter value to validate against.
        :raises cryptography.exceptions.InvalidToken: This is raised when the supplied HOTP
                                                      does not match the expected HOTP.

Throttling
~~~~~~~~~~

Due to the fact that the HOTP algorithm generates rather short tokens that are 6 - 8 digits
long, brute force attacks are possible. It is highly recommended that the server that
validates the token implement a throttling scheme that locks out the account for a period of
time after a number of failed attempts. The number of allowed attempts should be as low as
possible while still ensuring that usability is not significantly impacted.

Re-synchronization of the Counter
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The server's counter value should only be incremented on a successful HOTP authentication.
However, the counter on the client is incremented every time a new HOTP value is requested.
This can lead to the counter value being out of synchronization between the client and server.

Due to this, it is highly recommended that the server sets a look-ahead window that allows the
server to calculate the next ``x`` HOTP values and check them against the supplied HOTP value.
This can be accomplished with something similar to the following code.

.. code-block:: python

    def verify(hotp, counter, look_ahead):
        assert look_ahead >= 0
        correct_counter = None

        otp = HOTP(key, 6, default_backend())
        for count in range(counter, counter+look_ahead):
            try:
                otp.verify(hotp, count)
                correct_counter = count
            except InvalidToken:
                pass

        return correct_counter

.. currentmodule:: cryptography.hazmat.primitives.twofactor.totp

.. class:: TOTP(key, length, algorithm, time_step, backend)

    TOTP objects take a ``key``, ``length``, ``algorithm`` and ``time_step``
    parameter. The ``key`` should be randomly generated bytes and is recommended
    to be as long as your hash function's output (e.g 256-bit for SHA256).
    The ``length`` parameter controls the length of the generated one time
    password and must be >= 6 and <= 8.

    This is an implementation of :rfc:`6238`.

    .. doctest::

        >>> import os
        >>> import time
        >>> from cryptography.hazmat.backends import default_backend
        >>> from cryptography.hazmat.primitives.twofactor.totp import TOTP
        >>> from cryptography.hazmat.primitives.hashes import SHA1
        >>> key = os.urandom(16)
        >>> totp = TOTP(key, 8, SHA1(), 30, backend=default_backend())
        >>> time_value = time.time()
        >>> totp_value = totp.generate(time_value)
        >>> totp.verify(totp_value, time_value)

    :param bytes key: Per-user secret key. This value must be kept secret
                      and be at least 128 bits. It is recommended that the
                      key be 160 bits.
    :param int length: Length of generated one time password as ``int``.
    :param HashAlgorithm algorithm: A
        :class:`~cryptography.hazmat.primitives.hashes`
        provider.
    :param int time_step: The time step size. The recommended size is 30.
    :param backend: A
        :class:`~cryptography.hazmat.backends.interfaces.HMACBackend`
        provider.
    :raises ValueError: This is raised if the provided ``key`` is shorter than 128 bits
                        or if the ``length`` parameter is not 6, 7 or 8.
    :raises UnsupportedAlgorithm: This is raised if the provided ``algorithm`` is not
                                  :class:`~cryptography.hazmat.primitives.hashes.SHA1()`,
                                  :class:`~cryptography.hazmat.primitives.hashes.SHA256()`
                                  or :class:`~cryptography.hazmat.primitives.hashes.SHA512()`.

    .. method:: generate(time)

        :param int time: The time value used to generate the one time password.
        :return bytes: A one time password value.

    .. method:: verify(totp, time)

        :param bytes totp: The one time password value to validate.
        :param int time: The time value to validate against.
        :raises cryptography.exceptions.InvalidToken: This is raised when the supplied TOTP
                                                      does not match the expected TOTP.