summaryrefslogtreecommitdiff
path: root/README.md
diff options
context:
space:
mode:
authorSimeon Simeonov2020-04-06 10:28:46 +0200
committerSimeon Simeonov2020-04-06 10:28:46 +0200
commite297c0696cf594f8f700dd82fdcdda582e348eda (patch)
treedeea1cfef4041dc492a38d8304c20be14d18c8ba /README.md
parent097aaa49fa99cffec9db74432d7025457c34199e (diff)
Add a simple CLI interface with tests and update README.md
Diffstat (limited to 'README.md')
-rw-r--r--README.md93
1 files changed, 86 insertions, 7 deletions
diff --git a/README.md b/README.md
index ac3bca9..5811b54 100644
--- a/README.md
+++ b/README.md
@@ -70,20 +70,99 @@ one-time password."
70 70
71## Examples 71## Examples
72 72
73We define the two entities: *client* and *server*. The entire application of
74RFC-2289 consists of interactions between them.
75
73 ```python 76 ```python
74 import getpass 77 #
78 import getpass # client only
79
80 import otp2289 # client and server
75 81
76 import otp2289 82 # the server starts by picking:
83 # - algorithm (MD5 or SHA1) to use
84 # - seed - 1 to 16 alphanumeric characters. The seed must never be reused.
85 # - initial step - number (int) that will be decremented for each OTP.
86 # In FreeBSD, the following default values are used:
87 # - MD5
88 # - the first two letters of the hostname + 5 random digits for seed
89 # - initial step: 500
77 90
78 # create a generator object 91 # the client receives those values, chooses a strong password and creates
79 passwd_bytes = getpass.getpass().encode() # Type: This is a test. 92 # initialization digest (hash). The password 'This is a test.' will give you
93 # the same results as in the following example.
94 passwd_bytes = getpass.getpass().encode() # Fetch the password as bytes
80 generator = otp2289.generator.OTPGenerator(passwd_bytes, 95 generator = otp2289.generator.OTPGenerator(passwd_bytes,
81 'TesT', 96 'TesT',
82 otp2289.OTP_ALGO_MD5) 97 otp2289.OTP_ALGO_MD5)
83 generator.generate_otp_hexdigest(0) 98 digest = generator.generate_otp_hexdigest(500)
84 generator.gen.generate_otp_words(0) 99 # digest is now: 0x2b8d82b6ac14346c
100 # the client sends it to the server
101
102 # the server creates the first state. Note that step is decremented by 1:
103 state = otp2289.server.OTPState(digest, 499, 'TesT', otp2289.OTP_ALGO_MD5)
104 # the state can be stored in a OTPStore container:
105 store = otp2289.server.OTPStore()
106 # key can be any str that can be used to reference the state (f.i username)
107 store.add_state('myusername', state) # where key can be any str that can be
108 # OTPStore is provided only for convenience as it is not part of RFC-2289.
109 # The server can store states any way it wants. A normal dict is also fine.
110 # Once the initial state is set on the server, the client can authenticate.
111
112 # Upon authentication request (f.i. login), the server issues a challenge
113 # based on the state:
114 challenge = state.challenge_string # challenge is now 'otp-md5 499 TesT '
115
116 # the client can now respond by using (or recreating) the same generator
117 # created earlier. RFC-2289 defines two types of responses:
118 # - hex (like '0x2b8d82b6ac14346c') - more suited for automation
119 # - tokens consisting of 6 short words - better when responding manually
120 hex_response = generator.generate_otp_hexdigest(499) # '0x6323f96296a2526b'
121 token_response = generator.generate_otp_words(499)
122 # token_response is now: 'CANT JAW BITS NU LO PUP'
123 # a possible shortcut may be to use the challenge-string directly:
124 hex_response = generator.generate_otp_hexdigest_from_challenge(challenge)
125 token_response = generator.generate_otp_words_from_challenge(challenge)
126 # ... giving the same results.
127
128 # once the response is received, the server validates it by yet again using
129 # the current state:
130 result = state.response_validates(hex_response)
131 # or
132 result = state.response_validates(token_response)
133 # result should be True if the response matches the state, False if not
134 # in case of invalid response or response checksum doesn't match, a
135 # otp2289.server.OTPInvalidResponse exception is raised.
136
137 # once the state has successfully validated the corresponding response,
138 the state **must never be used again** and a state corresponding to the
139 "next" (498) step created.
140 state = state.get_next_state()
141
142 # the next authentication attempt...
143 challenge = state.challenge_string # challenge is now 'otp-md5 498 TesT '
144 # ... and on the client side...
145 hex_response = generator.generate_otp_hexdigest_from_challenge(challenge)
146 # etc. etc...
85 ``` 147 ```
86 148
149If you don't care about developing applications in Python and only care about
150generating one-time passwords (tokens / hex digests) and authenticating with
151existing solutions (f.i. FreeBSD servers), pyotp2289 comes with a simple CLI:
152
153 ```bash
154 python -m otp2289 --generate-otp-response -f token -i 498 -s TesT
155 ```
156
157... will prompt for password and generate a 6 words (token) response.
158
159 ```bash
160 python -m otp2289 --generate-otp-range -f token -i 498 -s TesT
161 ```
162
163... will prompt for password and generate a range of 4 one-time passwords
164starting from (and including) 498.
165
87 166
88## Author 167## Author
89 168
@@ -95,5 +174,5 @@ Simeon Simeonov - sgs @ Freenode
95Copyright (c) 2020, Simeon Simeonov 174Copyright (c) 2020, Simeon Simeonov
96All rights reserved. 175All rights reserved.
97 176
98[licensed](LICENSE) under the BSD 2-clause. 177[Licensed](LICENSE) under the BSD 2-clause.
99SPDX-License-Identifier: BSD-2-Clause-FreeBSD 178SPDX-License-Identifier: BSD-2-Clause-FreeBSD