summaryrefslogtreecommitdiff
path: root/src/otp2289/generator.py
diff options
context:
space:
mode:
authorSimeon Simeonov2022-03-31 07:21:33 +0200
committerSimeon Simeonov2022-03-31 07:21:33 +0200
commit4ea3c077bd4111960af1b93195cb25bfb1d6edce (patch)
tree9ed0b8fdb4398cf79a8ecc3ab39ade2d374eb65c /src/otp2289/generator.py
parentf19fde48c6b3f65c164c931286e72a0d254c1fb3 (diff)
Restructure the project in order to replace distutils with setuptoolsv1.1.0
Diffstat (limited to 'src/otp2289/generator.py')
-rw-r--r--src/otp2289/generator.py760
1 files changed, 760 insertions, 0 deletions
diff --git a/src/otp2289/generator.py b/src/otp2289/generator.py
new file mode 100644
index 0000000..7b86e1b
--- /dev/null
+++ b/src/otp2289/generator.py
@@ -0,0 +1,760 @@
1# -*- coding: utf-8 -*-
2# SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3#
4# Copyright (c) 2020-2022 Simeon Simeonov
5# All rights reserved.
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions
9# are met:
10# 1. Redistributions of source code must retain the above copyright
11# notice, this list of conditions and the following disclaimer.
12# 2. Redistributions in binary form must reproduce the above copyright notice,
13# this list of conditions and the following disclaimer in the documentation
14# and/or other materials provided with the distribution.
15#
16# THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
17# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24# (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.
26"""A pure Python implementation of the RFC-2289 OTP generator"""
27import binascii
28import hashlib
29import string
30
31OTP_ALGO_MD5 = 1
32OTP_ALGO_SHA1 = 2
33
34# the tokens are defined in https://tools.ietf.org/html/rfc2289 #
35RFC1760_TOKENS = [
36 'A', 'ABE', 'ACE', 'ACT', 'AD', 'ADA', 'ADD',
37 'AGO', 'AID', 'AIM', 'AIR', 'ALL', 'ALP', 'AM', 'AMY',
38 'AN', 'ANA', 'AND', 'ANN', 'ANT', 'ANY', 'APE', 'APS',
39 'APT', 'ARC', 'ARE', 'ARK', 'ARM', 'ART', 'AS', 'ASH',
40 'ASK', 'AT', 'ATE', 'AUG', 'AUK', 'AVE', 'AWE', 'AWK',
41 'AWL', 'AWN', 'AX', 'AYE', 'BAD', 'BAG', 'BAH', 'BAM',
42 'BAN', 'BAR', 'BAT', 'BAY', 'BE', 'BED', 'BEE', 'BEG',
43 'BEN', 'BET', 'BEY', 'BIB', 'BID', 'BIG', 'BIN', 'BIT',
44 'BOB', 'BOG', 'BON', 'BOO', 'BOP', 'BOW', 'BOY', 'BUB',
45 'BUD', 'BUG', 'BUM', 'BUN', 'BUS', 'BUT', 'BUY', 'BY',
46 'BYE', 'CAB', 'CAL', 'CAM', 'CAN', 'CAP', 'CAR', 'CAT',
47 'CAW', 'COD', 'COG', 'COL', 'CON', 'COO', 'COP', 'COT',
48 'COW', 'COY', 'CRY', 'CUB', 'CUE', 'CUP', 'CUR', 'CUT',
49 'DAB', 'DAD', 'DAM', 'DAN', 'DAR', 'DAY', 'DEE', 'DEL',
50 'DEN', 'DES', 'DEW', 'DID', 'DIE', 'DIG', 'DIN', 'DIP',
51 'DO', 'DOE', 'DOG', 'DON', 'DOT', 'DOW', 'DRY', 'DUB',
52 'DUD', 'DUE', 'DUG', 'DUN', 'EAR', 'EAT', 'ED', 'EEL',
53 'EGG', 'EGO', 'ELI', 'ELK', 'ELM', 'ELY', 'EM', 'END',
54 'EST', 'ETC', 'EVA', 'EVE', 'EWE', 'EYE', 'FAD', 'FAN',
55 'FAR', 'FAT', 'FAY', 'FED', 'FEE', 'FEW', 'FIB', 'FIG',
56 'FIN', 'FIR', 'FIT', 'FLO', 'FLY', 'FOE', 'FOG', 'FOR',
57 'FRY', 'FUM', 'FUN', 'FUR', 'GAB', 'GAD', 'GAG', 'GAL',
58 'GAM', 'GAP', 'GAS', 'GAY', 'GEE', 'GEL', 'GEM', 'GET',
59 'GIG', 'GIL', 'GIN', 'GO', 'GOT', 'GUM', 'GUN', 'GUS',
60 'GUT', 'GUY', 'GYM', 'GYP', 'HA', 'HAD', 'HAL', 'HAM',
61 'HAN', 'HAP', 'HAS', 'HAT', 'HAW', 'HAY', 'HE', 'HEM',
62 'HEN', 'HER', 'HEW', 'HEY', 'HI', 'HID', 'HIM', 'HIP',
63 'HIS', 'HIT', 'HO', 'HOB', 'HOC', 'HOE', 'HOG', 'HOP',
64 'HOT', 'HOW', 'HUB', 'HUE', 'HUG', 'HUH', 'HUM', 'HUT',
65 'I', 'ICY', 'IDA', 'IF', 'IKE', 'ILL', 'INK', 'INN',
66 'IO', 'ION', 'IQ', 'IRA', 'IRE', 'IRK', 'IS', 'IT',
67 'ITS', 'IVY', 'JAB', 'JAG', 'JAM', 'JAN', 'JAR', 'JAW',
68 'JAY', 'JET', 'JIG', 'JIM', 'JO', 'JOB', 'JOE', 'JOG',
69 'JOT', 'JOY', 'JUG', 'JUT', 'KAY', 'KEG', 'KEN', 'KEY',
70 'KID', 'KIM', 'KIN', 'KIT', 'LA', 'LAB', 'LAC', 'LAD',
71 'LAG', 'LAM', 'LAP', 'LAW', 'LAY', 'LEA', 'LED', 'LEE',
72 'LEG', 'LEN', 'LEO', 'LET', 'LEW', 'LID', 'LIE', 'LIN',
73 'LIP', 'LIT', 'LO', 'LOB', 'LOG', 'LOP', 'LOS', 'LOT',
74 'LOU', 'LOW', 'LOY', 'LUG', 'LYE', 'MA', 'MAC', 'MAD',
75 'MAE', 'MAN', 'MAO', 'MAP', 'MAT', 'MAW', 'MAY', 'ME',
76 'MEG', 'MEL', 'MEN', 'MET', 'MEW', 'MID', 'MIN', 'MIT',
77 'MOB', 'MOD', 'MOE', 'MOO', 'MOP', 'MOS', 'MOT', 'MOW',
78 'MUD', 'MUG', 'MUM', 'MY', 'NAB', 'NAG', 'NAN', 'NAP',
79 'NAT', 'NAY', 'NE', 'NED', 'NEE', 'NET', 'NEW', 'NIB',
80 'NIL', 'NIP', 'NIT', 'NO', 'NOB', 'NOD', 'NON', 'NOR',
81 'NOT', 'NOV', 'NOW', 'NU', 'NUN', 'NUT', 'O', 'OAF',
82 'OAK', 'OAR', 'OAT', 'ODD', 'ODE', 'OF', 'OFF', 'OFT',
83 'OH', 'OIL', 'OK', 'OLD', 'ON', 'ONE', 'OR', 'ORB',
84 'ORE', 'ORR', 'OS', 'OTT', 'OUR', 'OUT', 'OVA', 'OW',
85 'OWE', 'OWL', 'OWN', 'OX', 'PA', 'PAD', 'PAL', 'PAM',
86 'PAN', 'PAP', 'PAR', 'PAT', 'PAW', 'PAY', 'PEA', 'PEG',
87 'PEN', 'PEP', 'PER', 'PET', 'PEW', 'PHI', 'PI', 'PIE',
88 'PIN', 'PIT', 'PLY', 'PO', 'POD', 'POE', 'POP', 'POT',
89 'POW', 'PRO', 'PRY', 'PUB', 'PUG', 'PUN', 'PUP', 'PUT',
90 'QUO', 'RAG', 'RAM', 'RAN', 'RAP', 'RAT', 'RAW', 'RAY',
91 'REB', 'RED', 'REP', 'RET', 'RIB', 'RID', 'RIG', 'RIM',
92 'RIO', 'RIP', 'ROB', 'ROD', 'ROE', 'RON', 'ROT', 'ROW',
93 'ROY', 'RUB', 'RUE', 'RUG', 'RUM', 'RUN', 'RYE', 'SAC',
94 'SAD', 'SAG', 'SAL', 'SAM', 'SAN', 'SAP', 'SAT', 'SAW',
95 'SAY', 'SEA', 'SEC', 'SEE', 'SEN', 'SET', 'SEW', 'SHE',
96 'SHY', 'SIN', 'SIP', 'SIR', 'SIS', 'SIT', 'SKI', 'SKY',
97 'SLY', 'SO', 'SOB', 'SOD', 'SON', 'SOP', 'SOW', 'SOY',
98 'SPA', 'SPY', 'SUB', 'SUD', 'SUE', 'SUM', 'SUN', 'SUP',
99 'TAB', 'TAD', 'TAG', 'TAN', 'TAP', 'TAR', 'TEA', 'TED',
100 'TEE', 'TEN', 'THE', 'THY', 'TIC', 'TIE', 'TIM', 'TIN',
101 'TIP', 'TO', 'TOE', 'TOG', 'TOM', 'TON', 'TOO', 'TOP',
102 'TOW', 'TOY', 'TRY', 'TUB', 'TUG', 'TUM', 'TUN', 'TWO',
103 'UN', 'UP', 'US', 'USE', 'VAN', 'VAT', 'VET', 'VIE',
104 'WAD', 'WAG', 'WAR', 'WAS', 'WAY', 'WE', 'WEB', 'WED',
105 'WEE', 'WET', 'WHO', 'WHY', 'WIN', 'WIT', 'WOK', 'WON',
106 'WOO', 'WOW', 'WRY', 'WU', 'YAM', 'YAP', 'YAW', 'YE',
107 'YEA', 'YES', 'YET', 'YOU', 'ABED', 'ABEL', 'ABET', 'ABLE',
108 'ABUT', 'ACHE', 'ACID', 'ACME', 'ACRE', 'ACTA', 'ACTS', 'ADAM',
109 'ADDS', 'ADEN', 'AFAR', 'AFRO', 'AGEE', 'AHEM', 'AHOY', 'AIDA',
110 'AIDE', 'AIDS', 'AIRY', 'AJAR', 'AKIN', 'ALAN', 'ALEC', 'ALGA',
111 'ALIA', 'ALLY', 'ALMA', 'ALOE', 'ALSO', 'ALTO', 'ALUM', 'ALVA',
112 'AMEN', 'AMES', 'AMID', 'AMMO', 'AMOK', 'AMOS', 'AMRA', 'ANDY',
113 'ANEW', 'ANNA', 'ANNE', 'ANTE', 'ANTI', 'AQUA', 'ARAB', 'ARCH',
114 'AREA', 'ARGO', 'ARID', 'ARMY', 'ARTS', 'ARTY', 'ASIA', 'ASKS',
115 'ATOM', 'AUNT', 'AURA', 'AUTO', 'AVER', 'AVID', 'AVIS', 'AVON',
116 'AVOW', 'AWAY', 'AWRY', 'BABE', 'BABY', 'BACH', 'BACK', 'BADE',
117 'BAIL', 'BAIT', 'BAKE', 'BALD', 'BALE', 'BALI', 'BALK', 'BALL',
118 'BALM', 'BAND', 'BANE', 'BANG', 'BANK', 'BARB', 'BARD', 'BARE',
119 'BARK', 'BARN', 'BARR', 'BASE', 'BASH', 'BASK', 'BASS', 'BATE',
120 'BATH', 'BAWD', 'BAWL', 'BEAD', 'BEAK', 'BEAM', 'BEAN', 'BEAR',
121 'BEAT', 'BEAU', 'BECK', 'BEEF', 'BEEN', 'BEER', 'BEET', 'BELA',
122 'BELL', 'BELT', 'BEND', 'BENT', 'BERG', 'BERN', 'BERT', 'BESS',
123 'BEST', 'BETA', 'BETH', 'BHOY', 'BIAS', 'BIDE', 'BIEN', 'BILE',
124 'BILK', 'BILL', 'BIND', 'BING', 'BIRD', 'BITE', 'BITS', 'BLAB',
125 'BLAT', 'BLED', 'BLEW', 'BLOB', 'BLOC', 'BLOT', 'BLOW', 'BLUE',
126 'BLUM', 'BLUR', 'BOAR', 'BOAT', 'BOCA', 'BOCK', 'BODE', 'BODY',
127 'BOGY', 'BOHR', 'BOIL', 'BOLD', 'BOLO', 'BOLT', 'BOMB', 'BONA',
128 'BOND', 'BONE', 'BONG', 'BONN', 'BONY', 'BOOK', 'BOOM', 'BOON',
129 'BOOT', 'BORE', 'BORG', 'BORN', 'BOSE', 'BOSS', 'BOTH', 'BOUT',
130 'BOWL', 'BOYD', 'BRAD', 'BRAE', 'BRAG', 'BRAN', 'BRAY', 'BRED',
131 'BREW', 'BRIG', 'BRIM', 'BROW', 'BUCK', 'BUDD', 'BUFF', 'BULB',
132 'BULK', 'BULL', 'BUNK', 'BUNT', 'BUOY', 'BURG', 'BURL', 'BURN',
133 'BURR', 'BURT', 'BURY', 'BUSH', 'BUSS', 'BUST', 'BUSY', 'BYTE',
134 'CADY', 'CAFE', 'CAGE', 'CAIN', 'CAKE', 'CALF', 'CALL', 'CALM',
135 'CAME', 'CANE', 'CANT', 'CARD', 'CARE', 'CARL', 'CARR', 'CART',
136 'CASE', 'CASH', 'CASK', 'CAST', 'CAVE', 'CEIL', 'CELL', 'CENT',
137 'CERN', 'CHAD', 'CHAR', 'CHAT', 'CHAW', 'CHEF', 'CHEN', 'CHEW',
138 'CHIC', 'CHIN', 'CHOU', 'CHOW', 'CHUB', 'CHUG', 'CHUM', 'CITE',
139 'CITY', 'CLAD', 'CLAM', 'CLAN', 'CLAW', 'CLAY', 'CLOD', 'CLOG',
140 'CLOT', 'CLUB', 'CLUE', 'COAL', 'COAT', 'COCA', 'COCK', 'COCO',
141 'CODA', 'CODE', 'CODY', 'COED', 'COIL', 'COIN', 'COKE', 'COLA',
142 'COLD', 'COLT', 'COMA', 'COMB', 'COME', 'COOK', 'COOL', 'COON',
143 'COOT', 'CORD', 'CORE', 'CORK', 'CORN', 'COST', 'COVE', 'COWL',
144 'CRAB', 'CRAG', 'CRAM', 'CRAY', 'CREW', 'CRIB', 'CROW', 'CRUD',
145 'CUBA', 'CUBE', 'CUFF', 'CULL', 'CULT', 'CUNY', 'CURB', 'CURD',
146 'CURE', 'CURL', 'CURT', 'CUTS', 'DADE', 'DALE', 'DAME', 'DANA',
147 'DANE', 'DANG', 'DANK', 'DARE', 'DARK', 'DARN', 'DART', 'DASH',
148 'DATA', 'DATE', 'DAVE', 'DAVY', 'DAWN', 'DAYS', 'DEAD', 'DEAF',
149 'DEAL', 'DEAN', 'DEAR', 'DEBT', 'DECK', 'DEED', 'DEEM', 'DEER',
150 'DEFT', 'DEFY', 'DELL', 'DENT', 'DENY', 'DESK', 'DIAL', 'DICE',
151 'DIED', 'DIET', 'DIME', 'DINE', 'DING', 'DINT', 'DIRE', 'DIRT',
152 'DISC', 'DISH', 'DISK', 'DIVE', 'DOCK', 'DOES', 'DOLE', 'DOLL',
153 'DOLT', 'DOME', 'DONE', 'DOOM', 'DOOR', 'DORA', 'DOSE', 'DOTE',
154 'DOUG', 'DOUR', 'DOVE', 'DOWN', 'DRAB', 'DRAG', 'DRAM', 'DRAW',
155 'DREW', 'DRUB', 'DRUG', 'DRUM', 'DUAL', 'DUCK', 'DUCT', 'DUEL',
156 'DUET', 'DUKE', 'DULL', 'DUMB', 'DUNE', 'DUNK', 'DUSK', 'DUST',
157 'DUTY', 'EACH', 'EARL', 'EARN', 'EASE', 'EAST', 'EASY', 'EBEN',
158 'ECHO', 'EDDY', 'EDEN', 'EDGE', 'EDGY', 'EDIT', 'EDNA', 'EGAN',
159 'ELAN', 'ELBA', 'ELLA', 'ELSE', 'EMIL', 'EMIT', 'EMMA', 'ENDS',
160 'ERIC', 'EROS', 'EVEN', 'EVER', 'EVIL', 'EYED', 'FACE', 'FACT',
161 'FADE', 'FAIL', 'FAIN', 'FAIR', 'FAKE', 'FALL', 'FAME', 'FANG',
162 'FARM', 'FAST', 'FATE', 'FAWN', 'FEAR', 'FEAT', 'FEED', 'FEEL',
163 'FEET', 'FELL', 'FELT', 'FEND', 'FERN', 'FEST', 'FEUD', 'FIEF',
164 'FIGS', 'FILE', 'FILL', 'FILM', 'FIND', 'FINE', 'FINK', 'FIRE',
165 'FIRM', 'FISH', 'FISK', 'FIST', 'FITS', 'FIVE', 'FLAG', 'FLAK',
166 'FLAM', 'FLAT', 'FLAW', 'FLEA', 'FLED', 'FLEW', 'FLIT', 'FLOC',
167 'FLOG', 'FLOW', 'FLUB', 'FLUE', 'FOAL', 'FOAM', 'FOGY', 'FOIL',
168 'FOLD', 'FOLK', 'FOND', 'FONT', 'FOOD', 'FOOL', 'FOOT', 'FORD',
169 'FORE', 'FORK', 'FORM', 'FORT', 'FOSS', 'FOUL', 'FOUR', 'FOWL',
170 'FRAU', 'FRAY', 'FRED', 'FREE', 'FRET', 'FREY', 'FROG', 'FROM',
171 'FUEL', 'FULL', 'FUME', 'FUND', 'FUNK', 'FURY', 'FUSE', 'FUSS',
172 'GAFF', 'GAGE', 'GAIL', 'GAIN', 'GAIT', 'GALA', 'GALE', 'GALL',
173 'GALT', 'GAME', 'GANG', 'GARB', 'GARY', 'GASH', 'GATE', 'GAUL',
174 'GAUR', 'GAVE', 'GAWK', 'GEAR', 'GELD', 'GENE', 'GENT', 'GERM',
175 'GETS', 'GIBE', 'GIFT', 'GILD', 'GILL', 'GILT', 'GINA', 'GIRD',
176 'GIRL', 'GIST', 'GIVE', 'GLAD', 'GLEE', 'GLEN', 'GLIB', 'GLOB',
177 'GLOM', 'GLOW', 'GLUE', 'GLUM', 'GLUT', 'GOAD', 'GOAL', 'GOAT',
178 'GOER', 'GOES', 'GOLD', 'GOLF', 'GONE', 'GONG', 'GOOD', 'GOOF',
179 'GORE', 'GORY', 'GOSH', 'GOUT', 'GOWN', 'GRAB', 'GRAD', 'GRAY',
180 'GREG', 'GREW', 'GREY', 'GRID', 'GRIM', 'GRIN', 'GRIT', 'GROW',
181 'GRUB', 'GULF', 'GULL', 'GUNK', 'GURU', 'GUSH', 'GUST', 'GWEN',
182 'GWYN', 'HAAG', 'HAAS', 'HACK', 'HAIL', 'HAIR', 'HALE', 'HALF',
183 'HALL', 'HALO', 'HALT', 'HAND', 'HANG', 'HANK', 'HANS', 'HARD',
184 'HARK', 'HARM', 'HART', 'HASH', 'HAST', 'HATE', 'HATH', 'HAUL',
185 'HAVE', 'HAWK', 'HAYS', 'HEAD', 'HEAL', 'HEAR', 'HEAT', 'HEBE',
186 'HECK', 'HEED', 'HEEL', 'HEFT', 'HELD', 'HELL', 'HELM', 'HERB',
187 'HERD', 'HERE', 'HERO', 'HERS', 'HESS', 'HEWN', 'HICK', 'HIDE',
188 'HIGH', 'HIKE', 'HILL', 'HILT', 'HIND', 'HINT', 'HIRE', 'HISS',
189 'HIVE', 'HOBO', 'HOCK', 'HOFF', 'HOLD', 'HOLE', 'HOLM', 'HOLT',
190 'HOME', 'HONE', 'HONK', 'HOOD', 'HOOF', 'HOOK', 'HOOT', 'HORN',
191 'HOSE', 'HOST', 'HOUR', 'HOVE', 'HOWE', 'HOWL', 'HOYT', 'HUCK',
192 'HUED', 'HUFF', 'HUGE', 'HUGH', 'HUGO', 'HULK', 'HULL', 'HUNK',
193 'HUNT', 'HURD', 'HURL', 'HURT', 'HUSH', 'HYDE', 'HYMN', 'IBIS',
194 'ICON', 'IDEA', 'IDLE', 'IFFY', 'INCA', 'INCH', 'INTO', 'IONS',
195 'IOTA', 'IOWA', 'IRIS', 'IRMA', 'IRON', 'ISLE', 'ITCH', 'ITEM',
196 'IVAN', 'JACK', 'JADE', 'JAIL', 'JAKE', 'JANE', 'JAVA', 'JEAN',
197 'JEFF', 'JERK', 'JESS', 'JEST', 'JIBE', 'JILL', 'JILT', 'JIVE',
198 'JOAN', 'JOBS', 'JOCK', 'JOEL', 'JOEY', 'JOHN', 'JOIN', 'JOKE',
199 'JOLT', 'JOVE', 'JUDD', 'JUDE', 'JUDO', 'JUDY', 'JUJU', 'JUKE',
200 'JULY', 'JUNE', 'JUNK', 'JUNO', 'JURY', 'JUST', 'JUTE', 'KAHN',
201 'KALE', 'KANE', 'KANT', 'KARL', 'KATE', 'KEEL', 'KEEN', 'KENO',
202 'KENT', 'KERN', 'KERR', 'KEYS', 'KICK', 'KILL', 'KIND', 'KING',
203 'KIRK', 'KISS', 'KITE', 'KLAN', 'KNEE', 'KNEW', 'KNIT', 'KNOB',
204 'KNOT', 'KNOW', 'KOCH', 'KONG', 'KUDO', 'KURD', 'KURT', 'KYLE',
205 'LACE', 'LACK', 'LACY', 'LADY', 'LAID', 'LAIN', 'LAIR', 'LAKE',
206 'LAMB', 'LAME', 'LAND', 'LANE', 'LANG', 'LARD', 'LARK', 'LASS',
207 'LAST', 'LATE', 'LAUD', 'LAVA', 'LAWN', 'LAWS', 'LAYS', 'LEAD',
208 'LEAF', 'LEAK', 'LEAN', 'LEAR', 'LEEK', 'LEER', 'LEFT', 'LEND',
209 'LENS', 'LENT', 'LEON', 'LESK', 'LESS', 'LEST', 'LETS', 'LIAR',
210 'LICE', 'LICK', 'LIED', 'LIEN', 'LIES', 'LIEU', 'LIFE', 'LIFT',
211 'LIKE', 'LILA', 'LILT', 'LILY', 'LIMA', 'LIMB', 'LIME', 'LIND',
212 'LINE', 'LINK', 'LINT', 'LION', 'LISA', 'LIST', 'LIVE', 'LOAD',
213 'LOAF', 'LOAM', 'LOAN', 'LOCK', 'LOFT', 'LOGE', 'LOIS', 'LOLA',
214 'LONE', 'LONG', 'LOOK', 'LOON', 'LOOT', 'LORD', 'LORE', 'LOSE',
215 'LOSS', 'LOST', 'LOUD', 'LOVE', 'LOWE', 'LUCK', 'LUCY', 'LUGE',
216 'LUKE', 'LULU', 'LUND', 'LUNG', 'LURA', 'LURE', 'LURK', 'LUSH',
217 'LUST', 'LYLE', 'LYNN', 'LYON', 'LYRA', 'MACE', 'MADE', 'MAGI',
218 'MAID', 'MAIL', 'MAIN', 'MAKE', 'MALE', 'MALI', 'MALL', 'MALT',
219 'MANA', 'MANN', 'MANY', 'MARC', 'MARE', 'MARK', 'MARS', 'MART',
220 'MARY', 'MASH', 'MASK', 'MASS', 'MAST', 'MATE', 'MATH', 'MAUL',
221 'MAYO', 'MEAD', 'MEAL', 'MEAN', 'MEAT', 'MEEK', 'MEET', 'MELD',
222 'MELT', 'MEMO', 'MEND', 'MENU', 'MERT', 'MESH', 'MESS', 'MICE',
223 'MIKE', 'MILD', 'MILE', 'MILK', 'MILL', 'MILT', 'MIMI', 'MIND',
224 'MINE', 'MINI', 'MINK', 'MINT', 'MIRE', 'MISS', 'MIST', 'MITE',
225 'MITT', 'MOAN', 'MOAT', 'MOCK', 'MODE', 'MOLD', 'MOLE', 'MOLL',
226 'MOLT', 'MONA', 'MONK', 'MONT', 'MOOD', 'MOON', 'MOOR', 'MOOT',
227 'MORE', 'MORN', 'MORT', 'MOSS', 'MOST', 'MOTH', 'MOVE', 'MUCH',
228 'MUCK', 'MUDD', 'MUFF', 'MULE', 'MULL', 'MURK', 'MUSH', 'MUST',
229 'MUTE', 'MUTT', 'MYRA', 'MYTH', 'NAGY', 'NAIL', 'NAIR', 'NAME',
230 'NARY', 'NASH', 'NAVE', 'NAVY', 'NEAL', 'NEAR', 'NEAT', 'NECK',
231 'NEED', 'NEIL', 'NELL', 'NEON', 'NERO', 'NESS', 'NEST', 'NEWS',
232 'NEWT', 'NIBS', 'NICE', 'NICK', 'NILE', 'NINA', 'NINE', 'NOAH',
233 'NODE', 'NOEL', 'NOLL', 'NONE', 'NOOK', 'NOON', 'NORM', 'NOSE',
234 'NOTE', 'NOUN', 'NOVA', 'NUDE', 'NULL', 'NUMB', 'OATH', 'OBEY',
235 'OBOE', 'ODIN', 'OHIO', 'OILY', 'OINT', 'OKAY', 'OLAF', 'OLDY',
236 'OLGA', 'OLIN', 'OMAN', 'OMEN', 'OMIT', 'ONCE', 'ONES', 'ONLY',
237 'ONTO', 'ONUS', 'ORAL', 'ORGY', 'OSLO', 'OTIS', 'OTTO', 'OUCH',
238 'OUST', 'OUTS', 'OVAL', 'OVEN', 'OVER', 'OWLY', 'OWNS', 'QUAD',
239 'QUIT', 'QUOD', 'RACE', 'RACK', 'RACY', 'RAFT', 'RAGE', 'RAID',
240 'RAIL', 'RAIN', 'RAKE', 'RANK', 'RANT', 'RARE', 'RASH', 'RATE',
241 'RAVE', 'RAYS', 'READ', 'REAL', 'REAM', 'REAR', 'RECK', 'REED',
242 'REEF', 'REEK', 'REEL', 'REID', 'REIN', 'RENA', 'REND', 'RENT',
243 'REST', 'RICE', 'RICH', 'RICK', 'RIDE', 'RIFT', 'RILL', 'RIME',
244 'RING', 'RINK', 'RISE', 'RISK', 'RITE', 'ROAD', 'ROAM', 'ROAR',
245 'ROBE', 'ROCK', 'RODE', 'ROIL', 'ROLL', 'ROME', 'ROOD', 'ROOF',
246 'ROOK', 'ROOM', 'ROOT', 'ROSA', 'ROSE', 'ROSS', 'ROSY', 'ROTH',
247 'ROUT', 'ROVE', 'ROWE', 'ROWS', 'RUBE', 'RUBY', 'RUDE', 'RUDY',
248 'RUIN', 'RULE', 'RUNG', 'RUNS', 'RUNT', 'RUSE', 'RUSH', 'RUSK',
249 'RUSS', 'RUST', 'RUTH', 'SACK', 'SAFE', 'SAGE', 'SAID', 'SAIL',
250 'SALE', 'SALK', 'SALT', 'SAME', 'SAND', 'SANE', 'SANG', 'SANK',
251 'SARA', 'SAUL', 'SAVE', 'SAYS', 'SCAN', 'SCAR', 'SCAT', 'SCOT',
252 'SEAL', 'SEAM', 'SEAR', 'SEAT', 'SEED', 'SEEK', 'SEEM', 'SEEN',
253 'SEES', 'SELF', 'SELL', 'SEND', 'SENT', 'SETS', 'SEWN', 'SHAG',
254 'SHAM', 'SHAW', 'SHAY', 'SHED', 'SHIM', 'SHIN', 'SHOD', 'SHOE',
255 'SHOT', 'SHOW', 'SHUN', 'SHUT', 'SICK', 'SIDE', 'SIFT', 'SIGH',
256 'SIGN', 'SILK', 'SILL', 'SILO', 'SILT', 'SINE', 'SING', 'SINK',
257 'SIRE', 'SITE', 'SITS', 'SITU', 'SKAT', 'SKEW', 'SKID', 'SKIM',
258 'SKIN', 'SKIT', 'SLAB', 'SLAM', 'SLAT', 'SLAY', 'SLED', 'SLEW',
259 'SLID', 'SLIM', 'SLIT', 'SLOB', 'SLOG', 'SLOT', 'SLOW', 'SLUG',
260 'SLUM', 'SLUR', 'SMOG', 'SMUG', 'SNAG', 'SNOB', 'SNOW', 'SNUB',
261 'SNUG', 'SOAK', 'SOAR', 'SOCK', 'SODA', 'SOFA', 'SOFT', 'SOIL',
262 'SOLD', 'SOME', 'SONG', 'SOON', 'SOOT', 'SORE', 'SORT', 'SOUL',
263 'SOUR', 'SOWN', 'STAB', 'STAG', 'STAN', 'STAR', 'STAY', 'STEM',
264 'STEW', 'STIR', 'STOW', 'STUB', 'STUN', 'SUCH', 'SUDS', 'SUIT',
265 'SULK', 'SUMS', 'SUNG', 'SUNK', 'SURE', 'SURF', 'SWAB', 'SWAG',
266 'SWAM', 'SWAN', 'SWAT', 'SWAY', 'SWIM', 'SWUM', 'TACK', 'TACT',
267 'TAIL', 'TAKE', 'TALE', 'TALK', 'TALL', 'TANK', 'TASK', 'TATE',
268 'TAUT', 'TEAL', 'TEAM', 'TEAR', 'TECH', 'TEEM', 'TEEN', 'TEET',
269 'TELL', 'TEND', 'TENT', 'TERM', 'TERN', 'TESS', 'TEST', 'THAN',
270 'THAT', 'THEE', 'THEM', 'THEN', 'THEY', 'THIN', 'THIS', 'THUD',
271 'THUG', 'TICK', 'TIDE', 'TIDY', 'TIED', 'TIER', 'TILE', 'TILL',
272 'TILT', 'TIME', 'TINA', 'TINE', 'TINT', 'TINY', 'TIRE', 'TOAD',
273 'TOGO', 'TOIL', 'TOLD', 'TOLL', 'TONE', 'TONG', 'TONY', 'TOOK',
274 'TOOL', 'TOOT', 'TORE', 'TORN', 'TOTE', 'TOUR', 'TOUT', 'TOWN',
275 'TRAG', 'TRAM', 'TRAY', 'TREE', 'TREK', 'TRIG', 'TRIM', 'TRIO',
276 'TROD', 'TROT', 'TROY', 'TRUE', 'TUBA', 'TUBE', 'TUCK', 'TUFT',
277 'TUNA', 'TUNE', 'TUNG', 'TURF', 'TURN', 'TUSK', 'TWIG', 'TWIN',
278 'TWIT', 'ULAN', 'UNIT', 'URGE', 'USED', 'USER', 'USES', 'UTAH',
279 'VAIL', 'VAIN', 'VALE', 'VARY', 'VASE', 'VAST', 'VEAL', 'VEDA',
280 'VEIL', 'VEIN', 'VEND', 'VENT', 'VERB', 'VERY', 'VETO', 'VICE',
281 'VIEW', 'VINE', 'VISE', 'VOID', 'VOLT', 'VOTE', 'WACK', 'WADE',
282 'WAGE', 'WAIL', 'WAIT', 'WAKE', 'WALE', 'WALK', 'WALL', 'WALT',
283 'WAND', 'WANE', 'WANG', 'WANT', 'WARD', 'WARM', 'WARN', 'WART',
284 'WASH', 'WAST', 'WATS', 'WATT', 'WAVE', 'WAVY', 'WAYS', 'WEAK',
285 'WEAL', 'WEAN', 'WEAR', 'WEED', 'WEEK', 'WEIR', 'WELD', 'WELL',
286 'WELT', 'WENT', 'WERE', 'WERT', 'WEST', 'WHAM', 'WHAT', 'WHEE',
287 'WHEN', 'WHET', 'WHOA', 'WHOM', 'WICK', 'WIFE', 'WILD', 'WILL',
288 'WIND', 'WINE', 'WING', 'WINK', 'WINO', 'WIRE', 'WISE', 'WISH',
289 'WITH', 'WOLF', 'WONT', 'WOOD', 'WOOL', 'WORD', 'WORE', 'WORK',
290 'WORM', 'WORN', 'WOVE', 'WRIT', 'WYNN', 'YALE', 'YANG', 'YANK',
291 'YARD', 'YARN', 'YAWL', 'YAWN', 'YEAH', 'YEAR', 'YELL', 'YOGA',
292 'YOKE']
293
294_ALGO_DICT = {OTP_ALGO_MD5: 'md5', OTP_ALGO_SHA1: 'sha1'}
295
296
297class OTPGeneratorException(Exception):
298 """OTPGeneratorException class"""
299
300
301class OTPChallengeException(Exception):
302 """OTPChallengeException class"""
303
304
305class OTPGenerator:
306 """OTPGenerator class"""
307
308 def __init__(
309 self,
310 password: bytes,
311 seed: str = '',
312 hash_algo=OTP_ALGO_MD5,
313 ):
314 """
315 Constructs an OTPGenerator object with a given password and seed.
316
317 :param password: The password string
318 :type password: bytes
319
320 :param seed: The seed received from the challenge, defaults to ''
321 :type seed: str
322
323 :param hash_algo: The hash algo, defaults to OTP_ALGO_MD5
324 :type hash_algo: int or str
325
326 :raises otp2289.OTPGeneratorException: If the input does not validate
327 """
328 # enforce the rfc2289 constraints
329 self._seed = seed
330 if self._seed: # the seed was set here. Validate it
331 self._seed = self.validate_seed(self._seed)
332 self._hash_algo = self.validate_hash_algo(hash_algo)
333 if not isinstance(password, bytes):
334 raise OTPGeneratorException('Password must be a byte-string')
335 if len(password) < 10:
336 raise OTPGeneratorException(
337 'Password must be longer than 10 bytes'
338 )
339 self._password = password
340
341 def __repr__(self):
342 """repr implementation"""
343 return (
344 f'{self.__class__} at {id(self)} (seed={self._seed}, '
345 f'hash_algo={self._hash_algo})'
346 )
347
348 @staticmethod
349 def bit_pair_sum(bit_stream: str) -> int:
350 """
351 Split bit_stream in bit-pairs and sum them all together.
352
353 :param bit_stream: The bit-stream object
354 :type bit_stream: str
355
356 :return: The sum of all bit-pairs in bit_stream
357 :rtype: int
358 """
359 if not isinstance(bit_stream, str):
360 raise OTPGeneratorException('bit_stream must be of type str')
361 if len(bit_stream) != 64:
362 raise OTPGeneratorException('bit_stream must be of size 64')
363 value = 0
364 for pair in zip(bit_stream[::2], bit_stream[1::2]):
365 value += int(''.join(pair), 2)
366 return value
367
368 @staticmethod
369 def bytes_to_tokens(hash_bytes: bytes) -> str:
370 """
371 Returns a 6 words token from bytes as specified by RFC-2289.
372
373 :param hash_bytes: The input bytes
374 :type hash_bytes: bytes
375
376 :return: 6 words tokens
377 :rtype: str
378 """
379 bit_stream = ''.join([f'{byte:0>8b}' for byte in hash_bytes])
380 bit_pair_sum = OTPGenerator.bit_pair_sum(bit_stream)
381 tokens = []
382 tokens.append(RFC1760_TOKENS[int(bit_stream[:11], 2)])
383 tokens.append(RFC1760_TOKENS[int(bit_stream[11:22], 2)])
384 tokens.append(RFC1760_TOKENS[int(bit_stream[22:33], 2)])
385 tokens.append(RFC1760_TOKENS[int(bit_stream[33:44], 2)])
386 tokens.append(RFC1760_TOKENS[int(bit_stream[44:55], 2)])
387 tokens.append(
388 RFC1760_TOKENS[
389 int(
390 bit_stream[55:64] + f'{bit_pair_sum:0>8b}'[-2:],
391 2,
392 )
393 ]
394 )
395 return ' '.join(tokens)
396
397 @staticmethod
398 def get_tokens_from_challenge(challenge: str) -> tuple:
399 """
400 Returns tokens (seed, hash_algo and step) from a challenge string.
401
402 N.B. The tokens are not validated here.
403
404 :param challenge: The challenge string described in RFC-2289
405 :type challenge: str
406
407 :raises otp2289.OTPChallengeException: If the challenge is invalid
408
409 :return: (seed, hash_algo, step) tuple.
410 :rtype: tuple
411 """
412 if not isinstance(challenge, str):
413 raise OTPChallengeException('Challenge must be str')
414 challenge = challenge.strip()
415 if not challenge.startswith('otp-'):
416 raise OTPChallengeException('Invalid challenge')
417 try:
418 hash_algo, step, seed = challenge[4:].split()
419 return (seed, hash_algo, int(step))
420 except ValueError:
421 raise OTPChallengeException('Invalid challenge') from None
422
423 @staticmethod
424 def sha1_digest_folding(sha1_digest: bytes) -> bytes:
425 """
426 Implementation of the 160bit -> 64bit folding algorithm
427 for sha1 digest.
428
429 :param sha1_digest: The SHA1 digest
430 :type sha1_digest: bytes
431
432 :return: The byte-string representing the folded sha1-digest
433 :rtype: bytes
434 """
435 if not (isinstance(sha1_digest, bytes)):
436 raise OTPGeneratorException('sha1_digest must be of type bytes')
437 if len(sha1_digest) != 20:
438 raise OTPGeneratorException(
439 'sha1_digest must be 160 bits (20 bytes) long'
440 )
441 digested = list(5 * b'i') # 5 bytes (40 bits)
442 result = list(8 * b'x') # 8 bytes (64 bits)
443 for i in range(5):
444 digested[i] = (
445 ((sha1_digest[i * 4 + 0] & 0xFF) << 24)
446 | ((sha1_digest[i * 4 + 1] & 0xFF) << 16)
447 | ((sha1_digest[i * 4 + 2] & 0xFF) << 8)
448 | (sha1_digest[i * 4 + 3] & 0xFF)
449 )
450 # sha.digest[0] ^= sha.digest[2];
451 # sha.digest[1] ^= sha.digest[3];
452 # sha.digest[0] ^= sha.digest[4];
453 digested[0] ^= digested[2]
454 digested[1] ^= digested[3]
455 digested[0] ^= digested[4]
456 # for (i = 0, j = 0; j < 8; i++, j += 4) {
457 # result[j] = (unsigned char)(sha.digest[i] & 0xff);
458 # result[j+1] = (unsigned char)((sha.digest[i] >> 8) & 0xff);
459 # result[j+2] = (unsigned char)((sha.digest[i] >> 16) & 0xff);
460 # result[j+3] = (unsigned char)((sha.digest[i] >> 24) & 0xff);
461 # }
462 # just hardcoding the two iterations for better efficiency
463 result[0] = digested[0] & 0xFF
464 result[1] = (digested[0] >> 8) & 0xFF
465 result[2] = (digested[0] >> 16) & 0xFF
466 result[3] = (digested[0] >> 24) & 0xFF
467 result[4] = digested[1] & 0xFF
468 result[5] = (digested[1] >> 8) & 0xFF
469 result[6] = (digested[1] >> 16) & 0xFF
470 result[7] = (digested[1] >> 24) & 0xFF
471 return bytes(result)
472
473 @staticmethod
474 def strxor(byte_str1: bytes, byte_str2: bytes) -> bytes:
475 """
476 Implementation of strxor similar to the one provided by pycrypto.
477
478 :param byte_str1: Byte-string 1
479 :type byte_str1: bytes
480
481 :param byte_str2: Byte-string 2
482 :type byte_str2: bytes
483
484 :return: The byte-string representing the result of byte_str1^byte_str2
485 :rtype: bytes
486 """
487 if not (isinstance(byte_str1, bytes) and isinstance(byte_str2, bytes)):
488 raise OTPGeneratorException(
489 'byte_str1 and byte_str2 must be of type bytes'
490 )
491 length = len(byte_str1)
492 if length != len(byte_str2) or length < 1:
493 raise OTPGeneratorException(
494 'byte_str1 and byte_str2 must be of the same length > 0'
495 )
496 return bytes([byte_str1[i] ^ byte_str2[i] for i in range(length)])
497
498 @staticmethod
499 def tokens_to_bytes(tokens_str: str) -> bytes:
500 """
501 Returns bytes from a 6 words token as specified by RFC-2289.
502
503 :param tokens_str: String representing 6 words tokens
504 :type tokens_str: str
505
506 :raises otp2289.OTPGeneratorException: When the tokens_str is invalid
507
508 :return: 6 words tokens
509 :rtype: bytes
510 """
511 if not isinstance(tokens_str, str):
512 raise OTPGeneratorException('tokens must be a str')
513 tokens = tokens_str.split()
514 if len(tokens) != 6:
515 raise OTPGeneratorException(
516 'Tokens-string does not contain 6 tokens'
517 )
518 token_ints = []
519 try:
520 token_ints = [
521 RFC1760_TOKENS.index(token.upper()) for token in tokens
522 ]
523 except ValueError:
524 raise OTPGeneratorException(
525 'One or more words not present in RFC1760'
526 ) from None
527 # now we build a string of bits
528 bit_stream = format(token_ints[0], '011b')
529 bit_stream += format(token_ints[1], '011b')
530 bit_stream += format(token_ints[2], '011b')
531 bit_stream += format(token_ints[3], '011b')
532 bit_stream += format(token_ints[4], '011b')
533 bit_stream += format(token_ints[5], '011b')
534 # we have 66 bits: 64 digest + 2 bit pair sum (control number)
535 # RFC-2289: All OTP generators MUST calculate this checksum and all
536 # OTP servers MUST verify this checksum explicitly as part of the
537 # operation of decoding this representation of the one-time password.
538 if (
539 f'{OTPGenerator.bit_pair_sum(bit_stream[:64]):0>8b}'[-2:]
540 != bit_stream[-2:]
541 ):
542 raise OTPGeneratorException('Invalid bit checksum')
543 return int(bit_stream[:64], 2).to_bytes(8, 'big')
544
545 @staticmethod
546 def validate_hash_algo(hash_algo) -> str:
547 """
548 Validates the provided hash-algorithm.
549
550 :param hash_algo: The hash algo, defaults to OTP_ALGO_MD5
551 :type hash_algo: int or str
552
553 :raises otp2289.OTPGeneratorException: If hash_algo does not validate
554
555 :return: The validated hash_algo in str-form
556 :rtype: str
557 """
558 if isinstance(hash_algo, int):
559 if hash_algo not in _ALGO_DICT:
560 raise OTPGeneratorException(
561 'hash_algo is not among the known algorithms'
562 )
563 hash_algo = _ALGO_DICT.get(hash_algo)
564 if not isinstance(hash_algo, str):
565 raise OTPGeneratorException('hash_algo must be an int or a str')
566 if hash_algo not in hashlib.algorithms_available:
567 raise OTPGeneratorException(
568 f'{hash_algo} is not supported by this version of the '
569 'hashlib module'
570 )
571 return hash_algo
572
573 @staticmethod
574 def validate_seed(seed: str) -> str:
575 """
576 Validates the provided seed as defined by RFC-2289.
577
578 :param seed: The seed received from the challenge, defaults to ''
579 :type seed: str
580
581 :raises otp2289.OTPGeneratorException: If seed does not validate
582
583 :return: The validated (and very same) seed
584 :rtype: str
585 """
586 if not isinstance(seed, str):
587 raise OTPGeneratorException('Seed must be a string')
588 if not seed or len(seed) > 16:
589 raise OTPGeneratorException(
590 'The seed MUST be of 1 to 16 characters in length'
591 )
592 for char in seed:
593 if char not in string.ascii_letters + string.digits:
594 raise OTPGeneratorException(
595 'The seed MUST consist of purely alphanumeric characters'
596 )
597 return seed
598
599 @staticmethod
600 def validate_step(step: int) -> int:
601 """
602 Validates the provided step as defined by RFC-2289.
603
604 :param seed: The step received from the challenge
605 :type seed: int
606
607 :raises otp2289.OTPGeneratorException: If step does not validate
608
609 :return: The validated (and very same) step
610 :rtype: int
611 """
612 if not isinstance(step, int):
613 raise OTPGeneratorException('Step value MUST be an int')
614 if step < 0:
615 raise OTPGeneratorException('Step value MUST be >= 0')
616 return step
617
618 def generate_otp_hexdigest(self, step: int) -> str:
619 """
620 Generates the OTP hexdigest for the given step.
621
622 :param step: The step to generate OTP for
623 :type step: int
624
625 :return: Hexdigest for the given step
626 :rtype: str
627 """
628 return '0x' + binascii.hexlify(self._generate_otp_bytes(step)).decode()
629
630 def generate_otp_hexdigest_from_challenge(self, challenge: str) -> str:
631 """
632 Same as generate_otp_hexdigest, but it generates hex. from a challenge.
633
634 RFC-2289 states:
635 The challenge MUST be in a standard syntax so
636 that automated generators can recognize the challenge in context and
637 extract these parameters. The syntax of the challenge is:
638 otp-<algorithm identifier> <sequence integer> <seed>
639
640 :param challenge: The challenge string
641 :type challenge: str
642
643 :return: Hexdigest for the given challenge
644 :rtype: str
645 """
646 seed, hash_algo, step = self.get_tokens_from_challenge(challenge)
647 self._seed = self.validate_seed(seed)
648 self._hash_algo = self.validate_hash_algo(hash_algo)
649 return self.generate_otp_hexdigest(step)
650
651 def generate_otp_words(self, step: int) -> str:
652 """
653 Generates the OTP six words token for the given step.
654
655 :param step: The step to generate OTP for
656 :type step: int
657
658 :return: Six words (separated by single space) token for the given step
659 :rtype: str
660 """
661 return self.bytes_to_tokens(self._generate_otp_bytes(step))
662
663 def generate_otp_words_from_challenge(self, challenge: str) -> str:
664 """
665 Same as generate_otp_words, but it generates words from a challenge.
666
667 RFC-2289 states:
668 The challenge MUST be in a standard syntax so
669 that automated generators can recognize the challenge in context and
670 extract these parameters. The syntax of the challenge is:
671 otp-<algorithm identifier> <sequence integer> <seed>
672
673 :param challenge: The challenge string
674 :type challenge: str
675
676 :return: Six words token for the given challenge
677 :rtype: str
678 """
679 seed, hash_algo, step = self.get_tokens_from_challenge(challenge)
680 self._seed = self.validate_seed(seed)
681 self._hash_algo = self.validate_hash_algo(hash_algo)
682 return self.generate_otp_words(step)
683
684 def hexdigest_range(self, start: int = 499, stop: int = 0):
685 """
686 Returns an iterator that providing hexdigests corresponding to steps
687 from `start` to and including `stop`.
688
689 :param start: The start of the range (default: 499)
690 :type start: int
691
692 :param stop: The last step (default: 0)
693 :type stop: int
694
695 :return: Iterator
696 :rtype: generator
697 """
698 if not isinstance(start, int) and isinstance(stop, int):
699 raise OTPGeneratorException('Step value MUST be an int')
700 if start < stop:
701 raise OTPGeneratorException(
702 'Start value can not be lower than stop'
703 )
704 for step in range(start, stop - 1, -1):
705 yield self.generate_otp_hexdigest(step)
706
707 def words_range(self, start: int = 499, stop: int = 0):
708 """
709 Returns an iterator that providing the words corresponding to steps
710 from `start` to and including `stop`.
711
712 :param start: The start of the range (default: 499)
713 :type start: int
714
715 :param stop: The last step (default: 0)
716 :type stop: int
717
718 :return: Iterator
719 :rtype: generator
720 """
721 if not isinstance(start, int) and isinstance(stop, int):
722 raise OTPGeneratorException('Step value MUST be an int')
723 if start < stop:
724 raise OTPGeneratorException(
725 'Start value can not be lower than stop'
726 )
727 for step in range(start, stop - 1, -1):
728 yield self.generate_otp_words(step)
729
730 def _generate_otp_bytes(self, step: int) -> bytes:
731 """
732 Generates the OTP bytes for the given step.
733
734 :param step: The step to generate OTP for
735 :type step: int
736
737 :return: The digest bytes for the given step
738 :rtype: bytes
739 """
740 step = self.validate_step(step)
741 digest = b''
742 for _ in range(step + 1):
743 hash_obj = hashlib.new(self._hash_algo)
744 if not digest:
745 # 0 step
746 hash_obj.update(self._seed.lower().encode() + self._password)
747 else:
748 hash_obj.update(digest)
749 large_digest = hash_obj.digest()
750 if self._hash_algo == 'md5':
751 # md4 and md5 128bit -> 64bit folding
752 digest = self.strxor(large_digest[0:8], large_digest[8:])
753 elif self._hash_algo == 'sha1':
754 # sha1 160bit -> 64bit folding
755 digest = self.sha1_digest_folding(large_digest)
756 else:
757 raise OTPGeneratorException(
758 f'{self._hash_algo} is not supported by this module'
759 )
760 return digest