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