diff options
| author | Simeon Simeonov | 2020-03-25 15:31:57 +0100 |
|---|---|---|
| committer | Simeon Simeonov | 2020-03-25 15:31:57 +0100 |
| commit | 23b4ede93f8cb202ff0e85cef45921928ff6a94f (patch) | |
| tree | ec34064e1fd82404f75ee2a10e219f041fd37f6d | |
Initial commit with working generator and corresponding tests
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | README.md | 6 | ||||
| -rw-r--r-- | otp2289/__init__.py | 28 | ||||
| -rw-r--r-- | otp2289/generator.py | 538 | ||||
| -rw-r--r-- | tests/test_generator.py | 115 |
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 | |||
| 3 | pyotp2289 is a pure Python 3 implementation of RFC-2289. | ||
| 4 | |||
| 5 | It requires no additional libraries. | ||
| 6 | pytest 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 | |||
| 4 | from .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 | |||
| 15 | def 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 | |||
| 23 | VERSION = 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 | """ | ||
| 4 | A pure Python implementation of the RFC-2289 OTP generator | ||
| 5 | """ | ||
| 6 | |||
| 7 | import binascii | ||
| 8 | import hashlib | ||
| 9 | import string | ||
| 10 | |||
| 11 | |||
| 12 | OTP_ALGO_MD5 = 1 | ||
| 13 | OTP_ALGO_SHA1 = 2 | ||
| 14 | |||
| 15 | # the tokens are defined in https://tools.ietf.org/html/rfc2289 # | ||
| 16 | RFC1760_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 | |||
| 278 | class OTPGeneratorException(Exception): | ||
| 279 | """OTPGeneratorException class""" | ||
| 280 | |||
| 281 | |||
| 282 | class 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 | |||
| 4 | import otp2289 | ||
| 5 | |||
| 6 | |||
| 7 | def 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 | |||
| 64 | def 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' | ||
