summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimeon Simeonov2024-12-28 20:13:43 +0100
committerSimeon Simeonov2024-12-28 20:13:43 +0100
commit82ef6adec6e59f9cc9dc9fa1a13a2b43e534bada (patch)
tree5921f91387f8b4567f810c20570f458547e6ae64
parente3a82c195f8e18fbd9691845ff4e6121986e13ea (diff)
Rename *Exception to *Error and do some general linting
-rw-r--r--.ruff.toml98
-rw-r--r--README.md5
-rw-r--r--src/otp2289/__init__.py26
-rw-r--r--src/otp2289/__main__.py46
-rw-r--r--src/otp2289/generator.py2408
-rw-r--r--src/otp2289/server.py88
-rw-r--r--test/test_generator.py55
-rw-r--r--test/test_main.py3
-rw-r--r--test/test_server.py15
9 files changed, 2296 insertions, 448 deletions
diff --git a/.ruff.toml b/.ruff.toml
new file mode 100644
index 0000000..c5617c1
--- /dev/null
+++ b/.ruff.toml
@@ -0,0 +1,98 @@
1cache-dir = "~/.cache/ruff"
2indent-width = 4
3line-length = 79
4target-version = "py312"
5
6[lint]
7select = ["ALL", "D101", "D102", "D103", "D104"]
8ignore = [
9 "ANN",
10 "BLE001",
11 "COM812",
12 "D",
13 "EM101", # Exception must not use a string literal, assign to variable first
14 "EM102", # Exception must not use an f-string literal, assign to variable first
15 "ERA001",
16 "FBT001",
17 "FBT002",
18 "INP001",
19 "ISC001",
20 "N802",
21 "N806",
22 "PLR2004",
23 "PTH111",
24 "RUF012",
25 "RUF013",
26 "S101",
27 "S324",
28 "T201",
29 "TRY003",
30 "TRY300",
31 "UP020"
32]
33
34# D101 - Missing docstring in public class
35# D102 - Missing docstring in public method
36# D200 - One-line docstring should fit on one line
37# D203 - 1 blank line required before class docstring
38# D205 - 1 blank line required between summary line and description
39# D403 - First word of the first line should be capitalized: `str` -> `Str`
40# FBT001 - Boolean-typed positional argument in function definition
41# FBT002 - Boolean default positional argument in function definition
42# INP001 - File `beinc_weechat.py` is part of an implicit namespace package. Add an `__init__.py`
43# N802 - Function name `do_GET` should be lowercase
44# N806 - Variable `POST_data` in function should be lowercase
45# PLR2004 - Magic value used in comparison, consider replacing `200` with a constant variable
46# PTH111 - `os.path.expanduser()` should be replaced by `Path.expanduser()`
47# PTH113 - `os.path.isfile()` should be replaced by `Path.is_file()`
48# PTH123 - `open()` should be replaced by `Path.open()`
49# RUF012 - Mutable class attributes should be annotated with `typing.ClassVar`
50# RUF013 - PEP 484 prohibits implicit `Optional`
51# S101 - Use of `assert` detected
52# S324 - Probable use of insecure hash functions in `hashlib`: `md5`
53# T201 - `print` found
54# TRY003 - Avoid specifying long messages outside the exception class
55# TRY300 - Consider moving this statement to an `else` block
56# UP020 - Use builtin `open`
57
58# Allow fix for all enabled rules (when `--fix`) is provided.
59fixable = ["ALL"]
60unfixable = []
61
62# custom settings
63[lint.per-file-ignores]
64"src/otp2289/__main__.py" = ["PTH113", "PTH123"] # "Readability counts"
65
66
67[format]
68# Like Black, use double quotes for strings.
69quote-style = "single"
70
71# Like Black, indent with spaces, rather than tabs.
72indent-style = "space"
73
74# Like Black, respect magic trailing commas.
75skip-magic-trailing-comma = true
76
77# Like Black, automatically detect the appropriate line ending.
78line-ending = "auto"
79
80# Enable auto-formatting of code examples in docstrings. Markdown,
81# reStructuredText code/literal blocks and doctests are all supported.
82#
83# This is currently disabled by default, but it is planned for this
84# to be opt-out in the future.
85docstring-code-format = false
86
87# Set the line length limit used when formatting code snippets in
88# docstrings.
89#
90# This only has an effect when the `docstring-code-format` setting is
91# enabled.
92docstring-code-line-length = "dynamic"
93
94[lint.flake8-quotes]
95inline-quotes = "single"
96
97[lint.isort]
98split-on-trailing-comma = false
diff --git a/README.md b/README.md
index ecf8a22..7ef9344 100644
--- a/README.md
+++ b/README.md
@@ -33,7 +33,7 @@ I hope that somebody will find it useful.
33 pkg install security/py-pyotp2289 33 pkg install security/py-pyotp2289
34 ``` 34 ```
35 35
36– or: 36or:
37 37
38 ```bash 38 ```bash
39 cd /usr/ports/security/py-pyotp2289 39 cd /usr/ports/security/py-pyotp2289
@@ -46,9 +46,6 @@ I hope that somebody will find it useful.
46 # add sgs' custom repository using app-eselect/eselect-repository 46 # add sgs' custom repository using app-eselect/eselect-repository
47 eselect repository add sgs 47 eselect repository add sgs
48 48
49 # ... or using app-portage/layman (obsolete)
50 layman -a sgs
51
52 emerge dev-python/pyotp2289 49 emerge dev-python/pyotp2289
53 ``` 50 ```
54 51
diff --git a/src/otp2289/__init__.py b/src/otp2289/__init__.py
index 5e51498..c9e3c74 100644
--- a/src/otp2289/__init__.py
+++ b/src/otp2289/__init__.py
@@ -1,7 +1,6 @@
1# -*- coding: utf-8 -*-
2# SPDX-License-Identifier: BSD-2-Clause-FreeBSD 1# SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3# 2#
4# Copyright (c) 2020-2023 Simeon Simeonov 3# Copyright (c) 2020-2025 Simeon Simeonov
5# All rights reserved. 4# All rights reserved.
6# 5#
7# Redistribution and use in source and binary forms, with or without 6# Redistribution and use in source and binary forms, with or without
@@ -24,23 +23,24 @@
24# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26"""A pure Python implementation of RFC-2289""" 25"""A pure Python implementation of RFC-2289"""
26
27from .generator import ( 27from .generator import (
28 OTP_ALGO_MD5, 28 OTP_ALGO_MD5,
29 OTP_ALGO_SHA1, 29 OTP_ALGO_SHA1,
30 OTPChallengeException, 30 OTPChallengeError,
31 OTPGenerator, 31 OTPGenerator,
32 OTPGeneratorException, 32 OTPGeneratorError,
33) 33)
34from .server import ( 34from .server import (
35 OTPInvalidResponse, 35 OTPInvalidResponseError,
36 OTPState, 36 OTPState,
37 OTPStateException, 37 OTPStateError,
38 OTPStore, 38 OTPStore,
39 OTPStoreException, 39 OTPStoreError,
40) 40)
41 41
42__author__ = 'Simeon Simeonov' 42__author__ = 'Simeon Simeonov'
43__version__ = '1.2.1' 43__version__ = '1.2.2'
44__license__ = 'BSD 2-Clause' 44__license__ = 'BSD 2-Clause'
45 45
46 46
@@ -57,12 +57,12 @@ VERSION = tuple(map(int_or_str, __version__.split('.')))
57__all__ = [ 57__all__ = [
58 'OTP_ALGO_MD5', 58 'OTP_ALGO_MD5',
59 'OTP_ALGO_SHA1', 59 'OTP_ALGO_SHA1',
60 'OTPChallengeException', 60 'OTPChallengeError',
61 'OTPGenerator', 61 'OTPGenerator',
62 'OTPGeneratorException', 62 'OTPGeneratorError',
63 'OTPInvalidResponse', 63 'OTPInvalidResponseError',
64 'OTPState', 64 'OTPState',
65 'OTPStateException', 65 'OTPStateError',
66 'OTPStore', 66 'OTPStore',
67 'OTPStoreException', 67 'OTPStoreError',
68] 68]
diff --git a/src/otp2289/__main__.py b/src/otp2289/__main__.py
index d267b1b..9f1aab8 100644
--- a/src/otp2289/__main__.py
+++ b/src/otp2289/__main__.py
@@ -1,7 +1,6 @@
1# -*- coding: utf-8 -*-
2# SPDX-License-Identifier: BSD-2-Clause-FreeBSD 1# SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3# 2#
4# Copyright (c) 2020-2023 Simeon Simeonov 3# Copyright (c) 2020-2025 Simeon Simeonov
5# All rights reserved. 4# All rights reserved.
6# 5#
7# Redistribution and use in source and binary forms, with or without 6# Redistribution and use in source and binary forms, with or without
@@ -32,10 +31,10 @@ python -m otp2289 --initiate-new-sequence -s TesT
32python -m otp2289 --generate-otp-response -c "otp-md5 499 TesT " -f token 31python -m otp2289 --generate-otp-response -c "otp-md5 499 TesT " -f token
33python -m otp2289 --generate-otp-response -s TesT -i 499 -f token 32python -m otp2289 --generate-otp-response -s TesT -i 499 -f token
34""" 33"""
34
35import argparse 35import argparse
36import errno 36import errno
37import getpass 37import getpass
38import io
39import os 38import os
40import secrets 39import secrets
41import string 40import string
@@ -56,17 +55,15 @@ def generate_otp_response(args: argparse.Namespace) -> str:
56 :param args: The arguments assigned from argparse 55 :param args: The arguments assigned from argparse
57 :type args: argparse.Namespace 56 :type args: argparse.Namespace
58 57
59 :raises otp2289.OTPChallengeException: If the challenge is invalid 58 :raises otp2289.OTPChallengeError: If the challenge is invalid
60 59
61 :raises otp2289.OTPGeneratorException: If generator parameters are wrong 60 :raises otp2289.OTPGeneratorError: If generator parameters are wrong
62 61
63 :return: The response string 62 :return: The response string
64 :rtype: str 63 :rtype: str
65 """ 64 """
66 generator = otp2289.generator.OTPGenerator( 65 generator = otp2289.generator.OTPGenerator(
67 args.password.encode(), 66 args.password.encode(), args.seed, args.hash_algo
68 args.seed,
69 args.hash_algo,
70 ) 67 )
71 if args.challenge_string: 68 if args.challenge_string:
72 if args.output_format == 'token': 69 if args.output_format == 'token':
@@ -95,27 +92,26 @@ def generate_otp_range(args: argparse.Namespace) -> str:
95 :param args: The arguments assigned from argparse 92 :param args: The arguments assigned from argparse
96 :type args: argparse.Namespace 93 :type args: argparse.Namespace
97 94
98 :raises otp2289.OTPChallengeException: If the challenge is invalid 95 :raises otp2289.OTPChallengeError: If the challenge is invalid
99 96
100 :raises otp2289.OTPGeneratorException: If generator parameters are wrong 97 :raises otp2289.OTPGeneratorError: If generator parameters are wrong
101 98
102 :return: The responses string 99 :return: The responses string
103 :rtype: str 100 :rtype: str
104 """ 101 """
105 generator = otp2289.generator.OTPGenerator( 102 generator = otp2289.generator.OTPGenerator(
106 args.password.encode(), 103 args.password.encode(), args.seed, args.hash_algo
107 args.seed,
108 args.hash_algo,
109 ) 104 )
110 if args.output_format == 'token': 105 if args.output_format == 'token':
111 method = generator.generate_otp_words 106 method = generator.generate_otp_words
112 else: 107 else:
113 method = generator.generate_otp_hexdigest 108 method = generator.generate_otp_hexdigest
109
114 # handle most cases explicitly 110 # handle most cases explicitly
115 if args.range == 1: 111 if args.range == 1:
116 return f'{args.step}: ' + method(args.step) 112 return f'{args.step}: ' + method(args.step)
117 if args.range > args.step + 1: 113 args.range = min(args.range, args.step + 1)
118 args.range = args.step + 1 114
119 # any need for quiet? 115 # any need for quiet?
120 header = '' 116 header = ''
121 if not args.quiet: 117 if not args.quiet:
@@ -126,11 +122,7 @@ def generate_otp_range(args: argparse.Namespace) -> str:
126 return header + os.linesep.join( 122 return header + os.linesep.join(
127 [ 123 [
128 f'{step}: ' + method(step) 124 f'{step}: ' + method(step)
129 for step in range( 125 for step in range(args.step, args.step - args.range, -1)
130 args.step,
131 args.step - args.range,
132 -1,
133 )
134 ] 126 ]
135 ) 127 )
136 128
@@ -171,7 +163,7 @@ def get_password(args: argparse.Namespace) -> str:
171 return password 163 return password
172 164
173 if os.path.isfile(args.password): 165 if os.path.isfile(args.password):
174 with io.open(args.password, 'r', encoding='utf-8') as fp: 166 with open(args.password, encoding='utf-8') as fp:
175 return fp.readline().strip() 167 return fp.readline().strip()
176 168
177 return args.password 169 return args.password
@@ -196,9 +188,9 @@ def initiate_new_sequence(args: argparse.Namespace) -> str:
196 :param args: The arguments assigned from argparse 188 :param args: The arguments assigned from argparse
197 :type args: argparse.Namespace 189 :type args: argparse.Namespace
198 190
199 :raises otp2289.OTPChallengeException: If the challenge is invalid 191 :raises otp2289.OTPChallengeError: If the challenge is invalid
200 192
201 :raises otp2289.OTPGeneratorException: If generator parameters are wrong 193 :raises otp2289.OTPGeneratorError: If generator parameters are wrong
202 194
203 :return: The response string 195 :return: The response string
204 :rtype: str 196 :rtype: str
@@ -212,9 +204,7 @@ def initiate_new_sequence(args: argparse.Namespace) -> str:
212 f'Hash: {args.hash_algo}{os.linesep}' 204 f'Hash: {args.hash_algo}{os.linesep}'
213 ) 205 )
214 generator = otp2289.generator.OTPGenerator( 206 generator = otp2289.generator.OTPGenerator(
215 args.password.encode(), 207 args.password.encode(), args.seed, args.hash_algo
216 args.seed,
217 args.hash_algo,
218 ) 208 )
219 if args.challenge_string: 209 if args.challenge_string:
220 return header + generator.generate_otp_hexdigest_from_challenge( 210 return header + generator.generate_otp_hexdigest_from_challenge(
@@ -372,9 +362,9 @@ def main(args=None):
372 if args.generate_otp_response: 362 if args.generate_otp_response:
373 print(generate_otp_response(args)) 363 print(generate_otp_response(args))
374 sys.exit(0) 364 sys.exit(0)
375 except otp2289.generator.OTPGeneratorException as exp: 365 except otp2289.generator.OTPGeneratorError as exp:
376 eprint(f'GeneratorException: {exp}') 366 eprint(f'GeneratorException: {exp}')
377 except otp2289.generator.OTPChallengeException as exp: 367 except otp2289.generator.OTPChallengeError as exp:
378 eprint(f'ChallengeException: {exp}') 368 eprint(f'ChallengeException: {exp}')
379 except Exception as exp: 369 except Exception as exp:
380 eprint(f'Unknown error: {exp}') 370 eprint(f'Unknown error: {exp}')
diff --git a/src/otp2289/generator.py b/src/otp2289/generator.py
index 73ca18b..c03d289 100644
--- a/src/otp2289/generator.py
+++ b/src/otp2289/generator.py
@@ -1,7 +1,6 @@
1# -*- coding: utf-8 -*-
2# SPDX-License-Identifier: BSD-2-Clause-FreeBSD 1# SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3# 2#
4# Copyright (c) 2020-2023 Simeon Simeonov 3# Copyright (c) 2020-2025 Simeon Simeonov
5# All rights reserved. 4# All rights reserved.
6# 5#
7# Redistribution and use in source and binary forms, with or without 6# Redistribution and use in source and binary forms, with or without
@@ -24,6 +23,7 @@
24# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26"""A pure Python implementation of the RFC-2289 OTP generator""" 25"""A pure Python implementation of the RFC-2289 OTP generator"""
26
27import binascii 27import binascii
28import hashlib 28import hashlib
29import string 29import string
@@ -33,283 +33,2072 @@ OTP_ALGO_SHA1 = 2
33 33
34# the tokens are defined in https://tools.ietf.org/html/rfc2289 # 34# the tokens are defined in https://tools.ietf.org/html/rfc2289 #
35RFC1760_TOKENS = [ 35RFC1760_TOKENS = [
36 'A', 'ABE', 'ACE', 'ACT', 'AD', 'ADA', 'ADD', 36 'A',
37 'AGO', 'AID', 'AIM', 'AIR', 'ALL', 'ALP', 'AM', 'AMY', 37 'ABE',
38 'AN', 'ANA', 'AND', 'ANN', 'ANT', 'ANY', 'APE', 'APS', 38 'ACE',
39 'APT', 'ARC', 'ARE', 'ARK', 'ARM', 'ART', 'AS', 'ASH', 39 'ACT',
40 'ASK', 'AT', 'ATE', 'AUG', 'AUK', 'AVE', 'AWE', 'AWK', 40 'AD',
41 'AWL', 'AWN', 'AX', 'AYE', 'BAD', 'BAG', 'BAH', 'BAM', 41 'ADA',
42 'BAN', 'BAR', 'BAT', 'BAY', 'BE', 'BED', 'BEE', 'BEG', 42 'ADD',
43 'BEN', 'BET', 'BEY', 'BIB', 'BID', 'BIG', 'BIN', 'BIT', 43 'AGO',
44 'BOB', 'BOG', 'BON', 'BOO', 'BOP', 'BOW', 'BOY', 'BUB', 44 'AID',
45 'BUD', 'BUG', 'BUM', 'BUN', 'BUS', 'BUT', 'BUY', 'BY', 45 'AIM',
46 'BYE', 'CAB', 'CAL', 'CAM', 'CAN', 'CAP', 'CAR', 'CAT', 46 'AIR',
47 'CAW', 'COD', 'COG', 'COL', 'CON', 'COO', 'COP', 'COT', 47 'ALL',
48 'COW', 'COY', 'CRY', 'CUB', 'CUE', 'CUP', 'CUR', 'CUT', 48 'ALP',
49 'DAB', 'DAD', 'DAM', 'DAN', 'DAR', 'DAY', 'DEE', 'DEL', 49 'AM',
50 'DEN', 'DES', 'DEW', 'DID', 'DIE', 'DIG', 'DIN', 'DIP', 50 'AMY',
51 'DO', 'DOE', 'DOG', 'DON', 'DOT', 'DOW', 'DRY', 'DUB', 51 'AN',
52 'DUD', 'DUE', 'DUG', 'DUN', 'EAR', 'EAT', 'ED', 'EEL', 52 'ANA',
53 'EGG', 'EGO', 'ELI', 'ELK', 'ELM', 'ELY', 'EM', 'END', 53 'AND',
54 'EST', 'ETC', 'EVA', 'EVE', 'EWE', 'EYE', 'FAD', 'FAN', 54 'ANN',
55 'FAR', 'FAT', 'FAY', 'FED', 'FEE', 'FEW', 'FIB', 'FIG', 55 'ANT',
56 'FIN', 'FIR', 'FIT', 'FLO', 'FLY', 'FOE', 'FOG', 'FOR', 56 'ANY',
57 'FRY', 'FUM', 'FUN', 'FUR', 'GAB', 'GAD', 'GAG', 'GAL', 57 'APE',
58 'GAM', 'GAP', 'GAS', 'GAY', 'GEE', 'GEL', 'GEM', 'GET', 58 'APS',
59 'GIG', 'GIL', 'GIN', 'GO', 'GOT', 'GUM', 'GUN', 'GUS', 59 'APT',
60 'GUT', 'GUY', 'GYM', 'GYP', 'HA', 'HAD', 'HAL', 'HAM', 60 'ARC',
61 'HAN', 'HAP', 'HAS', 'HAT', 'HAW', 'HAY', 'HE', 'HEM', 61 'ARE',
62 'HEN', 'HER', 'HEW', 'HEY', 'HI', 'HID', 'HIM', 'HIP', 62 'ARK',
63 'HIS', 'HIT', 'HO', 'HOB', 'HOC', 'HOE', 'HOG', 'HOP', 63 'ARM',
64 'HOT', 'HOW', 'HUB', 'HUE', 'HUG', 'HUH', 'HUM', 'HUT', 64 'ART',
65 'I', 'ICY', 'IDA', 'IF', 'IKE', 'ILL', 'INK', 'INN', 65 'AS',
66 'IO', 'ION', 'IQ', 'IRA', 'IRE', 'IRK', 'IS', 'IT', 66 'ASH',
67 'ITS', 'IVY', 'JAB', 'JAG', 'JAM', 'JAN', 'JAR', 'JAW', 67 'ASK',
68 'JAY', 'JET', 'JIG', 'JIM', 'JO', 'JOB', 'JOE', 'JOG', 68 'AT',
69 'JOT', 'JOY', 'JUG', 'JUT', 'KAY', 'KEG', 'KEN', 'KEY', 69 'ATE',
70 'KID', 'KIM', 'KIN', 'KIT', 'LA', 'LAB', 'LAC', 'LAD', 70 'AUG',
71 'LAG', 'LAM', 'LAP', 'LAW', 'LAY', 'LEA', 'LED', 'LEE', 71 'AUK',
72 'LEG', 'LEN', 'LEO', 'LET', 'LEW', 'LID', 'LIE', 'LIN', 72 'AVE',
73 'LIP', 'LIT', 'LO', 'LOB', 'LOG', 'LOP', 'LOS', 'LOT', 73 'AWE',
74 'LOU', 'LOW', 'LOY', 'LUG', 'LYE', 'MA', 'MAC', 'MAD', 74 'AWK',
75 'MAE', 'MAN', 'MAO', 'MAP', 'MAT', 'MAW', 'MAY', 'ME', 75 'AWL',
76 'MEG', 'MEL', 'MEN', 'MET', 'MEW', 'MID', 'MIN', 'MIT', 76 'AWN',
77 'MOB', 'MOD', 'MOE', 'MOO', 'MOP', 'MOS', 'MOT', 'MOW', 77 'AX',
78 'MUD', 'MUG', 'MUM', 'MY', 'NAB', 'NAG', 'NAN', 'NAP', 78 'AYE',
79 'NAT', 'NAY', 'NE', 'NED', 'NEE', 'NET', 'NEW', 'NIB', 79 'BAD',
80 'NIL', 'NIP', 'NIT', 'NO', 'NOB', 'NOD', 'NON', 'NOR', 80 'BAG',
81 'NOT', 'NOV', 'NOW', 'NU', 'NUN', 'NUT', 'O', 'OAF', 81 'BAH',
82 'OAK', 'OAR', 'OAT', 'ODD', 'ODE', 'OF', 'OFF', 'OFT', 82 'BAM',
83 'OH', 'OIL', 'OK', 'OLD', 'ON', 'ONE', 'OR', 'ORB', 83 'BAN',
84 'ORE', 'ORR', 'OS', 'OTT', 'OUR', 'OUT', 'OVA', 'OW', 84 'BAR',
85 'OWE', 'OWL', 'OWN', 'OX', 'PA', 'PAD', 'PAL', 'PAM', 85 'BAT',
86 'PAN', 'PAP', 'PAR', 'PAT', 'PAW', 'PAY', 'PEA', 'PEG', 86 'BAY',
87 'PEN', 'PEP', 'PER', 'PET', 'PEW', 'PHI', 'PI', 'PIE', 87 'BE',
88 'PIN', 'PIT', 'PLY', 'PO', 'POD', 'POE', 'POP', 'POT', 88 'BED',
89 'POW', 'PRO', 'PRY', 'PUB', 'PUG', 'PUN', 'PUP', 'PUT', 89 'BEE',
90 'QUO', 'RAG', 'RAM', 'RAN', 'RAP', 'RAT', 'RAW', 'RAY', 90 'BEG',
91 'REB', 'RED', 'REP', 'RET', 'RIB', 'RID', 'RIG', 'RIM', 91 'BEN',
92 'RIO', 'RIP', 'ROB', 'ROD', 'ROE', 'RON', 'ROT', 'ROW', 92 'BET',
93 'ROY', 'RUB', 'RUE', 'RUG', 'RUM', 'RUN', 'RYE', 'SAC', 93 'BEY',
94 'SAD', 'SAG', 'SAL', 'SAM', 'SAN', 'SAP', 'SAT', 'SAW', 94 'BIB',
95 'SAY', 'SEA', 'SEC', 'SEE', 'SEN', 'SET', 'SEW', 'SHE', 95 'BID',
96 'SHY', 'SIN', 'SIP', 'SIR', 'SIS', 'SIT', 'SKI', 'SKY', 96 'BIG',
97 'SLY', 'SO', 'SOB', 'SOD', 'SON', 'SOP', 'SOW', 'SOY', 97 'BIN',
98 'SPA', 'SPY', 'SUB', 'SUD', 'SUE', 'SUM', 'SUN', 'SUP', 98 'BIT',
99 'TAB', 'TAD', 'TAG', 'TAN', 'TAP', 'TAR', 'TEA', 'TED', 99 'BOB',
100 'TEE', 'TEN', 'THE', 'THY', 'TIC', 'TIE', 'TIM', 'TIN', 100 'BOG',
101 'TIP', 'TO', 'TOE', 'TOG', 'TOM', 'TON', 'TOO', 'TOP', 101 'BON',
102 'TOW', 'TOY', 'TRY', 'TUB', 'TUG', 'TUM', 'TUN', 'TWO', 102 'BOO',
103 'UN', 'UP', 'US', 'USE', 'VAN', 'VAT', 'VET', 'VIE', 103 'BOP',
104 'WAD', 'WAG', 'WAR', 'WAS', 'WAY', 'WE', 'WEB', 'WED', 104 'BOW',
105 'WEE', 'WET', 'WHO', 'WHY', 'WIN', 'WIT', 'WOK', 'WON', 105 'BOY',
106 'WOO', 'WOW', 'WRY', 'WU', 'YAM', 'YAP', 'YAW', 'YE', 106 'BUB',
107 'YEA', 'YES', 'YET', 'YOU', 'ABED', 'ABEL', 'ABET', 'ABLE', 107 'BUD',
108 'ABUT', 'ACHE', 'ACID', 'ACME', 'ACRE', 'ACTA', 'ACTS', 'ADAM', 108 'BUG',
109 'ADDS', 'ADEN', 'AFAR', 'AFRO', 'AGEE', 'AHEM', 'AHOY', 'AIDA', 109 'BUM',
110 'AIDE', 'AIDS', 'AIRY', 'AJAR', 'AKIN', 'ALAN', 'ALEC', 'ALGA', 110 'BUN',
111 'ALIA', 'ALLY', 'ALMA', 'ALOE', 'ALSO', 'ALTO', 'ALUM', 'ALVA', 111 'BUS',
112 'AMEN', 'AMES', 'AMID', 'AMMO', 'AMOK', 'AMOS', 'AMRA', 'ANDY', 112 'BUT',
113 'ANEW', 'ANNA', 'ANNE', 'ANTE', 'ANTI', 'AQUA', 'ARAB', 'ARCH', 113 'BUY',
114 'AREA', 'ARGO', 'ARID', 'ARMY', 'ARTS', 'ARTY', 'ASIA', 'ASKS', 114 'BY',
115 'ATOM', 'AUNT', 'AURA', 'AUTO', 'AVER', 'AVID', 'AVIS', 'AVON', 115 'BYE',
116 'AVOW', 'AWAY', 'AWRY', 'BABE', 'BABY', 'BACH', 'BACK', 'BADE', 116 'CAB',
117 'BAIL', 'BAIT', 'BAKE', 'BALD', 'BALE', 'BALI', 'BALK', 'BALL', 117 'CAL',
118 'BALM', 'BAND', 'BANE', 'BANG', 'BANK', 'BARB', 'BARD', 'BARE', 118 'CAM',
119 'BARK', 'BARN', 'BARR', 'BASE', 'BASH', 'BASK', 'BASS', 'BATE', 119 'CAN',
120 'BATH', 'BAWD', 'BAWL', 'BEAD', 'BEAK', 'BEAM', 'BEAN', 'BEAR', 120 'CAP',
121 'BEAT', 'BEAU', 'BECK', 'BEEF', 'BEEN', 'BEER', 'BEET', 'BELA', 121 'CAR',
122 'BELL', 'BELT', 'BEND', 'BENT', 'BERG', 'BERN', 'BERT', 'BESS', 122 'CAT',
123 'BEST', 'BETA', 'BETH', 'BHOY', 'BIAS', 'BIDE', 'BIEN', 'BILE', 123 'CAW',
124 'BILK', 'BILL', 'BIND', 'BING', 'BIRD', 'BITE', 'BITS', 'BLAB', 124 'COD',
125 'BLAT', 'BLED', 'BLEW', 'BLOB', 'BLOC', 'BLOT', 'BLOW', 'BLUE', 125 'COG',
126 'BLUM', 'BLUR', 'BOAR', 'BOAT', 'BOCA', 'BOCK', 'BODE', 'BODY', 126 'COL',
127 'BOGY', 'BOHR', 'BOIL', 'BOLD', 'BOLO', 'BOLT', 'BOMB', 'BONA', 127 'CON',
128 'BOND', 'BONE', 'BONG', 'BONN', 'BONY', 'BOOK', 'BOOM', 'BOON', 128 'COO',
129 'BOOT', 'BORE', 'BORG', 'BORN', 'BOSE', 'BOSS', 'BOTH', 'BOUT', 129 'COP',
130 'BOWL', 'BOYD', 'BRAD', 'BRAE', 'BRAG', 'BRAN', 'BRAY', 'BRED', 130 'COT',
131 'BREW', 'BRIG', 'BRIM', 'BROW', 'BUCK', 'BUDD', 'BUFF', 'BULB', 131 'COW',
132 'BULK', 'BULL', 'BUNK', 'BUNT', 'BUOY', 'BURG', 'BURL', 'BURN', 132 'COY',
133 'BURR', 'BURT', 'BURY', 'BUSH', 'BUSS', 'BUST', 'BUSY', 'BYTE', 133 'CRY',
134 'CADY', 'CAFE', 'CAGE', 'CAIN', 'CAKE', 'CALF', 'CALL', 'CALM', 134 'CUB',
135 'CAME', 'CANE', 'CANT', 'CARD', 'CARE', 'CARL', 'CARR', 'CART', 135 'CUE',
136 'CASE', 'CASH', 'CASK', 'CAST', 'CAVE', 'CEIL', 'CELL', 'CENT', 136 'CUP',
137 'CERN', 'CHAD', 'CHAR', 'CHAT', 'CHAW', 'CHEF', 'CHEN', 'CHEW', 137 'CUR',
138 'CHIC', 'CHIN', 'CHOU', 'CHOW', 'CHUB', 'CHUG', 'CHUM', 'CITE', 138 'CUT',
139 'CITY', 'CLAD', 'CLAM', 'CLAN', 'CLAW', 'CLAY', 'CLOD', 'CLOG', 139 'DAB',
140 'CLOT', 'CLUB', 'CLUE', 'COAL', 'COAT', 'COCA', 'COCK', 'COCO', 140 'DAD',
141 'CODA', 'CODE', 'CODY', 'COED', 'COIL', 'COIN', 'COKE', 'COLA', 141 'DAM',
142 'COLD', 'COLT', 'COMA', 'COMB', 'COME', 'COOK', 'COOL', 'COON', 142 'DAN',
143 'COOT', 'CORD', 'CORE', 'CORK', 'CORN', 'COST', 'COVE', 'COWL', 143 'DAR',
144 'CRAB', 'CRAG', 'CRAM', 'CRAY', 'CREW', 'CRIB', 'CROW', 'CRUD', 144 'DAY',
145 'CUBA', 'CUBE', 'CUFF', 'CULL', 'CULT', 'CUNY', 'CURB', 'CURD', 145 'DEE',
146 'CURE', 'CURL', 'CURT', 'CUTS', 'DADE', 'DALE', 'DAME', 'DANA', 146 'DEL',
147 'DANE', 'DANG', 'DANK', 'DARE', 'DARK', 'DARN', 'DART', 'DASH', 147 'DEN',
148 'DATA', 'DATE', 'DAVE', 'DAVY', 'DAWN', 'DAYS', 'DEAD', 'DEAF', 148 'DES',
149 'DEAL', 'DEAN', 'DEAR', 'DEBT', 'DECK', 'DEED', 'DEEM', 'DEER', 149 'DEW',
150 'DEFT', 'DEFY', 'DELL', 'DENT', 'DENY', 'DESK', 'DIAL', 'DICE', 150 'DID',
151 'DIED', 'DIET', 'DIME', 'DINE', 'DING', 'DINT', 'DIRE', 'DIRT', 151 'DIE',
152 'DISC', 'DISH', 'DISK', 'DIVE', 'DOCK', 'DOES', 'DOLE', 'DOLL', 152 'DIG',
153 'DOLT', 'DOME', 'DONE', 'DOOM', 'DOOR', 'DORA', 'DOSE', 'DOTE', 153 'DIN',
154 'DOUG', 'DOUR', 'DOVE', 'DOWN', 'DRAB', 'DRAG', 'DRAM', 'DRAW', 154 'DIP',
155 'DREW', 'DRUB', 'DRUG', 'DRUM', 'DUAL', 'DUCK', 'DUCT', 'DUEL', 155 'DO',
156 'DUET', 'DUKE', 'DULL', 'DUMB', 'DUNE', 'DUNK', 'DUSK', 'DUST', 156 'DOE',
157 'DUTY', 'EACH', 'EARL', 'EARN', 'EASE', 'EAST', 'EASY', 'EBEN', 157 'DOG',
158 'ECHO', 'EDDY', 'EDEN', 'EDGE', 'EDGY', 'EDIT', 'EDNA', 'EGAN', 158 'DON',
159 'ELAN', 'ELBA', 'ELLA', 'ELSE', 'EMIL', 'EMIT', 'EMMA', 'ENDS', 159 'DOT',
160 'ERIC', 'EROS', 'EVEN', 'EVER', 'EVIL', 'EYED', 'FACE', 'FACT', 160 'DOW',
161 'FADE', 'FAIL', 'FAIN', 'FAIR', 'FAKE', 'FALL', 'FAME', 'FANG', 161 'DRY',
162 'FARM', 'FAST', 'FATE', 'FAWN', 'FEAR', 'FEAT', 'FEED', 'FEEL', 162 'DUB',
163 'FEET', 'FELL', 'FELT', 'FEND', 'FERN', 'FEST', 'FEUD', 'FIEF', 163 'DUD',
164 'FIGS', 'FILE', 'FILL', 'FILM', 'FIND', 'FINE', 'FINK', 'FIRE', 164 'DUE',
165 'FIRM', 'FISH', 'FISK', 'FIST', 'FITS', 'FIVE', 'FLAG', 'FLAK', 165 'DUG',
166 'FLAM', 'FLAT', 'FLAW', 'FLEA', 'FLED', 'FLEW', 'FLIT', 'FLOC', 166 'DUN',
167 'FLOG', 'FLOW', 'FLUB', 'FLUE', 'FOAL', 'FOAM', 'FOGY', 'FOIL', 167 'EAR',
168 'FOLD', 'FOLK', 'FOND', 'FONT', 'FOOD', 'FOOL', 'FOOT', 'FORD', 168 'EAT',
169 'FORE', 'FORK', 'FORM', 'FORT', 'FOSS', 'FOUL', 'FOUR', 'FOWL', 169 'ED',
170 'FRAU', 'FRAY', 'FRED', 'FREE', 'FRET', 'FREY', 'FROG', 'FROM', 170 'EEL',
171 'FUEL', 'FULL', 'FUME', 'FUND', 'FUNK', 'FURY', 'FUSE', 'FUSS', 171 'EGG',
172 'GAFF', 'GAGE', 'GAIL', 'GAIN', 'GAIT', 'GALA', 'GALE', 'GALL', 172 'EGO',
173 'GALT', 'GAME', 'GANG', 'GARB', 'GARY', 'GASH', 'GATE', 'GAUL', 173 'ELI',
174 'GAUR', 'GAVE', 'GAWK', 'GEAR', 'GELD', 'GENE', 'GENT', 'GERM', 174 'ELK',
175 'GETS', 'GIBE', 'GIFT', 'GILD', 'GILL', 'GILT', 'GINA', 'GIRD', 175 'ELM',
176 'GIRL', 'GIST', 'GIVE', 'GLAD', 'GLEE', 'GLEN', 'GLIB', 'GLOB', 176 'ELY',
177 'GLOM', 'GLOW', 'GLUE', 'GLUM', 'GLUT', 'GOAD', 'GOAL', 'GOAT', 177 'EM',
178 'GOER', 'GOES', 'GOLD', 'GOLF', 'GONE', 'GONG', 'GOOD', 'GOOF', 178 'END',
179 'GORE', 'GORY', 'GOSH', 'GOUT', 'GOWN', 'GRAB', 'GRAD', 'GRAY', 179 'EST',
180 'GREG', 'GREW', 'GREY', 'GRID', 'GRIM', 'GRIN', 'GRIT', 'GROW', 180 'ETC',
181 'GRUB', 'GULF', 'GULL', 'GUNK', 'GURU', 'GUSH', 'GUST', 'GWEN', 181 'EVA',
182 'GWYN', 'HAAG', 'HAAS', 'HACK', 'HAIL', 'HAIR', 'HALE', 'HALF', 182 'EVE',
183 'HALL', 'HALO', 'HALT', 'HAND', 'HANG', 'HANK', 'HANS', 'HARD', 183 'EWE',
184 'HARK', 'HARM', 'HART', 'HASH', 'HAST', 'HATE', 'HATH', 'HAUL', 184 'EYE',
185 'HAVE', 'HAWK', 'HAYS', 'HEAD', 'HEAL', 'HEAR', 'HEAT', 'HEBE', 185 'FAD',
186 'HECK', 'HEED', 'HEEL', 'HEFT', 'HELD', 'HELL', 'HELM', 'HERB', 186 'FAN',
187 'HERD', 'HERE', 'HERO', 'HERS', 'HESS', 'HEWN', 'HICK', 'HIDE', 187 'FAR',
188 'HIGH', 'HIKE', 'HILL', 'HILT', 'HIND', 'HINT', 'HIRE', 'HISS', 188 'FAT',
189 'HIVE', 'HOBO', 'HOCK', 'HOFF', 'HOLD', 'HOLE', 'HOLM', 'HOLT', 189 'FAY',
190 'HOME', 'HONE', 'HONK', 'HOOD', 'HOOF', 'HOOK', 'HOOT', 'HORN', 190 'FED',
191 'HOSE', 'HOST', 'HOUR', 'HOVE', 'HOWE', 'HOWL', 'HOYT', 'HUCK', 191 'FEE',
192 'HUED', 'HUFF', 'HUGE', 'HUGH', 'HUGO', 'HULK', 'HULL', 'HUNK', 192 'FEW',
193 'HUNT', 'HURD', 'HURL', 'HURT', 'HUSH', 'HYDE', 'HYMN', 'IBIS', 193 'FIB',
194 'ICON', 'IDEA', 'IDLE', 'IFFY', 'INCA', 'INCH', 'INTO', 'IONS', 194 'FIG',
195 'IOTA', 'IOWA', 'IRIS', 'IRMA', 'IRON', 'ISLE', 'ITCH', 'ITEM', 195 'FIN',
196 'IVAN', 'JACK', 'JADE', 'JAIL', 'JAKE', 'JANE', 'JAVA', 'JEAN', 196 'FIR',
197 'JEFF', 'JERK', 'JESS', 'JEST', 'JIBE', 'JILL', 'JILT', 'JIVE', 197 'FIT',
198 'JOAN', 'JOBS', 'JOCK', 'JOEL', 'JOEY', 'JOHN', 'JOIN', 'JOKE', 198 'FLO',
199 'JOLT', 'JOVE', 'JUDD', 'JUDE', 'JUDO', 'JUDY', 'JUJU', 'JUKE', 199 'FLY',
200 'JULY', 'JUNE', 'JUNK', 'JUNO', 'JURY', 'JUST', 'JUTE', 'KAHN', 200 'FOE',
201 'KALE', 'KANE', 'KANT', 'KARL', 'KATE', 'KEEL', 'KEEN', 'KENO', 201 'FOG',
202 'KENT', 'KERN', 'KERR', 'KEYS', 'KICK', 'KILL', 'KIND', 'KING', 202 'FOR',
203 'KIRK', 'KISS', 'KITE', 'KLAN', 'KNEE', 'KNEW', 'KNIT', 'KNOB', 203 'FRY',
204 'KNOT', 'KNOW', 'KOCH', 'KONG', 'KUDO', 'KURD', 'KURT', 'KYLE', 204 'FUM',
205 'LACE', 'LACK', 'LACY', 'LADY', 'LAID', 'LAIN', 'LAIR', 'LAKE', 205 'FUN',
206 'LAMB', 'LAME', 'LAND', 'LANE', 'LANG', 'LARD', 'LARK', 'LASS', 206 'FUR',
207 'LAST', 'LATE', 'LAUD', 'LAVA', 'LAWN', 'LAWS', 'LAYS', 'LEAD', 207 'GAB',
208 'LEAF', 'LEAK', 'LEAN', 'LEAR', 'LEEK', 'LEER', 'LEFT', 'LEND', 208 'GAD',
209 'LENS', 'LENT', 'LEON', 'LESK', 'LESS', 'LEST', 'LETS', 'LIAR', 209 'GAG',
210 'LICE', 'LICK', 'LIED', 'LIEN', 'LIES', 'LIEU', 'LIFE', 'LIFT', 210 'GAL',
211 'LIKE', 'LILA', 'LILT', 'LILY', 'LIMA', 'LIMB', 'LIME', 'LIND', 211 'GAM',
212 'LINE', 'LINK', 'LINT', 'LION', 'LISA', 'LIST', 'LIVE', 'LOAD', 212 'GAP',
213 'LOAF', 'LOAM', 'LOAN', 'LOCK', 'LOFT', 'LOGE', 'LOIS', 'LOLA', 213 'GAS',
214 'LONE', 'LONG', 'LOOK', 'LOON', 'LOOT', 'LORD', 'LORE', 'LOSE', 214 'GAY',
215 'LOSS', 'LOST', 'LOUD', 'LOVE', 'LOWE', 'LUCK', 'LUCY', 'LUGE', 215 'GEE',
216 'LUKE', 'LULU', 'LUND', 'LUNG', 'LURA', 'LURE', 'LURK', 'LUSH', 216 'GEL',
217 'LUST', 'LYLE', 'LYNN', 'LYON', 'LYRA', 'MACE', 'MADE', 'MAGI', 217 'GEM',
218 'MAID', 'MAIL', 'MAIN', 'MAKE', 'MALE', 'MALI', 'MALL', 'MALT', 218 'GET',
219 'MANA', 'MANN', 'MANY', 'MARC', 'MARE', 'MARK', 'MARS', 'MART', 219 'GIG',
220 'MARY', 'MASH', 'MASK', 'MASS', 'MAST', 'MATE', 'MATH', 'MAUL', 220 'GIL',
221 'MAYO', 'MEAD', 'MEAL', 'MEAN', 'MEAT', 'MEEK', 'MEET', 'MELD', 221 'GIN',
222 'MELT', 'MEMO', 'MEND', 'MENU', 'MERT', 'MESH', 'MESS', 'MICE', 222 'GO',
223 'MIKE', 'MILD', 'MILE', 'MILK', 'MILL', 'MILT', 'MIMI', 'MIND', 223 'GOT',
224 'MINE', 'MINI', 'MINK', 'MINT', 'MIRE', 'MISS', 'MIST', 'MITE', 224 'GUM',
225 'MITT', 'MOAN', 'MOAT', 'MOCK', 'MODE', 'MOLD', 'MOLE', 'MOLL', 225 'GUN',
226 'MOLT', 'MONA', 'MONK', 'MONT', 'MOOD', 'MOON', 'MOOR', 'MOOT', 226 'GUS',
227 'MORE', 'MORN', 'MORT', 'MOSS', 'MOST', 'MOTH', 'MOVE', 'MUCH', 227 'GUT',
228 'MUCK', 'MUDD', 'MUFF', 'MULE', 'MULL', 'MURK', 'MUSH', 'MUST', 228 'GUY',
229 'MUTE', 'MUTT', 'MYRA', 'MYTH', 'NAGY', 'NAIL', 'NAIR', 'NAME', 229 'GYM',
230 'NARY', 'NASH', 'NAVE', 'NAVY', 'NEAL', 'NEAR', 'NEAT', 'NECK', 230 'GYP',
231 'NEED', 'NEIL', 'NELL', 'NEON', 'NERO', 'NESS', 'NEST', 'NEWS', 231 'HA',
232 'NEWT', 'NIBS', 'NICE', 'NICK', 'NILE', 'NINA', 'NINE', 'NOAH', 232 'HAD',
233 'NODE', 'NOEL', 'NOLL', 'NONE', 'NOOK', 'NOON', 'NORM', 'NOSE', 233 'HAL',
234 'NOTE', 'NOUN', 'NOVA', 'NUDE', 'NULL', 'NUMB', 'OATH', 'OBEY', 234 'HAM',
235 'OBOE', 'ODIN', 'OHIO', 'OILY', 'OINT', 'OKAY', 'OLAF', 'OLDY', 235 'HAN',
236 'OLGA', 'OLIN', 'OMAN', 'OMEN', 'OMIT', 'ONCE', 'ONES', 'ONLY', 236 'HAP',
237 'ONTO', 'ONUS', 'ORAL', 'ORGY', 'OSLO', 'OTIS', 'OTTO', 'OUCH', 237 'HAS',
238 'OUST', 'OUTS', 'OVAL', 'OVEN', 'OVER', 'OWLY', 'OWNS', 'QUAD', 238 'HAT',
239 'QUIT', 'QUOD', 'RACE', 'RACK', 'RACY', 'RAFT', 'RAGE', 'RAID', 239 'HAW',
240 'RAIL', 'RAIN', 'RAKE', 'RANK', 'RANT', 'RARE', 'RASH', 'RATE', 240 'HAY',
241 'RAVE', 'RAYS', 'READ', 'REAL', 'REAM', 'REAR', 'RECK', 'REED', 241 'HE',
242 'REEF', 'REEK', 'REEL', 'REID', 'REIN', 'RENA', 'REND', 'RENT', 242 'HEM',
243 'REST', 'RICE', 'RICH', 'RICK', 'RIDE', 'RIFT', 'RILL', 'RIME', 243 'HEN',
244 'RING', 'RINK', 'RISE', 'RISK', 'RITE', 'ROAD', 'ROAM', 'ROAR', 244 'HER',
245 'ROBE', 'ROCK', 'RODE', 'ROIL', 'ROLL', 'ROME', 'ROOD', 'ROOF', 245 'HEW',
246 'ROOK', 'ROOM', 'ROOT', 'ROSA', 'ROSE', 'ROSS', 'ROSY', 'ROTH', 246 'HEY',
247 'ROUT', 'ROVE', 'ROWE', 'ROWS', 'RUBE', 'RUBY', 'RUDE', 'RUDY', 247 'HI',
248 'RUIN', 'RULE', 'RUNG', 'RUNS', 'RUNT', 'RUSE', 'RUSH', 'RUSK', 248 'HID',
249 'RUSS', 'RUST', 'RUTH', 'SACK', 'SAFE', 'SAGE', 'SAID', 'SAIL', 249 'HIM',
250 'SALE', 'SALK', 'SALT', 'SAME', 'SAND', 'SANE', 'SANG', 'SANK', 250 'HIP',
251 'SARA', 'SAUL', 'SAVE', 'SAYS', 'SCAN', 'SCAR', 'SCAT', 'SCOT', 251 'HIS',
252 'SEAL', 'SEAM', 'SEAR', 'SEAT', 'SEED', 'SEEK', 'SEEM', 'SEEN', 252 'HIT',
253 'SEES', 'SELF', 'SELL', 'SEND', 'SENT', 'SETS', 'SEWN', 'SHAG', 253 'HO',
254 'SHAM', 'SHAW', 'SHAY', 'SHED', 'SHIM', 'SHIN', 'SHOD', 'SHOE', 254 'HOB',
255 'SHOT', 'SHOW', 'SHUN', 'SHUT', 'SICK', 'SIDE', 'SIFT', 'SIGH', 255 'HOC',
256 'SIGN', 'SILK', 'SILL', 'SILO', 'SILT', 'SINE', 'SING', 'SINK', 256 'HOE',
257 'SIRE', 'SITE', 'SITS', 'SITU', 'SKAT', 'SKEW', 'SKID', 'SKIM', 257 'HOG',
258 'SKIN', 'SKIT', 'SLAB', 'SLAM', 'SLAT', 'SLAY', 'SLED', 'SLEW', 258 'HOP',
259 'SLID', 'SLIM', 'SLIT', 'SLOB', 'SLOG', 'SLOT', 'SLOW', 'SLUG', 259 'HOT',
260 'SLUM', 'SLUR', 'SMOG', 'SMUG', 'SNAG', 'SNOB', 'SNOW', 'SNUB', 260 'HOW',
261 'SNUG', 'SOAK', 'SOAR', 'SOCK', 'SODA', 'SOFA', 'SOFT', 'SOIL', 261 'HUB',
262 'SOLD', 'SOME', 'SONG', 'SOON', 'SOOT', 'SORE', 'SORT', 'SOUL', 262 'HUE',
263 'SOUR', 'SOWN', 'STAB', 'STAG', 'STAN', 'STAR', 'STAY', 'STEM', 263 'HUG',
264 'STEW', 'STIR', 'STOW', 'STUB', 'STUN', 'SUCH', 'SUDS', 'SUIT', 264 'HUH',
265 'SULK', 'SUMS', 'SUNG', 'SUNK', 'SURE', 'SURF', 'SWAB', 'SWAG', 265 'HUM',
266 'SWAM', 'SWAN', 'SWAT', 'SWAY', 'SWIM', 'SWUM', 'TACK', 'TACT', 266 'HUT',
267 'TAIL', 'TAKE', 'TALE', 'TALK', 'TALL', 'TANK', 'TASK', 'TATE', 267 'I',
268 'TAUT', 'TEAL', 'TEAM', 'TEAR', 'TECH', 'TEEM', 'TEEN', 'TEET', 268 'ICY',
269 'TELL', 'TEND', 'TENT', 'TERM', 'TERN', 'TESS', 'TEST', 'THAN', 269 'IDA',
270 'THAT', 'THEE', 'THEM', 'THEN', 'THEY', 'THIN', 'THIS', 'THUD', 270 'IF',
271 'THUG', 'TICK', 'TIDE', 'TIDY', 'TIED', 'TIER', 'TILE', 'TILL', 271 'IKE',
272 'TILT', 'TIME', 'TINA', 'TINE', 'TINT', 'TINY', 'TIRE', 'TOAD', 272 'ILL',
273 'TOGO', 'TOIL', 'TOLD', 'TOLL', 'TONE', 'TONG', 'TONY', 'TOOK', 273 'INK',
274 'TOOL', 'TOOT', 'TORE', 'TORN', 'TOTE', 'TOUR', 'TOUT', 'TOWN', 274 'INN',
275 'TRAG', 'TRAM', 'TRAY', 'TREE', 'TREK', 'TRIG', 'TRIM', 'TRIO', 275 'IO',
276 'TROD', 'TROT', 'TROY', 'TRUE', 'TUBA', 'TUBE', 'TUCK', 'TUFT', 276 'ION',
277 'TUNA', 'TUNE', 'TUNG', 'TURF', 'TURN', 'TUSK', 'TWIG', 'TWIN', 277 'IQ',
278 'TWIT', 'ULAN', 'UNIT', 'URGE', 'USED', 'USER', 'USES', 'UTAH', 278 'IRA',
279 'VAIL', 'VAIN', 'VALE', 'VARY', 'VASE', 'VAST', 'VEAL', 'VEDA', 279 'IRE',
280 'VEIL', 'VEIN', 'VEND', 'VENT', 'VERB', 'VERY', 'VETO', 'VICE', 280 'IRK',
281 'VIEW', 'VINE', 'VISE', 'VOID', 'VOLT', 'VOTE', 'WACK', 'WADE', 281 'IS',
282 'WAGE', 'WAIL', 'WAIT', 'WAKE', 'WALE', 'WALK', 'WALL', 'WALT', 282 'IT',
283 'WAND', 'WANE', 'WANG', 'WANT', 'WARD', 'WARM', 'WARN', 'WART', 283 'ITS',
284 'WASH', 'WAST', 'WATS', 'WATT', 'WAVE', 'WAVY', 'WAYS', 'WEAK', 284 'IVY',
285 'WEAL', 'WEAN', 'WEAR', 'WEED', 'WEEK', 'WEIR', 'WELD', 'WELL', 285 'JAB',
286 'WELT', 'WENT', 'WERE', 'WERT', 'WEST', 'WHAM', 'WHAT', 'WHEE', 286 'JAG',
287 'WHEN', 'WHET', 'WHOA', 'WHOM', 'WICK', 'WIFE', 'WILD', 'WILL', 287 'JAM',
288 'WIND', 'WINE', 'WING', 'WINK', 'WINO', 'WIRE', 'WISE', 'WISH', 288 'JAN',
289 'WITH', 'WOLF', 'WONT', 'WOOD', 'WOOL', 'WORD', 'WORE', 'WORK', 289 'JAR',
290 'WORM', 'WORN', 'WOVE', 'WRIT', 'WYNN', 'YALE', 'YANG', 'YANK', 290 'JAW',
291 'YARD', 'YARN', 'YAWL', 'YAWN', 'YEAH', 'YEAR', 'YELL', 'YOGA', 291 'JAY',
292 'YOKE'] 292 'JET',
293 'JIG',
294 'JIM',
295 'JO',
296 'JOB',
297 'JOE',
298 'JOG',
299 'JOT',
300 'JOY',
301 'JUG',
302 'JUT',
303 'KAY',
304 'KEG',
305 'KEN',
306 'KEY',
307 'KID',
308 'KIM',
309 'KIN',
310 'KIT',
311 'LA',
312 'LAB',
313 'LAC',
314 'LAD',
315 'LAG',
316 'LAM',
317 'LAP',
318 'LAW',
319 'LAY',
320 'LEA',
321 'LED',
322 'LEE',
323 'LEG',
324 'LEN',
325 'LEO',
326 'LET',
327 'LEW',
328 'LID',
329 'LIE',
330 'LIN',
331 'LIP',
332 'LIT',
333 'LO',
334 'LOB',
335 'LOG',
336 'LOP',
337 'LOS',
338 'LOT',
339 'LOU',
340 'LOW',
341 'LOY',
342 'LUG',
343 'LYE',
344 'MA',
345 'MAC',
346 'MAD',
347 'MAE',
348 'MAN',
349 'MAO',
350 'MAP',
351 'MAT',
352 'MAW',
353 'MAY',
354 'ME',
355 'MEG',
356 'MEL',
357 'MEN',
358 'MET',
359 'MEW',
360 'MID',
361 'MIN',
362 'MIT',
363 'MOB',
364 'MOD',
365 'MOE',
366 'MOO',
367 'MOP',
368 'MOS',
369 'MOT',
370 'MOW',
371 'MUD',
372 'MUG',
373 'MUM',
374 'MY',
375 'NAB',
376 'NAG',
377 'NAN',
378 'NAP',
379 'NAT',
380 'NAY',
381 'NE',
382 'NED',
383 'NEE',
384 'NET',
385 'NEW',
386 'NIB',
387 'NIL',
388 'NIP',
389 'NIT',
390 'NO',
391 'NOB',
392 'NOD',
393 'NON',
394 'NOR',
395 'NOT',
396 'NOV',
397 'NOW',
398 'NU',
399 'NUN',
400 'NUT',
401 'O',
402 'OAF',
403 'OAK',
404 'OAR',
405 'OAT',
406 'ODD',
407 'ODE',
408 'OF',
409 'OFF',
410 'OFT',
411 'OH',
412 'OIL',
413 'OK',
414 'OLD',
415 'ON',
416 'ONE',
417 'OR',
418 'ORB',
419 'ORE',
420 'ORR',
421 'OS',
422 'OTT',
423 'OUR',
424 'OUT',
425 'OVA',
426 'OW',
427 'OWE',
428 'OWL',
429 'OWN',
430 'OX',
431 'PA',
432 'PAD',
433 'PAL',
434 'PAM',
435 'PAN',
436 'PAP',
437 'PAR',
438 'PAT',
439 'PAW',
440 'PAY',
441 'PEA',
442 'PEG',
443 'PEN',
444 'PEP',
445 'PER',
446 'PET',
447 'PEW',
448 'PHI',
449 'PI',
450 'PIE',
451 'PIN',
452 'PIT',
453 'PLY',
454 'PO',
455 'POD',
456 'POE',
457 'POP',
458 'POT',
459 'POW',
460 'PRO',
461 'PRY',
462 'PUB',
463 'PUG',
464 'PUN',
465 'PUP',
466 'PUT',
467 'QUO',
468 'RAG',
469 'RAM',
470 'RAN',
471 'RAP',
472 'RAT',
473 'RAW',
474 'RAY',
475 'REB',
476 'RED',
477 'REP',
478 'RET',
479 'RIB',
480 'RID',
481 'RIG',
482 'RIM',
483 'RIO',
484 'RIP',
485 'ROB',
486 'ROD',
487 'ROE',
488 'RON',
489 'ROT',
490 'ROW',
491 'ROY',
492 'RUB',
493 'RUE',
494 'RUG',
495 'RUM',
496 'RUN',
497 'RYE',
498 'SAC',
499 'SAD',
500 'SAG',
501 'SAL',
502 'SAM',
503 'SAN',
504 'SAP',
505 'SAT',
506 'SAW',
507 'SAY',
508 'SEA',
509 'SEC',
510 'SEE',
511 'SEN',
512 'SET',
513 'SEW',
514 'SHE',
515 'SHY',
516 'SIN',
517 'SIP',
518 'SIR',
519 'SIS',
520 'SIT',
521 'SKI',
522 'SKY',
523 'SLY',
524 'SO',
525 'SOB',
526 'SOD',
527 'SON',
528 'SOP',
529 'SOW',
530 'SOY',
531 'SPA',
532 'SPY',
533 'SUB',
534 'SUD',
535 'SUE',
536 'SUM',
537 'SUN',
538 'SUP',
539 'TAB',
540 'TAD',
541 'TAG',
542 'TAN',
543 'TAP',
544 'TAR',
545 'TEA',
546 'TED',
547 'TEE',
548 'TEN',
549 'THE',
550 'THY',
551 'TIC',
552 'TIE',
553 'TIM',
554 'TIN',
555 'TIP',
556 'TO',
557 'TOE',
558 'TOG',
559 'TOM',
560 'TON',
561 'TOO',
562 'TOP',
563 'TOW',
564 'TOY',
565 'TRY',
566 'TUB',
567 'TUG',
568 'TUM',
569 'TUN',
570 'TWO',
571 'UN',
572 'UP',
573 'US',
574 'USE',
575 'VAN',
576 'VAT',
577 'VET',
578 'VIE',
579 'WAD',
580 'WAG',
581 'WAR',
582 'WAS',
583 'WAY',
584 'WE',
585 'WEB',
586 'WED',
587 'WEE',
588 'WET',
589 'WHO',
590 'WHY',
591 'WIN',
592 'WIT',
593 'WOK',
594 'WON',
595 'WOO',
596 'WOW',
597 'WRY',
598 'WU',
599 'YAM',
600 'YAP',
601 'YAW',
602 'YE',
603 'YEA',
604 'YES',
605 'YET',
606 'YOU',
607 'ABED',
608 'ABEL',
609 'ABET',
610 'ABLE',
611 'ABUT',
612 'ACHE',
613 'ACID',
614 'ACME',
615 'ACRE',
616 'ACTA',
617 'ACTS',
618 'ADAM',
619 'ADDS',
620 'ADEN',
621 'AFAR',
622 'AFRO',
623 'AGEE',
624 'AHEM',
625 'AHOY',
626 'AIDA',
627 'AIDE',
628 'AIDS',
629 'AIRY',
630 'AJAR',
631 'AKIN',
632 'ALAN',
633 'ALEC',
634 'ALGA',
635 'ALIA',
636 'ALLY',
637 'ALMA',
638 'ALOE',
639 'ALSO',
640 'ALTO',
641 'ALUM',
642 'ALVA',
643 'AMEN',
644 'AMES',
645 'AMID',
646 'AMMO',
647 'AMOK',
648 'AMOS',
649 'AMRA',
650 'ANDY',
651 'ANEW',
652 'ANNA',
653 'ANNE',
654 'ANTE',
655 'ANTI',
656 'AQUA',
657 'ARAB',
658 'ARCH',
659 'AREA',
660 'ARGO',
661 'ARID',
662 'ARMY',
663 'ARTS',
664 'ARTY',
665 'ASIA',
666 'ASKS',
667 'ATOM',
668 'AUNT',
669 'AURA',
670 'AUTO',
671 'AVER',
672 'AVID',
673 'AVIS',
674 'AVON',
675 'AVOW',
676 'AWAY',
677 'AWRY',
678 'BABE',
679 'BABY',
680 'BACH',
681 'BACK',
682 'BADE',
683 'BAIL',
684 'BAIT',
685 'BAKE',
686 'BALD',
687 'BALE',
688 'BALI',
689 'BALK',
690 'BALL',
691 'BALM',
692 'BAND',
693 'BANE',
694 'BANG',
695 'BANK',
696 'BARB',
697 'BARD',
698 'BARE',
699 'BARK',
700 'BARN',
701 'BARR',
702 'BASE',
703 'BASH',
704 'BASK',
705 'BASS',
706 'BATE',
707 'BATH',
708 'BAWD',
709 'BAWL',
710 'BEAD',
711 'BEAK',
712 'BEAM',
713 'BEAN',
714 'BEAR',
715 'BEAT',
716 'BEAU',
717 'BECK',
718 'BEEF',
719 'BEEN',
720 'BEER',
721 'BEET',
722 'BELA',
723 'BELL',
724 'BELT',
725 'BEND',
726 'BENT',
727 'BERG',
728 'BERN',
729 'BERT',
730 'BESS',
731 'BEST',
732 'BETA',
733 'BETH',
734 'BHOY',
735 'BIAS',
736 'BIDE',
737 'BIEN',
738 'BILE',
739 'BILK',
740 'BILL',
741 'BIND',
742 'BING',
743 'BIRD',
744 'BITE',
745 'BITS',
746 'BLAB',
747 'BLAT',
748 'BLED',
749 'BLEW',
750 'BLOB',
751 'BLOC',
752 'BLOT',
753 'BLOW',
754 'BLUE',
755 'BLUM',
756 'BLUR',
757 'BOAR',
758 'BOAT',
759 'BOCA',
760 'BOCK',
761 'BODE',
762 'BODY',
763 'BOGY',
764 'BOHR',
765 'BOIL',
766 'BOLD',
767 'BOLO',
768 'BOLT',
769 'BOMB',
770 'BONA',
771 'BOND',
772 'BONE',
773 'BONG',
774 'BONN',
775 'BONY',
776 'BOOK',
777 'BOOM',
778 'BOON',
779 'BOOT',
780 'BORE',
781 'BORG',
782 'BORN',
783 'BOSE',
784 'BOSS',
785 'BOTH',
786 'BOUT',
787 'BOWL',
788 'BOYD',
789 'BRAD',
790 'BRAE',
791 'BRAG',
792 'BRAN',
793 'BRAY',
794 'BRED',
795 'BREW',
796 'BRIG',
797 'BRIM',
798 'BROW',
799 'BUCK',
800 'BUDD',
801 'BUFF',
802 'BULB',
803 'BULK',
804 'BULL',
805 'BUNK',
806 'BUNT',
807 'BUOY',
808 'BURG',
809 'BURL',
810 'BURN',
811 'BURR',
812 'BURT',
813 'BURY',
814 'BUSH',
815 'BUSS',
816 'BUST',
817 'BUSY',
818 'BYTE',
819 'CADY',
820 'CAFE',
821 'CAGE',
822 'CAIN',
823 'CAKE',
824 'CALF',
825 'CALL',
826 'CALM',
827 'CAME',
828 'CANE',
829 'CANT',
830 'CARD',
831 'CARE',
832 'CARL',
833 'CARR',
834 'CART',
835 'CASE',
836 'CASH',
837 'CASK',
838 'CAST',
839 'CAVE',
840 'CEIL',
841 'CELL',
842 'CENT',
843 'CERN',
844 'CHAD',
845 'CHAR',
846 'CHAT',
847 'CHAW',
848 'CHEF',
849 'CHEN',
850 'CHEW',
851 'CHIC',
852 'CHIN',
853 'CHOU',
854 'CHOW',
855 'CHUB',
856 'CHUG',
857 'CHUM',
858 'CITE',
859 'CITY',
860 'CLAD',
861 'CLAM',
862 'CLAN',
863 'CLAW',
864 'CLAY',
865 'CLOD',
866 'CLOG',
867 'CLOT',
868 'CLUB',
869 'CLUE',
870 'COAL',
871 'COAT',
872 'COCA',
873 'COCK',
874 'COCO',
875 'CODA',
876 'CODE',
877 'CODY',
878 'COED',
879 'COIL',
880 'COIN',
881 'COKE',
882 'COLA',
883 'COLD',
884 'COLT',
885 'COMA',
886 'COMB',
887 'COME',
888 'COOK',
889 'COOL',
890 'COON',
891 'COOT',
892 'CORD',
893 'CORE',
894 'CORK',
895 'CORN',
896 'COST',
897 'COVE',
898 'COWL',
899 'CRAB',
900 'CRAG',
901 'CRAM',
902 'CRAY',
903 'CREW',
904 'CRIB',
905 'CROW',
906 'CRUD',
907 'CUBA',
908 'CUBE',
909 'CUFF',
910 'CULL',
911 'CULT',
912 'CUNY',
913 'CURB',
914 'CURD',
915 'CURE',
916 'CURL',
917 'CURT',
918 'CUTS',
919 'DADE',
920 'DALE',
921 'DAME',
922 'DANA',
923 'DANE',
924 'DANG',
925 'DANK',
926 'DARE',
927 'DARK',
928 'DARN',
929 'DART',
930 'DASH',
931 'DATA',
932 'DATE',
933 'DAVE',
934 'DAVY',
935 'DAWN',
936 'DAYS',
937 'DEAD',
938 'DEAF',
939 'DEAL',
940 'DEAN',
941 'DEAR',
942 'DEBT',
943 'DECK',
944 'DEED',
945 'DEEM',
946 'DEER',
947 'DEFT',
948 'DEFY',
949 'DELL',
950 'DENT',
951 'DENY',
952 'DESK',
953 'DIAL',
954 'DICE',
955 'DIED',
956 'DIET',
957 'DIME',
958 'DINE',
959 'DING',
960 'DINT',
961 'DIRE',
962 'DIRT',
963 'DISC',
964 'DISH',
965 'DISK',
966 'DIVE',
967 'DOCK',
968 'DOES',
969 'DOLE',
970 'DOLL',
971 'DOLT',
972 'DOME',
973 'DONE',
974 'DOOM',
975 'DOOR',
976 'DORA',
977 'DOSE',
978 'DOTE',
979 'DOUG',
980 'DOUR',
981 'DOVE',
982 'DOWN',
983 'DRAB',
984 'DRAG',
985 'DRAM',
986 'DRAW',
987 'DREW',
988 'DRUB',
989 'DRUG',
990 'DRUM',
991 'DUAL',
992 'DUCK',
993 'DUCT',
994 'DUEL',
995 'DUET',
996 'DUKE',
997 'DULL',
998 'DUMB',
999 'DUNE',
1000 'DUNK',
1001 'DUSK',
1002 'DUST',
1003 'DUTY',
1004 'EACH',
1005 'EARL',
1006 'EARN',
1007 'EASE',
1008 'EAST',
1009 'EASY',
1010 'EBEN',
1011 'ECHO',
1012 'EDDY',
1013 'EDEN',
1014 'EDGE',
1015 'EDGY',
1016 'EDIT',
1017 'EDNA',
1018 'EGAN',
1019 'ELAN',
1020 'ELBA',
1021 'ELLA',
1022 'ELSE',
1023 'EMIL',
1024 'EMIT',
1025 'EMMA',
1026 'ENDS',
1027 'ERIC',
1028 'EROS',
1029 'EVEN',
1030 'EVER',
1031 'EVIL',
1032 'EYED',
1033 'FACE',
1034 'FACT',
1035 'FADE',
1036 'FAIL',
1037 'FAIN',
1038 'FAIR',
1039 'FAKE',
1040 'FALL',
1041 'FAME',
1042 'FANG',
1043 'FARM',
1044 'FAST',
1045 'FATE',
1046 'FAWN',
1047 'FEAR',
1048 'FEAT',
1049 'FEED',
1050 'FEEL',
1051 'FEET',
1052 'FELL',
1053 'FELT',
1054 'FEND',
1055 'FERN',
1056 'FEST',
1057 'FEUD',
1058 'FIEF',
1059 'FIGS',
1060 'FILE',
1061 'FILL',
1062 'FILM',
1063 'FIND',
1064 'FINE',
1065 'FINK',
1066 'FIRE',
1067 'FIRM',
1068 'FISH',
1069 'FISK',
1070 'FIST',
1071 'FITS',
1072 'FIVE',
1073 'FLAG',
1074 'FLAK',
1075 'FLAM',
1076 'FLAT',
1077 'FLAW',
1078 'FLEA',
1079 'FLED',
1080 'FLEW',
1081 'FLIT',
1082 'FLOC',
1083 'FLOG',
1084 'FLOW',
1085 'FLUB',
1086 'FLUE',
1087 'FOAL',
1088 'FOAM',
1089 'FOGY',
1090 'FOIL',
1091 'FOLD',
1092 'FOLK',
1093 'FOND',
1094 'FONT',
1095 'FOOD',
1096 'FOOL',
1097 'FOOT',
1098 'FORD',
1099 'FORE',
1100 'FORK',
1101 'FORM',
1102 'FORT',
1103 'FOSS',
1104 'FOUL',
1105 'FOUR',
1106 'FOWL',
1107 'FRAU',
1108 'FRAY',
1109 'FRED',
1110 'FREE',
1111 'FRET',
1112 'FREY',
1113 'FROG',
1114 'FROM',
1115 'FUEL',
1116 'FULL',
1117 'FUME',
1118 'FUND',
1119 'FUNK',
1120 'FURY',
1121 'FUSE',
1122 'FUSS',
1123 'GAFF',
1124 'GAGE',
1125 'GAIL',
1126 'GAIN',
1127 'GAIT',
1128 'GALA',
1129 'GALE',
1130 'GALL',
1131 'GALT',
1132 'GAME',
1133 'GANG',
1134 'GARB',
1135 'GARY',
1136 'GASH',
1137 'GATE',
1138 'GAUL',
1139 'GAUR',
1140 'GAVE',
1141 'GAWK',
1142 'GEAR',
1143 'GELD',
1144 'GENE',
1145 'GENT',
1146 'GERM',
1147 'GETS',
1148 'GIBE',
1149 'GIFT',
1150 'GILD',
1151 'GILL',
1152 'GILT',
1153 'GINA',
1154 'GIRD',
1155 'GIRL',
1156 'GIST',
1157 'GIVE',
1158 'GLAD',
1159 'GLEE',
1160 'GLEN',
1161 'GLIB',
1162 'GLOB',
1163 'GLOM',
1164 'GLOW',
1165 'GLUE',
1166 'GLUM',
1167 'GLUT',
1168 'GOAD',
1169 'GOAL',
1170 'GOAT',
1171 'GOER',
1172 'GOES',
1173 'GOLD',
1174 'GOLF',
1175 'GONE',
1176 'GONG',
1177 'GOOD',
1178 'GOOF',
1179 'GORE',
1180 'GORY',
1181 'GOSH',
1182 'GOUT',
1183 'GOWN',
1184 'GRAB',
1185 'GRAD',
1186 'GRAY',
1187 'GREG',
1188 'GREW',
1189 'GREY',
1190 'GRID',
1191 'GRIM',
1192 'GRIN',
1193 'GRIT',
1194 'GROW',
1195 'GRUB',
1196 'GULF',
1197 'GULL',
1198 'GUNK',
1199 'GURU',
1200 'GUSH',
1201 'GUST',
1202 'GWEN',
1203 'GWYN',
1204 'HAAG',
1205 'HAAS',
1206 'HACK',
1207 'HAIL',
1208 'HAIR',
1209 'HALE',
1210 'HALF',
1211 'HALL',
1212 'HALO',
1213 'HALT',
1214 'HAND',
1215 'HANG',
1216 'HANK',
1217 'HANS',
1218 'HARD',
1219 'HARK',
1220 'HARM',
1221 'HART',
1222 'HASH',
1223 'HAST',
1224 'HATE',
1225 'HATH',
1226 'HAUL',
1227 'HAVE',
1228 'HAWK',
1229 'HAYS',
1230 'HEAD',
1231 'HEAL',
1232 'HEAR',
1233 'HEAT',
1234 'HEBE',
1235 'HECK',
1236 'HEED',
1237 'HEEL',
1238 'HEFT',
1239 'HELD',
1240 'HELL',
1241 'HELM',
1242 'HERB',
1243 'HERD',
1244 'HERE',
1245 'HERO',
1246 'HERS',
1247 'HESS',
1248 'HEWN',
1249 'HICK',
1250 'HIDE',
1251 'HIGH',
1252 'HIKE',
1253 'HILL',
1254 'HILT',
1255 'HIND',
1256 'HINT',
1257 'HIRE',
1258 'HISS',
1259 'HIVE',
1260 'HOBO',
1261 'HOCK',
1262 'HOFF',
1263 'HOLD',
1264 'HOLE',
1265 'HOLM',
1266 'HOLT',
1267 'HOME',
1268 'HONE',
1269 'HONK',
1270 'HOOD',
1271 'HOOF',
1272 'HOOK',
1273 'HOOT',
1274 'HORN',
1275 'HOSE',
1276 'HOST',
1277 'HOUR',
1278 'HOVE',
1279 'HOWE',
1280 'HOWL',
1281 'HOYT',
1282 'HUCK',
1283 'HUED',
1284 'HUFF',
1285 'HUGE',
1286 'HUGH',
1287 'HUGO',
1288 'HULK',
1289 'HULL',
1290 'HUNK',
1291 'HUNT',
1292 'HURD',
1293 'HURL',
1294 'HURT',
1295 'HUSH',
1296 'HYDE',
1297 'HYMN',
1298 'IBIS',
1299 'ICON',
1300 'IDEA',
1301 'IDLE',
1302 'IFFY',
1303 'INCA',
1304 'INCH',
1305 'INTO',
1306 'IONS',
1307 'IOTA',
1308 'IOWA',
1309 'IRIS',
1310 'IRMA',
1311 'IRON',
1312 'ISLE',
1313 'ITCH',
1314 'ITEM',
1315 'IVAN',
1316 'JACK',
1317 'JADE',
1318 'JAIL',
1319 'JAKE',
1320 'JANE',
1321 'JAVA',
1322 'JEAN',
1323 'JEFF',
1324 'JERK',
1325 'JESS',
1326 'JEST',
1327 'JIBE',
1328 'JILL',
1329 'JILT',
1330 'JIVE',
1331 'JOAN',
1332 'JOBS',
1333 'JOCK',
1334 'JOEL',
1335 'JOEY',
1336 'JOHN',
1337 'JOIN',
1338 'JOKE',
1339 'JOLT',
1340 'JOVE',
1341 'JUDD',
1342 'JUDE',
1343 'JUDO',
1344 'JUDY',
1345 'JUJU',
1346 'JUKE',
1347 'JULY',
1348 'JUNE',
1349 'JUNK',
1350 'JUNO',
1351 'JURY',
1352 'JUST',
1353 'JUTE',
1354 'KAHN',
1355 'KALE',
1356 'KANE',
1357 'KANT',
1358 'KARL',
1359 'KATE',
1360 'KEEL',
1361 'KEEN',
1362 'KENO',
1363 'KENT',
1364 'KERN',
1365 'KERR',
1366 'KEYS',
1367 'KICK',
1368 'KILL',
1369 'KIND',
1370 'KING',
1371 'KIRK',
1372 'KISS',
1373 'KITE',
1374 'KLAN',
1375 'KNEE',
1376 'KNEW',
1377 'KNIT',
1378 'KNOB',
1379 'KNOT',
1380 'KNOW',
1381 'KOCH',
1382 'KONG',
1383 'KUDO',
1384 'KURD',
1385 'KURT',
1386 'KYLE',
1387 'LACE',
1388 'LACK',
1389 'LACY',
1390 'LADY',
1391 'LAID',
1392 'LAIN',
1393 'LAIR',
1394 'LAKE',
1395 'LAMB',
1396 'LAME',
1397 'LAND',
1398 'LANE',
1399 'LANG',
1400 'LARD',
1401 'LARK',
1402 'LASS',
1403 'LAST',
1404 'LATE',
1405 'LAUD',
1406 'LAVA',
1407 'LAWN',
1408 'LAWS',
1409 'LAYS',
1410 'LEAD',
1411 'LEAF',
1412 'LEAK',
1413 'LEAN',
1414 'LEAR',
1415 'LEEK',
1416 'LEER',
1417 'LEFT',
1418 'LEND',
1419 'LENS',
1420 'LENT',
1421 'LEON',
1422 'LESK',
1423 'LESS',
1424 'LEST',
1425 'LETS',
1426 'LIAR',
1427 'LICE',
1428 'LICK',
1429 'LIED',
1430 'LIEN',
1431 'LIES',
1432 'LIEU',
1433 'LIFE',
1434 'LIFT',
1435 'LIKE',
1436 'LILA',
1437 'LILT',
1438 'LILY',
1439 'LIMA',
1440 'LIMB',
1441 'LIME',
1442 'LIND',
1443 'LINE',
1444 'LINK',
1445 'LINT',
1446 'LION',
1447 'LISA',
1448 'LIST',
1449 'LIVE',
1450 'LOAD',
1451 'LOAF',
1452 'LOAM',
1453 'LOAN',
1454 'LOCK',
1455 'LOFT',
1456 'LOGE',
1457 'LOIS',
1458 'LOLA',
1459 'LONE',
1460 'LONG',
1461 'LOOK',
1462 'LOON',
1463 'LOOT',
1464 'LORD',
1465 'LORE',
1466 'LOSE',
1467 'LOSS',
1468 'LOST',
1469 'LOUD',
1470 'LOVE',
1471 'LOWE',
1472 'LUCK',
1473 'LUCY',
1474 'LUGE',
1475 'LUKE',
1476 'LULU',
1477 'LUND',
1478 'LUNG',
1479 'LURA',
1480 'LURE',
1481 'LURK',
1482 'LUSH',
1483 'LUST',
1484 'LYLE',
1485 'LYNN',
1486 'LYON',
1487 'LYRA',
1488 'MACE',
1489 'MADE',
1490 'MAGI',
1491 'MAID',
1492 'MAIL',
1493 'MAIN',
1494 'MAKE',
1495 'MALE',
1496 'MALI',
1497 'MALL',
1498 'MALT',
1499 'MANA',
1500 'MANN',
1501 'MANY',
1502 'MARC',
1503 'MARE',
1504 'MARK',
1505 'MARS',
1506 'MART',
1507 'MARY',
1508 'MASH',
1509 'MASK',
1510 'MASS',
1511 'MAST',
1512 'MATE',
1513 'MATH',
1514 'MAUL',
1515 'MAYO',
1516 'MEAD',
1517 'MEAL',
1518 'MEAN',
1519 'MEAT',
1520 'MEEK',
1521 'MEET',
1522 'MELD',
1523 'MELT',
1524 'MEMO',
1525 'MEND',
1526 'MENU',
1527 'MERT',
1528 'MESH',
1529 'MESS',
1530 'MICE',
1531 'MIKE',
1532 'MILD',
1533 'MILE',
1534 'MILK',
1535 'MILL',
1536 'MILT',
1537 'MIMI',
1538 'MIND',
1539 'MINE',
1540 'MINI',
1541 'MINK',
1542 'MINT',
1543 'MIRE',
1544 'MISS',
1545 'MIST',
1546 'MITE',
1547 'MITT',
1548 'MOAN',
1549 'MOAT',
1550 'MOCK',
1551 'MODE',
1552 'MOLD',
1553 'MOLE',
1554 'MOLL',
1555 'MOLT',
1556 'MONA',
1557 'MONK',
1558 'MONT',
1559 'MOOD',
1560 'MOON',
1561 'MOOR',
1562 'MOOT',
1563 'MORE',
1564 'MORN',
1565 'MORT',
1566 'MOSS',
1567 'MOST',
1568 'MOTH',
1569 'MOVE',
1570 'MUCH',
1571 'MUCK',
1572 'MUDD',
1573 'MUFF',
1574 'MULE',
1575 'MULL',
1576 'MURK',
1577 'MUSH',
1578 'MUST',
1579 'MUTE',
1580 'MUTT',
1581 'MYRA',
1582 'MYTH',
1583 'NAGY',
1584 'NAIL',
1585 'NAIR',
1586 'NAME',
1587 'NARY',
1588 'NASH',
1589 'NAVE',
1590 'NAVY',
1591 'NEAL',
1592 'NEAR',
1593 'NEAT',
1594 'NECK',
1595 'NEED',
1596 'NEIL',
1597 'NELL',
1598 'NEON',
1599 'NERO',
1600 'NESS',
1601 'NEST',
1602 'NEWS',
1603 'NEWT',
1604 'NIBS',
1605 'NICE',
1606 'NICK',
1607 'NILE',
1608 'NINA',
1609 'NINE',
1610 'NOAH',
1611 'NODE',
1612 'NOEL',
1613 'NOLL',
1614 'NONE',
1615 'NOOK',
1616 'NOON',
1617 'NORM',
1618 'NOSE',
1619 'NOTE',
1620 'NOUN',
1621 'NOVA',
1622 'NUDE',
1623 'NULL',
1624 'NUMB',
1625 'OATH',
1626 'OBEY',
1627 'OBOE',
1628 'ODIN',
1629 'OHIO',
1630 'OILY',
1631 'OINT',
1632 'OKAY',
1633 'OLAF',
1634 'OLDY',
1635 'OLGA',
1636 'OLIN',
1637 'OMAN',
1638 'OMEN',
1639 'OMIT',
1640 'ONCE',
1641 'ONES',
1642 'ONLY',
1643 'ONTO',
1644 'ONUS',
1645 'ORAL',
1646 'ORGY',
1647 'OSLO',
1648 'OTIS',
1649 'OTTO',
1650 'OUCH',
1651 'OUST',
1652 'OUTS',
1653 'OVAL',
1654 'OVEN',
1655 'OVER',
1656 'OWLY',
1657 'OWNS',
1658 'QUAD',
1659 'QUIT',
1660 'QUOD',
1661 'RACE',
1662 'RACK',
1663 'RACY',
1664 'RAFT',
1665 'RAGE',
1666 'RAID',
1667 'RAIL',
1668 'RAIN',
1669 'RAKE',
1670 'RANK',
1671 'RANT',
1672 'RARE',
1673 'RASH',
1674 'RATE',
1675 'RAVE',
1676 'RAYS',
1677 'READ',
1678 'REAL',
1679 'REAM',
1680 'REAR',
1681 'RECK',
1682 'REED',
1683 'REEF',
1684 'REEK',
1685 'REEL',
1686 'REID',
1687 'REIN',
1688 'RENA',
1689 'REND',
1690 'RENT',
1691 'REST',
1692 'RICE',
1693 'RICH',
1694 'RICK',
1695 'RIDE',
1696 'RIFT',
1697 'RILL',
1698 'RIME',
1699 'RING',
1700 'RINK',
1701 'RISE',
1702 'RISK',
1703 'RITE',
1704 'ROAD',
1705 'ROAM',
1706 'ROAR',
1707 'ROBE',
1708 'ROCK',
1709 'RODE',
1710 'ROIL',
1711 'ROLL',
1712 'ROME',
1713 'ROOD',
1714 'ROOF',
1715 'ROOK',
1716 'ROOM',
1717 'ROOT',
1718 'ROSA',
1719 'ROSE',
1720 'ROSS',
1721 'ROSY',
1722 'ROTH',
1723 'ROUT',
1724 'ROVE',
1725 'ROWE',
1726 'ROWS',
1727 'RUBE',
1728 'RUBY',
1729 'RUDE',
1730 'RUDY',
1731 'RUIN',
1732 'RULE',
1733 'RUNG',
1734 'RUNS',
1735 'RUNT',
1736 'RUSE',
1737 'RUSH',
1738 'RUSK',
1739 'RUSS',
1740 'RUST',
1741 'RUTH',
1742 'SACK',
1743 'SAFE',
1744 'SAGE',
1745 'SAID',
1746 'SAIL',
1747 'SALE',
1748 'SALK',
1749 'SALT',
1750 'SAME',
1751 'SAND',
1752 'SANE',
1753 'SANG',
1754 'SANK',
1755 'SARA',
1756 'SAUL',
1757 'SAVE',
1758 'SAYS',
1759 'SCAN',
1760 'SCAR',
1761 'SCAT',
1762 'SCOT',
1763 'SEAL',
1764 'SEAM',
1765 'SEAR',
1766 'SEAT',
1767 'SEED',
1768 'SEEK',
1769 'SEEM',
1770 'SEEN',
1771 'SEES',
1772 'SELF',
1773 'SELL',
1774 'SEND',
1775 'SENT',
1776 'SETS',
1777 'SEWN',
1778 'SHAG',
1779 'SHAM',
1780 'SHAW',
1781 'SHAY',
1782 'SHED',
1783 'SHIM',
1784 'SHIN',
1785 'SHOD',
1786 'SHOE',
1787 'SHOT',
1788 'SHOW',
1789 'SHUN',
1790 'SHUT',
1791 'SICK',
1792 'SIDE',
1793 'SIFT',
1794 'SIGH',
1795 'SIGN',
1796 'SILK',
1797 'SILL',
1798 'SILO',
1799 'SILT',
1800 'SINE',
1801 'SING',
1802 'SINK',
1803 'SIRE',
1804 'SITE',
1805 'SITS',
1806 'SITU',
1807 'SKAT',
1808 'SKEW',
1809 'SKID',
1810 'SKIM',
1811 'SKIN',
1812 'SKIT',
1813 'SLAB',
1814 'SLAM',
1815 'SLAT',
1816 'SLAY',
1817 'SLED',
1818 'SLEW',
1819 'SLID',
1820 'SLIM',
1821 'SLIT',
1822 'SLOB',
1823 'SLOG',
1824 'SLOT',
1825 'SLOW',
1826 'SLUG',
1827 'SLUM',
1828 'SLUR',
1829 'SMOG',
1830 'SMUG',
1831 'SNAG',
1832 'SNOB',
1833 'SNOW',
1834 'SNUB',
1835 'SNUG',
1836 'SOAK',
1837 'SOAR',
1838 'SOCK',
1839 'SODA',
1840 'SOFA',
1841 'SOFT',
1842 'SOIL',
1843 'SOLD',
1844 'SOME',
1845 'SONG',
1846 'SOON',
1847 'SOOT',
1848 'SORE',
1849 'SORT',
1850 'SOUL',
1851 'SOUR',
1852 'SOWN',
1853 'STAB',
1854 'STAG',
1855 'STAN',
1856 'STAR',
1857 'STAY',
1858 'STEM',
1859 'STEW',
1860 'STIR',
1861 'STOW',
1862 'STUB',
1863 'STUN',
1864 'SUCH',
1865 'SUDS',
1866 'SUIT',
1867 'SULK',
1868 'SUMS',
1869 'SUNG',
1870 'SUNK',
1871 'SURE',
1872 'SURF',
1873 'SWAB',
1874 'SWAG',
1875 'SWAM',
1876 'SWAN',
1877 'SWAT',
1878 'SWAY',
1879 'SWIM',
1880 'SWUM',
1881 'TACK',
1882 'TACT',
1883 'TAIL',
1884 'TAKE',
1885 'TALE',
1886 'TALK',
1887 'TALL',
1888 'TANK',
1889 'TASK',
1890 'TATE',
1891 'TAUT',
1892 'TEAL',
1893 'TEAM',
1894 'TEAR',
1895 'TECH',
1896 'TEEM',
1897 'TEEN',
1898 'TEET',
1899 'TELL',
1900 'TEND',
1901 'TENT',
1902 'TERM',
1903 'TERN',
1904 'TESS',
1905 'TEST',
1906 'THAN',
1907 'THAT',
1908 'THEE',
1909 'THEM',
1910 'THEN',
1911 'THEY',
1912 'THIN',
1913 'THIS',
1914 'THUD',
1915 'THUG',
1916 'TICK',
1917 'TIDE',
1918 'TIDY',
1919 'TIED',
1920 'TIER',
1921 'TILE',
1922 'TILL',
1923 'TILT',
1924 'TIME',
1925 'TINA',
1926 'TINE',
1927 'TINT',
1928 'TINY',
1929 'TIRE',
1930 'TOAD',
1931 'TOGO',
1932 'TOIL',
1933 'TOLD',
1934 'TOLL',
1935 'TONE',
1936 'TONG',
1937 'TONY',
1938 'TOOK',
1939 'TOOL',
1940 'TOOT',
1941 'TORE',
1942 'TORN',
1943 'TOTE',
1944 'TOUR',
1945 'TOUT',
1946 'TOWN',
1947 'TRAG',
1948 'TRAM',
1949 'TRAY',
1950 'TREE',
1951 'TREK',
1952 'TRIG',
1953 'TRIM',
1954 'TRIO',
1955 'TROD',
1956 'TROT',
1957 'TROY',
1958 'TRUE',
1959 'TUBA',
1960 'TUBE',
1961 'TUCK',
1962 'TUFT',
1963 'TUNA',
1964 'TUNE',
1965 'TUNG',
1966 'TURF',
1967 'TURN',
1968 'TUSK',
1969 'TWIG',
1970 'TWIN',
1971 'TWIT',
1972 'ULAN',
1973 'UNIT',
1974 'URGE',
1975 'USED',
1976 'USER',
1977 'USES',
1978 'UTAH',
1979 'VAIL',
1980 'VAIN',
1981 'VALE',
1982 'VARY',
1983 'VASE',
1984 'VAST',
1985 'VEAL',
1986 'VEDA',
1987 'VEIL',
1988 'VEIN',
1989 'VEND',
1990 'VENT',
1991 'VERB',
1992 'VERY',
1993 'VETO',
1994 'VICE',
1995 'VIEW',
1996 'VINE',
1997 'VISE',
1998 'VOID',
1999 'VOLT',
2000 'VOTE',
2001 'WACK',
2002 'WADE',
2003 'WAGE',
2004 'WAIL',
2005 'WAIT',
2006 'WAKE',
2007 'WALE',
2008 'WALK',
2009 'WALL',
2010 'WALT',
2011 'WAND',
2012 'WANE',
2013 'WANG',
2014 'WANT',
2015 'WARD',
2016 'WARM',
2017 'WARN',
2018 'WART',
2019 'WASH',
2020 'WAST',
2021 'WATS',
2022 'WATT',
2023 'WAVE',
2024 'WAVY',
2025 'WAYS',
2026 'WEAK',
2027 'WEAL',
2028 'WEAN',
2029 'WEAR',
2030 'WEED',
2031 'WEEK',
2032 'WEIR',
2033 'WELD',
2034 'WELL',
2035 'WELT',
2036 'WENT',
2037 'WERE',
2038 'WERT',
2039 'WEST',
2040 'WHAM',
2041 'WHAT',
2042 'WHEE',
2043 'WHEN',
2044 'WHET',
2045 'WHOA',
2046 'WHOM',
2047 'WICK',
2048 'WIFE',
2049 'WILD',
2050 'WILL',
2051 'WIND',
2052 'WINE',
2053 'WING',
2054 'WINK',
2055 'WINO',
2056 'WIRE',
2057 'WISE',
2058 'WISH',
2059 'WITH',
2060 'WOLF',
2061 'WONT',
2062 'WOOD',
2063 'WOOL',
2064 'WORD',
2065 'WORE',
2066 'WORK',
2067 'WORM',
2068 'WORN',
2069 'WOVE',
2070 'WRIT',
2071 'WYNN',
2072 'YALE',
2073 'YANG',
2074 'YANK',
2075 'YARD',
2076 'YARN',
2077 'YAWL',
2078 'YAWN',
2079 'YEAH',
2080 'YEAR',
2081 'YELL',
2082 'YOGA',
2083 'YOKE',
2084]
293 2085
294_ALGO_DICT = {OTP_ALGO_MD5: 'md5', OTP_ALGO_SHA1: 'sha1'} 2086_ALGO_DICT = {OTP_ALGO_MD5: 'md5', OTP_ALGO_SHA1: 'sha1'}
295 2087
296 2088
297class OTPGeneratorException(Exception): 2089class OTPGeneratorError(Exception):
298 """OTPGeneratorException class""" 2090 """OTPGeneratorError class"""
299 2091
300 2092
301class OTPChallengeException(Exception): 2093class OTPChallengeError(Exception):
302 """OTPChallengeException class""" 2094 """OTPChallengeError class"""
303 2095
304 2096
305class OTPGenerator: 2097class OTPGenerator:
306 """OTPGenerator class""" 2098 """OTPGenerator class"""
307 2099
308 def __init__( 2100 def __init__(
309 self, 2101 self, password: bytes, seed: str = '', hash_algo=OTP_ALGO_MD5
310 password: bytes,
311 seed: str = '',
312 hash_algo=OTP_ALGO_MD5,
313 ): 2102 ):
314 """ 2103 """
315 Constructs an OTPGenerator object with a given password and seed. 2104 Constructs an OTPGenerator object with a given password and seed.
@@ -323,7 +2112,7 @@ class OTPGenerator:
323 :param hash_algo: The hash algo, defaults to OTP_ALGO_MD5 2112 :param hash_algo: The hash algo, defaults to OTP_ALGO_MD5
324 :type hash_algo: int or str 2113 :type hash_algo: int or str
325 2114
326 :raises otp2289.OTPGeneratorException: If the input does not validate 2115 :raises otp2289.OTPGeneratorError: If the input does not validate
327 """ 2116 """
328 # enforce the rfc2289 constraints 2117 # enforce the rfc2289 constraints
329 self._seed = seed 2118 self._seed = seed
@@ -331,11 +2120,9 @@ class OTPGenerator:
331 self._seed = self.validate_seed(self._seed) 2120 self._seed = self.validate_seed(self._seed)
332 self._hash_algo = self.validate_hash_algo(hash_algo) 2121 self._hash_algo = self.validate_hash_algo(hash_algo)
333 if not isinstance(password, bytes): 2122 if not isinstance(password, bytes):
334 raise OTPGeneratorException('Password must be a byte-string') 2123 raise OTPGeneratorError('Password must be a byte-string')
335 if len(password) < 10: 2124 if len(password) < 10:
336 raise OTPGeneratorException( 2125 raise OTPGeneratorError('Password must be longer than 10 bytes')
337 'Password must be longer than 10 bytes'
338 )
339 self._password = password 2126 self._password = password
340 2127
341 def __repr__(self): 2128 def __repr__(self):
@@ -357,11 +2144,11 @@ class OTPGenerator:
357 :rtype: int 2144 :rtype: int
358 """ 2145 """
359 if not isinstance(bit_stream, str): 2146 if not isinstance(bit_stream, str):
360 raise OTPGeneratorException('bit_stream must be of type str') 2147 raise OTPGeneratorError('bit_stream must be of type str')
361 if len(bit_stream) != 64: 2148 if len(bit_stream) != 64:
362 raise OTPGeneratorException('bit_stream must be of size 64') 2149 raise OTPGeneratorError('bit_stream must be of size 64')
363 value = 0 2150 value = 0
364 for pair in zip(bit_stream[::2], bit_stream[1::2]): 2151 for pair in zip(bit_stream[::2], bit_stream[1::2], strict=True):
365 value += int(''.join(pair), 2) 2152 value += int(''.join(pair), 2)
366 return value 2153 return value
367 2154
@@ -386,10 +2173,7 @@ class OTPGenerator:
386 tokens.append(RFC1760_TOKENS[int(bit_stream[44:55], 2)]) 2173 tokens.append(RFC1760_TOKENS[int(bit_stream[44:55], 2)])
387 tokens.append( 2174 tokens.append(
388 RFC1760_TOKENS[ 2175 RFC1760_TOKENS[
389 int( 2176 int(bit_stream[55:64] + f'{bit_pair_sum:0>8b}'[-2:], 2)
390 bit_stream[55:64] + f'{bit_pair_sum:0>8b}'[-2:],
391 2,
392 )
393 ] 2177 ]
394 ) 2178 )
395 return ' '.join(tokens) 2179 return ' '.join(tokens)
@@ -404,21 +2188,21 @@ class OTPGenerator:
404 :param challenge: The challenge string described in RFC-2289 2188 :param challenge: The challenge string described in RFC-2289
405 :type challenge: str 2189 :type challenge: str
406 2190
407 :raises otp2289.OTPChallengeException: If the challenge is invalid 2191 :raises otp2289.OTPChallengeError: If the challenge is invalid
408 2192
409 :return: (seed, hash_algo, step) tuple. 2193 :return: (seed, hash_algo, step) tuple.
410 :rtype: tuple 2194 :rtype: tuple
411 """ 2195 """
412 if not isinstance(challenge, str): 2196 if not isinstance(challenge, str):
413 raise OTPChallengeException('Challenge must be str') 2197 raise OTPChallengeError('Challenge must be str')
414 challenge = challenge.strip() 2198 challenge = challenge.strip()
415 if not challenge.startswith('otp-'): 2199 if not challenge.startswith('otp-'):
416 raise OTPChallengeException('Invalid challenge') 2200 raise OTPChallengeError('Invalid challenge')
417 try: 2201 try:
418 hash_algo, step, seed = challenge[4:].split() 2202 hash_algo, step, seed = challenge[4:].split()
419 return (seed, hash_algo, int(step)) 2203 return (seed, hash_algo, int(step))
420 except ValueError: 2204 except ValueError:
421 raise OTPChallengeException('Invalid challenge') from None 2205 raise OTPChallengeError('Invalid challenge') from None
422 2206
423 @staticmethod 2207 @staticmethod
424 def sha1_digest_folding(sha1_digest: bytes) -> bytes: 2208 def sha1_digest_folding(sha1_digest: bytes) -> bytes:
@@ -432,10 +2216,10 @@ class OTPGenerator:
432 :return: The byte-string representing the folded sha1-digest 2216 :return: The byte-string representing the folded sha1-digest
433 :rtype: bytes 2217 :rtype: bytes
434 """ 2218 """
435 if not (isinstance(sha1_digest, bytes)): 2219 if not isinstance(sha1_digest, bytes):
436 raise OTPGeneratorException('sha1_digest must be of type bytes') 2220 raise OTPGeneratorError('sha1_digest must be of type bytes')
437 if len(sha1_digest) != 20: 2221 if len(sha1_digest) != 20:
438 raise OTPGeneratorException( 2222 raise OTPGeneratorError(
439 'sha1_digest must be 160 bits (20 bytes) long' 2223 'sha1_digest must be 160 bits (20 bytes) long'
440 ) 2224 )
441 digested = list(5 * b'i') # 5 bytes (40 bits) 2225 digested = list(5 * b'i') # 5 bytes (40 bits)
@@ -485,12 +2269,12 @@ class OTPGenerator:
485 :rtype: bytes 2269 :rtype: bytes
486 """ 2270 """
487 if not (isinstance(byte_str1, bytes) and isinstance(byte_str2, bytes)): 2271 if not (isinstance(byte_str1, bytes) and isinstance(byte_str2, bytes)):
488 raise OTPGeneratorException( 2272 raise OTPGeneratorError(
489 'byte_str1 and byte_str2 must be of type bytes' 2273 'byte_str1 and byte_str2 must be of type bytes'
490 ) 2274 )
491 length = len(byte_str1) 2275 length = len(byte_str1)
492 if length != len(byte_str2) or length < 1: 2276 if length != len(byte_str2) or length < 1:
493 raise OTPGeneratorException( 2277 raise OTPGeneratorError(
494 'byte_str1 and byte_str2 must be of the same length > 0' 2278 'byte_str1 and byte_str2 must be of the same length > 0'
495 ) 2279 )
496 return bytes([byte_str1[i] ^ byte_str2[i] for i in range(length)]) 2280 return bytes([byte_str1[i] ^ byte_str2[i] for i in range(length)])
@@ -503,25 +2287,23 @@ class OTPGenerator:
503 :param tokens_str: String representing 6 words tokens 2287 :param tokens_str: String representing 6 words tokens
504 :type tokens_str: str 2288 :type tokens_str: str
505 2289
506 :raises otp2289.OTPGeneratorException: When the tokens_str is invalid 2290 :raises otp2289.OTPGeneratorError: When the tokens_str is invalid
507 2291
508 :return: 6 words tokens 2292 :return: 6 words tokens
509 :rtype: bytes 2293 :rtype: bytes
510 """ 2294 """
511 if not isinstance(tokens_str, str): 2295 if not isinstance(tokens_str, str):
512 raise OTPGeneratorException('tokens must be a str') 2296 raise OTPGeneratorError('tokens must be a str')
513 tokens = tokens_str.split() 2297 tokens = tokens_str.split()
514 if len(tokens) != 6: 2298 if len(tokens) != 6:
515 raise OTPGeneratorException( 2299 raise OTPGeneratorError('Tokens-string does not contain 6 tokens')
516 'Tokens-string does not contain 6 tokens'
517 )
518 token_ints = [] 2300 token_ints = []
519 try: 2301 try:
520 token_ints = [ 2302 token_ints = [
521 RFC1760_TOKENS.index(token.upper()) for token in tokens 2303 RFC1760_TOKENS.index(token.upper()) for token in tokens
522 ] 2304 ]
523 except ValueError: 2305 except ValueError:
524 raise OTPGeneratorException( 2306 raise OTPGeneratorError(
525 'One or more words not present in RFC1760' 2307 'One or more words not present in RFC1760'
526 ) from None 2308 ) from None
527 # now we build a string of bits 2309 # now we build a string of bits
@@ -539,7 +2321,7 @@ class OTPGenerator:
539 f'{OTPGenerator.bit_pair_sum(bit_stream[:64]):0>8b}'[-2:] 2321 f'{OTPGenerator.bit_pair_sum(bit_stream[:64]):0>8b}'[-2:]
540 != bit_stream[-2:] 2322 != bit_stream[-2:]
541 ): 2323 ):
542 raise OTPGeneratorException('Invalid bit checksum') 2324 raise OTPGeneratorError('Invalid bit checksum')
543 return int(bit_stream[:64], 2).to_bytes(8, 'big') 2325 return int(bit_stream[:64], 2).to_bytes(8, 'big')
544 2326
545 @staticmethod 2327 @staticmethod
@@ -550,21 +2332,21 @@ class OTPGenerator:
550 :param hash_algo: The hash algo, defaults to OTP_ALGO_MD5 2332 :param hash_algo: The hash algo, defaults to OTP_ALGO_MD5
551 :type hash_algo: int or str 2333 :type hash_algo: int or str
552 2334
553 :raises otp2289.OTPGeneratorException: If hash_algo does not validate 2335 :raises otp2289.OTPGeneratorError: If hash_algo does not validate
554 2336
555 :return: The validated hash_algo in str-form 2337 :return: The validated hash_algo in str-form
556 :rtype: str 2338 :rtype: str
557 """ 2339 """
558 if isinstance(hash_algo, int): 2340 if isinstance(hash_algo, int):
559 if hash_algo not in _ALGO_DICT: 2341 if hash_algo not in _ALGO_DICT:
560 raise OTPGeneratorException( 2342 raise OTPGeneratorError(
561 'hash_algo is not among the known algorithms' 2343 'hash_algo is not among the known algorithms'
562 ) 2344 )
563 hash_algo = _ALGO_DICT.get(hash_algo) 2345 hash_algo = _ALGO_DICT.get(hash_algo)
564 if not isinstance(hash_algo, str): 2346 if not isinstance(hash_algo, str):
565 raise OTPGeneratorException('hash_algo must be an int or a str') 2347 raise OTPGeneratorError('hash_algo must be an int or a str')
566 if hash_algo not in hashlib.algorithms_available: 2348 if hash_algo not in hashlib.algorithms_available:
567 raise OTPGeneratorException( 2349 raise OTPGeneratorError(
568 f'{hash_algo} is not supported by this version of the ' 2350 f'{hash_algo} is not supported by this version of the '
569 'hashlib module' 2351 'hashlib module'
570 ) 2352 )
@@ -578,20 +2360,20 @@ class OTPGenerator:
578 :param seed: The seed received from the challenge, defaults to '' 2360 :param seed: The seed received from the challenge, defaults to ''
579 :type seed: str 2361 :type seed: str
580 2362
581 :raises otp2289.OTPGeneratorException: If seed does not validate 2363 :raises otp2289.OTPGeneratorError: If seed does not validate
582 2364
583 :return: The validated (and very same) seed 2365 :return: The validated (and very same) seed
584 :rtype: str 2366 :rtype: str
585 """ 2367 """
586 if not isinstance(seed, str): 2368 if not isinstance(seed, str):
587 raise OTPGeneratorException('Seed must be a string') 2369 raise OTPGeneratorError('Seed must be a string')
588 if not seed or len(seed) > 16: 2370 if not seed or len(seed) > 16:
589 raise OTPGeneratorException( 2371 raise OTPGeneratorError(
590 'The seed MUST be of 1 to 16 characters in length' 2372 'The seed MUST be of 1 to 16 characters in length'
591 ) 2373 )
592 for char in seed: 2374 for char in seed:
593 if char not in string.ascii_letters + string.digits: 2375 if char not in string.ascii_letters + string.digits:
594 raise OTPGeneratorException( 2376 raise OTPGeneratorError(
595 'The seed MUST consist of purely alphanumeric characters' 2377 'The seed MUST consist of purely alphanumeric characters'
596 ) 2378 )
597 return seed 2379 return seed
@@ -604,15 +2386,15 @@ class OTPGenerator:
604 :param seed: The step received from the challenge 2386 :param seed: The step received from the challenge
605 :type seed: int 2387 :type seed: int
606 2388
607 :raises otp2289.OTPGeneratorException: If step does not validate 2389 :raises otp2289.OTPGeneratorError: If step does not validate
608 2390
609 :return: The validated (and very same) step 2391 :return: The validated (and very same) step
610 :rtype: int 2392 :rtype: int
611 """ 2393 """
612 if not isinstance(step, int): 2394 if not isinstance(step, int):
613 raise OTPGeneratorException('Step value MUST be an int') 2395 raise OTPGeneratorError('Step value MUST be an int')
614 if step < 0: 2396 if step < 0:
615 raise OTPGeneratorException('Step value MUST be >= 0') 2397 raise OTPGeneratorError('Step value MUST be >= 0')
616 return step 2398 return step
617 2399
618 def generate_otp_hexdigest(self, step: int) -> str: 2400 def generate_otp_hexdigest(self, step: int) -> str:
@@ -696,11 +2478,9 @@ class OTPGenerator:
696 :rtype: generator 2478 :rtype: generator
697 """ 2479 """
698 if not isinstance(start, int) and isinstance(stop, int): 2480 if not isinstance(start, int) and isinstance(stop, int):
699 raise OTPGeneratorException('Step value MUST be an int') 2481 raise OTPGeneratorError('Step value MUST be an int')
700 if start < stop: 2482 if start < stop:
701 raise OTPGeneratorException( 2483 raise OTPGeneratorError('Start value can not be lower than stop')
702 'Start value can not be lower than stop'
703 )
704 for step in range(start, stop - 1, -1): 2484 for step in range(start, stop - 1, -1):
705 yield self.generate_otp_hexdigest(step) 2485 yield self.generate_otp_hexdigest(step)
706 2486
@@ -719,11 +2499,9 @@ class OTPGenerator:
719 :rtype: generator 2499 :rtype: generator
720 """ 2500 """
721 if not isinstance(start, int) and isinstance(stop, int): 2501 if not isinstance(start, int) and isinstance(stop, int):
722 raise OTPGeneratorException('Step value MUST be an int') 2502 raise OTPGeneratorError('Step value MUST be an int')
723 if start < stop: 2503 if start < stop:
724 raise OTPGeneratorException( 2504 raise OTPGeneratorError('Start value can not be lower than stop')
725 'Start value can not be lower than stop'
726 )
727 for step in range(start, stop - 1, -1): 2505 for step in range(start, stop - 1, -1):
728 yield self.generate_otp_words(step) 2506 yield self.generate_otp_words(step)
729 2507
@@ -754,7 +2532,7 @@ class OTPGenerator:
754 # sha1 160bit -> 64bit folding 2532 # sha1 160bit -> 64bit folding
755 digest = self.sha1_digest_folding(large_digest) 2533 digest = self.sha1_digest_folding(large_digest)
756 else: 2534 else:
757 raise OTPGeneratorException( 2535 raise OTPGeneratorError(
758 f'{self._hash_algo} is not supported by this module' 2536 f'{self._hash_algo} is not supported by this module'
759 ) 2537 )
760 return digest 2538 return digest
diff --git a/src/otp2289/server.py b/src/otp2289/server.py
index 4305f75..e5ee0f2 100644
--- a/src/otp2289/server.py
+++ b/src/otp2289/server.py
@@ -1,7 +1,6 @@
1# -*- coding: utf-8 -*-
2# SPDX-License-Identifier: BSD-2-Clause-FreeBSD 1# SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3# 2#
4# Copyright (c) 2020-2023 Simeon Simeonov 3# Copyright (c) 2020-2025 Simeon Simeonov
5# All rights reserved. 4# All rights reserved.
6# 5#
7# Redistribution and use in source and binary forms, with or without 6# Redistribution and use in source and binary forms, with or without
@@ -24,22 +23,23 @@
24# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26"""A pure Python implementation of the RFC-2289 OTP server""" 25"""A pure Python implementation of the RFC-2289 OTP server"""
26
27import binascii 27import binascii
28import hashlib 28import hashlib
29 29
30from .generator import OTP_ALGO_MD5, OTPGenerator, OTPGeneratorException 30from .generator import OTP_ALGO_MD5, OTPGenerator, OTPGeneratorError
31 31
32 32
33class OTPStateException(Exception): 33class OTPStateError(Exception):
34 """OTPStateException class""" 34 """OTPStateError class"""
35 35
36 36
37class OTPStoreException(Exception): 37class OTPStoreError(Exception):
38 """OTPStoreException class""" 38 """OTPStoreError class"""
39 39
40 40
41class OTPInvalidResponse(Exception): 41class OTPInvalidResponseError(Exception):
42 """OTPInvalidResponse class""" 42 """OTPInvalidResponseError class"""
43 43
44 44
45class OTPState: 45class OTPState:
@@ -52,11 +52,7 @@ class OTPState:
52 """ 52 """
53 53
54 def __init__( 54 def __init__(
55 self, 55 self, ot_hex: str, current_step: int, seed: str, hash_algo=OTP_ALGO_MD5
56 ot_hex: str,
57 current_step: int,
58 seed: str,
59 hash_algo=OTP_ALGO_MD5,
60 ): 56 ):
61 """ 57 """
62 Constructs an OTPState object with the given arguments. 58 Constructs an OTPState object with the given arguments.
@@ -75,15 +71,15 @@ class OTPState:
75 :param hash_algo: The hash algo, defaults to OTP_ALGO_MD5 71 :param hash_algo: The hash algo, defaults to OTP_ALGO_MD5
76 :type hash_algo: int or str 72 :type hash_algo: int or str
77 73
78 :raises otp2289.OTPStateException: If the input does not validate 74 :raises otp2289.OTPStateError: If the input does not validate
79 """ 75 """
80 # enforce the rfc2289 constraints 76 # enforce the rfc2289 constraints
81 try: 77 try:
82 self._seed = OTPGenerator.validate_seed(seed) 78 self._seed = OTPGenerator.validate_seed(seed)
83 self._hash_algo = OTPGenerator.validate_hash_algo(hash_algo) 79 self._hash_algo = OTPGenerator.validate_hash_algo(hash_algo)
84 self._step = OTPGenerator.validate_step(current_step) 80 self._step = OTPGenerator.validate_step(current_step)
85 except OTPGeneratorException as exp: 81 except OTPGeneratorError as exp:
86 raise OTPStateException(exp.args[0]) from None 82 raise OTPStateError(exp.args[0]) from None
87 self._current_digest = None 83 self._current_digest = None
88 if ot_hex is not None: 84 if ot_hex is not None:
89 self._current_digest = self.validate_hex(ot_hex) 85 self._current_digest = self.validate_hex(ot_hex)
@@ -157,28 +153,28 @@ class OTPState:
157 153
158 The method first checks if response is a token and tries to convert 154 The method first checks if response is a token and tries to convert
159 it to bytes. If that fails, the method assumes that response is a hex. 155 it to bytes. If that fails, the method assumes that response is a hex.
160 If neither of those attempts succeeds OTPInvalidResponse is raised. 156 If neither of those attempts succeeds OTPInvalidResponseError is raised
161 It is up to the caller to run another iteration and compare the result 157 It is up to the caller to run another iteration and compare the result
162 to an existing digest in this state. 158 to an existing digest in this state.
163 159
164 :param response: The response to this state (its challenge) 160 :param response: The response to this state (its challenge)
165 :type response: str 161 :type response: str
166 162
167 :raises otp2289.OTPInvalidResponse: If the response is corrupt/illegal, 163 :raises otp2289.OTPInvalidResponseError: If the response is
168 but not if it simply does not 164 corrupt/illegal, but not if it
169 validate 165 simply does not validate
170 166
171 :return: The bytes representation of response (if any) 167 :return: The bytes representation of response (if any)
172 :rtype: bytes 168 :rtype: bytes
173 """ 169 """
174 try: 170 try:
175 return OTPGenerator.tokens_to_bytes(response) 171 return OTPGenerator.tokens_to_bytes(response)
176 except OTPGeneratorException: 172 except OTPGeneratorError:
177 # now assume hex... 173 # now assume hex...
178 try: 174 try:
179 return OTPState.validate_hex(response) 175 return OTPState.validate_hex(response)
180 except OTPStateException: 176 except OTPStateError:
181 raise OTPInvalidResponse( 177 raise OTPInvalidResponseError(
182 'The response is neither a valid token or hex' 178 'The response is neither a valid token or hex'
183 ) from None 179 ) from None
184 180
@@ -190,25 +186,25 @@ class OTPState:
190 :param ot_hex: The one-time hex to validate 186 :param ot_hex: The one-time hex to validate
191 :type ot_hex: str 187 :type ot_hex: str
192 188
193 :raises otp2289.OTPStateException: If hex does not validate 189 :raises otp2289.OTPStateError: If hex does not validate
194 190
195 :return: The validated hex (without leading 0x) converted to bytes 191 :return: The validated hex (without leading 0x) converted to bytes
196 :rtype: bytes 192 :rtype: bytes
197 """ 193 """
198 if not isinstance(ot_hex, str): 194 if not isinstance(ot_hex, str):
199 raise OTPStateException('OT-hex must be a str') 195 raise OTPStateError('OT-hex must be a str')
200 if ot_hex.startswith('0x'): 196 if ot_hex.startswith('0x'):
201 ot_hex = ot_hex[2:] 197 ot_hex = ot_hex[2:]
202 ot_hex = ot_hex.strip().lower() 198 ot_hex = ot_hex.strip().lower()
203 if len(ot_hex) != 16: 199 if len(ot_hex) != 16:
204 raise OTPStateException( 200 raise OTPStateError(
205 'The length of the hex should be 16 ' 201 'The length of the hex should be 16 '
206 '(representing 64 bits digest)' 202 '(representing 64 bits digest)'
207 ) 203 )
208 try: 204 try:
209 return binascii.unhexlify(ot_hex) 205 return binascii.unhexlify(ot_hex)
210 except binascii.Error: 206 except binascii.Error:
211 raise OTPStateException('Invalid OT-hex') from None 207 raise OTPStateError('Invalid OT-hex') from None
212 208
213 def get_next_state(self): 209 def get_next_state(self):
214 """ 210 """
@@ -223,16 +219,11 @@ class OTPState:
223 if self._new_digest_hex is None: 219 if self._new_digest_hex is None:
224 return None 220 return None
225 return OTPState( 221 return OTPState(
226 self._new_digest_hex, 222 self._new_digest_hex, self._step - 1, self._seed, self._hash_algo
227 self._step - 1,
228 self._seed,
229 self._hash_algo,
230 ) 223 )
231 224
232 def response_validates( 225 def response_validates(
233 self, 226 self, response: str, store_valid_response: str = True
234 response: str,
235 store_valid_response: str = True,
236 ) -> bool: 227 ) -> bool:
237 """ 228 """
238 Validates the incoming response as specified by RFC-2289. 229 Validates the incoming response as specified by RFC-2289.
@@ -243,14 +234,14 @@ class OTPState:
243 :param store_valid_response: Should a valid response be stored 234 :param store_valid_response: Should a valid response be stored
244 :type store_valid_response: bool 235 :type store_valid_response: bool
245 236
246 :raises otp2289.OTPInvalidResponse: If the response does not match 237 :raises otp2289.OTPInvalidResponseError: If the response does not match
247 this state 238 this state
248 239
249 :return: Returns True if response validates, False otherwise 240 :return: Returns True if response validates, False otherwise
250 :rtype: bool 241 :rtype: bool
251 """ 242 """
252 # self.response_to_bytes raises OTPInvalidResponse in case response 243 # self.response_to_bytes raises OTPInvalidResponseError in case
253 # is corrupt or in a wrong format 244 # response is corrupt or in a wrong format
254 response_bytes = self.response_to_bytes(response) 245 response_bytes = self.response_to_bytes(response)
255 if self._hash_algo == 'md5': 246 if self._hash_algo == 'md5':
256 digest = hashlib.md5(response_bytes).digest() 247 digest = hashlib.md5(response_bytes).digest()
@@ -281,7 +272,7 @@ class OTPState:
281 return True 272 return True
282 return False 273 return False
283 # this should not happen since the hash_algo is validated by the caller 274 # this should not happen since the hash_algo is validated by the caller
284 raise OTPInvalidResponse(f'Ivalid hash_algo: {self._hash_algo}') 275 raise OTPInvalidResponseError(f'Ivalid hash_algo: {self._hash_algo}')
285 276
286 def to_dict(self) -> dict: 277 def to_dict(self) -> dict:
287 """ 278 """
@@ -367,12 +358,12 @@ class OTPStore:
367 :param state: The OTPState object 358 :param state: The OTPState object
368 :type state: otp2289.OTPState 359 :type state: otp2289.OTPState
369 360
370 :raises otp2289.OTPStoreException: On failure 361 :raises otp2289.OTPStoreError: On failure
371 """ 362 """
372 if not isinstance(key, str): 363 if not isinstance(key, str):
373 raise OTPStoreException('key must be a str') 364 raise OTPStoreError('key must be a str')
374 if not isinstance(state, OTPState): 365 if not isinstance(state, OTPState):
375 raise OTPStoreException('state must be an OTPState-object') 366 raise OTPStoreError('state must be an OTPState-object')
376 self._data[key] = state 367 self._data[key] = state
377 self._states[state] = key 368 self._states[state] = key
378 369
@@ -393,22 +384,19 @@ class OTPStore:
393 384
394 :raises KeyError: If key does not exist 385 :raises KeyError: If key does not exist
395 386
396 :raises otp2289.OTPStoreException: On failure 387 :raises otp2289.OTPStoreError: On failure
397 388
398 :return: The state corresponding to the key 389 :return: The state corresponding to the key
399 :rtype: otp2289.OTPState 390 :rtype: otp2289.OTPState
400 """ 391 """
401 if not isinstance(key, str): 392 if not isinstance(key, str):
402 raise OTPStoreException('key must be a str') 393 raise OTPStoreError('key must be a str')
403 state = self._data.pop(key) 394 state = self._data.pop(key)
404 self._states.pop(state) 395 self._states.pop(state)
405 return state 396 return state
406 397
407 def response_validates( 398 def response_validates(
408 self, 399 self, key: str, response: str, store_valid_response: bool = True
409 key: str,
410 response: str,
411 store_valid_response: bool = True,
412 ) -> bool: 400 ) -> bool:
413 """ 401 """
414 A method that wraps around OTPState.response_validates and 402 A method that wraps around OTPState.response_validates and
@@ -429,8 +417,8 @@ class OTPStore:
429 417
430 :raises KeyError: If the key is not present 418 :raises KeyError: If the key is not present
431 419
432 :raises otp2289.OTPInvalidResponse: If the response does not match 420 :raises otp2289.OTPInvalidResponseError: If the response does not match
433 this state 421 this state
434 422
435 :return: Returns True if response validates, False otherwise 423 :return: Returns True if response validates, False otherwise
436 :rtype: bool 424 :rtype: bool
diff --git a/test/test_generator.py b/test/test_generator.py
index b4bb961..4329ae4 100644
--- a/test/test_generator.py
+++ b/test/test_generator.py
@@ -1,7 +1,6 @@
1# -*- coding: utf-8 -*-
2# SPDX-License-Identifier: BSD-2-Clause-FreeBSD 1# SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3# 2#
4# Copyright (c) 2020-2023, Simeon Simeonov 3# Copyright (c) 2020-2025, Simeon Simeonov
5# All rights reserved. 4# All rights reserved.
6# 5#
7# Redistribution and use in source and binary forms, with or without 6# Redistribution and use in source and binary forms, with or without
@@ -36,25 +35,25 @@ def test_caller_exceptions():
36 'TeSt', 35 'TeSt',
37 otp2289.OTP_ALGO_MD5, 36 otp2289.OTP_ALGO_MD5,
38 ) 37 )
39 with pytest.raises(otp2289.OTPGeneratorException) as exc_info: 38 with pytest.raises(otp2289.OTPGeneratorError) as exc_info:
40 gen.generate_otp_words('3') 39 gen.generate_otp_words('3')
41 assert exc_info.type is otp2289.OTPGeneratorException 40 assert exc_info.type is otp2289.OTPGeneratorError
42 assert exc_info.value.args[0] == 'Step value MUST be an int' 41 assert exc_info.value.args[0] == 'Step value MUST be an int'
43 with pytest.raises(otp2289.OTPGeneratorException) as exc_info: 42 with pytest.raises(otp2289.OTPGeneratorError) as exc_info:
44 gen.generate_otp_hexdigest(-1) 43 gen.generate_otp_hexdigest(-1)
45 assert exc_info.type is otp2289.OTPGeneratorException 44 assert exc_info.type is otp2289.OTPGeneratorError
46 assert exc_info.value.args[0] == 'Step value MUST be >= 0' 45 assert exc_info.value.args[0] == 'Step value MUST be >= 0'
47 with pytest.raises(otp2289.OTPChallengeException) as exc_info: 46 with pytest.raises(otp2289.OTPChallengeError) as exc_info:
48 gen.generate_otp_hexdigest_from_challenge(b'md5 fbd TeSt') 47 gen.generate_otp_hexdigest_from_challenge(b'md5 fbd TeSt')
49 assert exc_info.type is otp2289.OTPChallengeException 48 assert exc_info.type is otp2289.OTPChallengeError
50 assert exc_info.value.args[0] == 'Challenge must be str' 49 assert exc_info.value.args[0] == 'Challenge must be str'
51 with pytest.raises(otp2289.OTPChallengeException) as exc_info: 50 with pytest.raises(otp2289.OTPChallengeError) as exc_info:
52 gen.generate_otp_hexdigest_from_challenge('md5 fbd TeSt') 51 gen.generate_otp_hexdigest_from_challenge('md5 fbd TeSt')
53 assert exc_info.type is otp2289.OTPChallengeException 52 assert exc_info.type is otp2289.OTPChallengeError
54 assert exc_info.value.args[0] == 'Invalid challenge' 53 assert exc_info.value.args[0] == 'Invalid challenge'
55 with pytest.raises(otp2289.generator.OTPChallengeException) as exc_info: 54 with pytest.raises(otp2289.generator.OTPChallengeError) as exc_info:
56 gen.generate_otp_hexdigest_from_challenge('otp-md5 fbd TeSt') 55 gen.generate_otp_hexdigest_from_challenge('otp-md5 fbd TeSt')
57 assert exc_info.type is otp2289.generator.OTPChallengeException 56 assert exc_info.type is otp2289.generator.OTPChallengeError
58 assert exc_info.value.args[0] == 'Invalid challenge' 57 assert exc_info.value.args[0] == 'Invalid challenge'
59 58
60 59
@@ -63,74 +62,74 @@ def test_constructor_exceptions():
63 Tests the exceptions when initializing a new object (in the constructor) 62 Tests the exceptions when initializing a new object (in the constructor)
64 """ 63 """
65 # test the otp2289.OTPGenerator __init__ and validators 64 # test the otp2289.OTPGenerator __init__ and validators
66 with pytest.raises(otp2289.OTPGeneratorException) as exc_info: 65 with pytest.raises(otp2289.OTPGeneratorError) as exc_info:
67 otp2289.OTPGenerator( 66 otp2289.OTPGenerator(
68 'This is a test.'.encode(), 67 'This is a test.'.encode(),
69 'TeStø'.encode(), 68 'TeStø'.encode(),
70 otp2289.OTP_ALGO_MD5, 69 otp2289.OTP_ALGO_MD5,
71 ) 70 )
72 assert exc_info.type is otp2289.OTPGeneratorException 71 assert exc_info.type is otp2289.OTPGeneratorError
73 assert exc_info.value.args[0] == 'Seed must be a string' 72 assert exc_info.value.args[0] == 'Seed must be a string'
74 with pytest.raises(otp2289.OTPGeneratorException) as exc_info: 73 with pytest.raises(otp2289.OTPGeneratorError) as exc_info:
75 otp2289.OTPGenerator( 74 otp2289.OTPGenerator(
76 'This is a test.'.encode(), 75 'This is a test.'.encode(),
77 'TeStøtEsTteSTteStTest', 76 'TeStøtEsTteSTteStTest',
78 otp2289.OTP_ALGO_SHA1, 77 otp2289.OTP_ALGO_SHA1,
79 ) 78 )
80 assert exc_info.type is otp2289.OTPGeneratorException 79 assert exc_info.type is otp2289.OTPGeneratorError
81 assert exc_info.value.args[0] == ( 80 assert exc_info.value.args[0] == (
82 'The seed MUST be of 1 to 16 characters in length' 81 'The seed MUST be of 1 to 16 characters in length'
83 ) 82 )
84 with pytest.raises(otp2289.OTPGeneratorException) as exc_info: 83 with pytest.raises(otp2289.OTPGeneratorError) as exc_info:
85 otp2289.OTPGenerator( 84 otp2289.OTPGenerator(
86 'This is a test.'.encode(), 85 'This is a test.'.encode(),
87 'TeStø', 86 'TeStø',
88 otp2289.OTP_ALGO_SHA1, 87 otp2289.OTP_ALGO_SHA1,
89 ) 88 )
90 assert exc_info.type is otp2289.OTPGeneratorException 89 assert exc_info.type is otp2289.OTPGeneratorError
91 assert exc_info.value.args[0] == ( 90 assert exc_info.value.args[0] == (
92 'The seed MUST consist of purely alphanumeric characters' 91 'The seed MUST consist of purely alphanumeric characters'
93 ) 92 )
94 with pytest.raises(otp2289.OTPGeneratorException) as exc_info: 93 with pytest.raises(otp2289.OTPGeneratorError) as exc_info:
95 otp2289.OTPGenerator( 94 otp2289.OTPGenerator(
96 'This is a test.'.encode(), 95 'This is a test.'.encode(),
97 'TeSt', 96 'TeSt',
98 9, 97 9,
99 ) 98 )
100 assert exc_info.type is otp2289.OTPGeneratorException 99 assert exc_info.type is otp2289.OTPGeneratorError
101 assert exc_info.value.args[0] == ( 100 assert exc_info.value.args[0] == (
102 'hash_algo is not among the known algorithms' 101 'hash_algo is not among the known algorithms'
103 ) 102 )
104 with pytest.raises(otp2289.OTPGeneratorException) as exc_info: 103 with pytest.raises(otp2289.OTPGeneratorError) as exc_info:
105 otp2289.OTPGenerator( 104 otp2289.OTPGenerator(
106 'This is a test.'.encode(), 105 'This is a test.'.encode(),
107 'TeSt', 106 'TeSt',
108 b'md5', 107 b'md5',
109 ) 108 )
110 assert exc_info.type is otp2289.OTPGeneratorException 109 assert exc_info.type is otp2289.OTPGeneratorError
111 assert exc_info.value.args[0] == 'hash_algo must be an int or a str' 110 assert exc_info.value.args[0] == 'hash_algo must be an int or a str'
112 # test the package structure as well 111 # test the package structure as well
113 with pytest.raises(otp2289.generator.OTPGeneratorException) as exc_info: 112 with pytest.raises(otp2289.generator.OTPGeneratorError) as exc_info:
114 otp2289.generator.OTPGenerator( 113 otp2289.generator.OTPGenerator(
115 'This is a test.'.encode(), 114 'This is a test.'.encode(),
116 'TeSt', 115 'TeSt',
117 'foo', 116 'foo',
118 ) 117 )
119 assert exc_info.type is otp2289.generator.OTPGeneratorException 118 assert exc_info.type is otp2289.generator.OTPGeneratorError
120 assert exc_info.value.args[0] == ( 119 assert exc_info.value.args[0] == (
121 'foo is not supported by this version of the hashlib module' 120 'foo is not supported by this version of the hashlib module'
122 ) 121 )
123 with pytest.raises(otp2289.OTPGeneratorException) as exc_info: 122 with pytest.raises(otp2289.OTPGeneratorError) as exc_info:
124 otp2289.OTPGenerator('1234567', 'TeSt', otp2289.OTP_ALGO_MD5) 123 otp2289.OTPGenerator('1234567', 'TeSt', otp2289.OTP_ALGO_MD5)
125 assert exc_info.type is otp2289.OTPGeneratorException 124 assert exc_info.type is otp2289.OTPGeneratorError
126 assert exc_info.value.args[0] == 'Password must be a byte-string' 125 assert exc_info.value.args[0] == 'Password must be a byte-string'
127 with pytest.raises(otp2289.OTPGeneratorException) as exc_info: 126 with pytest.raises(otp2289.OTPGeneratorError) as exc_info:
128 otp2289.OTPGenerator( 127 otp2289.OTPGenerator(
129 '1234567'.encode(), 128 '1234567'.encode(),
130 'TeSt', 129 'TeSt',
131 otp2289.OTP_ALGO_MD5, 130 otp2289.OTP_ALGO_MD5,
132 ) 131 )
133 assert exc_info.type is otp2289.OTPGeneratorException 132 assert exc_info.type is otp2289.OTPGeneratorError
134 assert exc_info.value.args[0] == 'Password must be longer than 10 bytes' 133 assert exc_info.value.args[0] == 'Password must be longer than 10 bytes'
135 134
136 135
diff --git a/test/test_main.py b/test/test_main.py
index 35bd2a2..593bf80 100644
--- a/test/test_main.py
+++ b/test/test_main.py
@@ -1,7 +1,6 @@
1# -*- coding: utf-8 -*-
2# SPDX-License-Identifier: BSD-2-Clause-FreeBSD 1# SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3# 2#
4# Copyright (c) 2020-2023, Simeon Simeonov 3# Copyright (c) 2020-2025, Simeon Simeonov
5# All rights reserved. 4# All rights reserved.
6# 5#
7# Redistribution and use in source and binary forms, with or without 6# Redistribution and use in source and binary forms, with or without
diff --git a/test/test_server.py b/test/test_server.py
index 64e7f67..e81532f 100644
--- a/test/test_server.py
+++ b/test/test_server.py
@@ -1,7 +1,6 @@
1# -*- coding: utf-8 -*-
2# SPDX-License-Identifier: BSD-2-Clause-FreeBSD 1# SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3# 2#
4# Copyright (c) 2020-2023, Simeon Simeonov 3# Copyright (c) 2020-2025, Simeon Simeonov
5# All rights reserved. 4# All rights reserved.
6# 5#
7# Redistribution and use in source and binary forms, with or without 6# Redistribution and use in source and binary forms, with or without
@@ -39,9 +38,9 @@ def test_state_caller_exceptions():
39 'TeSt', 38 'TeSt',
40 otp2289.OTP_ALGO_MD5, 39 otp2289.OTP_ALGO_MD5,
41 ) 40 )
42 with pytest.raises(otp2289.OTPInvalidResponse) as exc_info: 41 with pytest.raises(otp2289.OTPInvalidResponseError) as exc_info:
43 state.response_validates('bla') 42 state.response_validates('bla')
44 assert exc_info.type is otp2289.OTPInvalidResponse 43 assert exc_info.type is otp2289.OTPInvalidResponseError
45 assert exc_info.value.args[0] == ( 44 assert exc_info.value.args[0] == (
46 'The response is neither a valid token or hex' 45 'The response is neither a valid token or hex'
47 ) 46 )
@@ -49,23 +48,23 @@ def test_state_caller_exceptions():
49 48
50def test_state_constructor_exceptions(): 49def test_state_constructor_exceptions():
51 """Tests the exceptions when initializing new OTPState objects""" 50 """Tests the exceptions when initializing new OTPState objects"""
52 with pytest.raises(otp2289.OTPStateException) as exc_info: 51 with pytest.raises(otp2289.OTPStateError) as exc_info:
53 otp2289.OTPState( 52 otp2289.OTPState(
54 '0x7965e05436f5029t', 53 '0x7965e05436f5029t',
55 1, 54 1,
56 'TeStø'.encode(), 55 'TeStø'.encode(),
57 otp2289.OTP_ALGO_MD5, 56 otp2289.OTP_ALGO_MD5,
58 ) 57 )
59 assert exc_info.type is otp2289.OTPStateException 58 assert exc_info.type is otp2289.OTPStateError
60 assert exc_info.value.args[0] == 'Seed must be a string' 59 assert exc_info.value.args[0] == 'Seed must be a string'
61 with pytest.raises(otp2289.OTPStateException) as exc_info: 60 with pytest.raises(otp2289.OTPStateError) as exc_info:
62 otp2289.OTPState( 61 otp2289.OTPState(
63 '0x7965e05436f5029t', 62 '0x7965e05436f5029t',
64 '1', 63 '1',
65 'TeSt', 64 'TeSt',
66 otp2289.OTP_ALGO_MD5, 65 otp2289.OTP_ALGO_MD5,
67 ) 66 )
68 assert exc_info.type is otp2289.OTPStateException 67 assert exc_info.type is otp2289.OTPStateError
69 assert exc_info.value.args[0] == 'Step value MUST be an int' 68 assert exc_info.value.args[0] == 'Step value MUST be an int'
70 69
71 70