msr90- Documentation & Code Examples for the MSR90 Magnetic Strip Card Reader 💳 |
git clone git://git.acid.vegas/msr90.git |
Log | Files | Refs | Archive | README | LICENSE |
msr90.py (5360B)
1 #!/usr/bin/env python 2 # MSR90 Magnetic Stripe Reader - Developed by acidvegas in Python (https://git.acid.vegas/msr90) 3 4 ''' 5 Notes: 6 - Currently only supports reading VISA cards, will add support for Mastercard, License, Gift Cards, etc. 7 ''' 8 9 import getpass 10 import re 11 12 13 # Define ANSI color codes 14 YELLOW = '\033[93m' 15 CYAN = '\033[96m' 16 GRAY = '\033[90m' 17 RESET = '\033[0m' 18 19 20 def format_pan(pan: str) -> str: 21 ''' 22 Format the Primary Account Number (PAN) by grouping the digits in sets of 4 23 24 :param pan: The Primary Account Number (PAN) to format 25 ''' 26 27 return ' '.join(pan[i:i+4] for i in range(0, len(pan), 4)) 28 29 30 def format_exp_date(exp_date: str) -> str: 31 ''' 32 Format the expiration date to be MM/YY 33 34 :param exp_date: The expiration date to format 35 ''' 36 37 return exp_date[2:4] + '/' + exp_date[0:2] 38 39 40 def service_code_descriptions(digit: int, code: str) -> str: 41 ''' 42 Get the description for the service code digit 43 44 :param digit: The digit number (1, 2, or 3) 45 :param code: The code value for the digit 46 ''' 47 48 descriptions = { 49 '1': { 50 '1': 'International interchange OK', 51 '2': 'International interchange, use IC (chip) where feasible', 52 '5': 'National interchange only except under bilateral agreement', 53 '6': 'National interchange only, use IC (chip) where feasible', 54 '7': 'No interchange except under bilateral agreement (closed loop)', 55 '9': 'Test' 56 }, 57 '2': { 58 '0': 'Normal', 59 '2': 'Contact issuer via online means', 60 '4': 'Contact issuer via online means except under bilateral agreement' 61 }, 62 '3': { 63 '0': 'No restrictions, PIN required', 64 '1': 'No restrictions', 65 '2': 'Goods and services only (no cash)', 66 '3': 'ATM only, PIN required', 67 '4': 'Cash only', 68 '5': 'Goods and services only (no cash), PIN required', 69 '6': 'No restrictions, use PIN where feasible', 70 '7': 'Goods and services only (no cash), use PIN where feasible' 71 } 72 } 73 74 return descriptions[str(digit)].get(code, 'Unknown') 75 76 77 def parse_magnetic_stripe(data: str): 78 ''' 79 Parse the magnetic stripe data and print the results 80 81 :param data: The raw magnetic stripe data to parse 82 ''' 83 84 # https://en.wikipedia.org/wiki/ISO/IEC_7813#Programming 85 # ^%B([0-9]{1,19})\^([^\^]{2,26})\^([0-9]{4}|\^)([0-9]{3}|\^)([^\?]*)\?$ 86 # ^\;([0-9]{1,19})\=([0-9]{4}|\=)([0-9]{3}|\=)([^\?]*)\?$ 87 88 track1_match = re.search(r'^%([AB])(\d{1,19})\^([^\^]{2,26})\^(\d{4})(\d{3})([^\?]*?)\?(\w?)', data) 89 track2_match = re.search(r';(\d{1,19})=(\d{4})(\d{3})(.*?)\?$', data) 90 track3_match = re.search(r'(\+[^\?]+\?)', data) 91 92 if track1_match: 93 format_code, pan, name, exp_date, service_code, discretionary_data, lrc = track1_match.groups() 94 if format_code == 'A': 95 raise ValueError('Track 1 data is using format code A which is not supported') 96 formatted_pan = format_pan(pan) 97 formatted_exp_date = format_exp_date(exp_date) 98 desc1 = service_code_descriptions(1, service_code[0]) 99 desc2 = service_code_descriptions(2, service_code[1]) 100 desc3 = service_code_descriptions(3, service_code[2]) 101 print('Track 1 Data:') 102 print(f'{YELLOW}Format Code {GRAY}|{RESET} {format_code} - Financial cards (ISO/IEC 7813)') 103 print(f'{YELLOW}Primary Account Number (PAN) {GRAY}|{RESET} {formatted_pan}') 104 print(f'{YELLOW}Cardholder Name {GRAY}|{RESET} {name}') 105 print(f'{YELLOW}Expiration Date {GRAY}|{RESET} {formatted_exp_date}') 106 print(f'{YELLOW}Service Code Digit 1 {GRAY}|{RESET} {service_code[0]}: {desc1}') 107 print(f'{YELLOW}Service Code Digit 2 {GRAY}|{RESET} {service_code[1]}: {desc2}') 108 print(f'{YELLOW}Service Code Digit 3 {GRAY}|{RESET} {service_code[2]}: {desc3}') 109 print(f'{YELLOW}Discretionary Data {GRAY}|{RESET} {discretionary_data}') 110 print(f'{YELLOW}LRC (optional) {GRAY}|{RESET} {lrc}') 111 print(f'{YELLOW}Raw Track Data {GRAY}|{RESET} {track1_match.group(0)}') 112 else: 113 generic_track1 = re.search(r'(%[^?]+\?)', data) 114 if generic_track1: 115 print('Track 1 Data:') 116 print(f'{YELLOW}Raw Track Data {GRAY}|{RESET}' + generic_track1.group(1)) 117 else: 118 raise ValueError(f'Track 1 data is not found (data: {data})') 119 120 if track2_match: 121 pan, exp_date, service_code, discretionary_data = track2_match.groups() 122 formatted_pan = format_pan(pan) 123 formatted_exp_date = format_exp_date(exp_date) 124 print('Track 2 Data:') 125 print(f'{CYAN}Primary Account Number (PAN) {GRAY}|{RESET} {formatted_pan}') 126 print(f'{CYAN}Expiration Date {GRAY}|{RESET} {formatted_exp_date}') 127 print(f'{CYAN}Service Code {GRAY}|{RESET} {service_code}') 128 print(f'{CYAN}Discretionary Data {GRAY}|{RESET} {discretionary_data}') 129 print(f'{CYAN}Raw Track Data {GRAY}|{RESET} {track2_match.group(0)}') 130 else: 131 generic_track2 = re.search(r'(;[^\?]+\?)', data) 132 if generic_track2: 133 print('Track 2 Data:') 134 print(f'{CYAN}Raw Track Data {GRAY}|{RESET}' + generic_track2.group(1)) 135 else: 136 raise ValueError(f'Track 2 data is not found (data: {data})') 137 138 if track3_match: 139 print('Track 3 Data:') 140 print(f'{GRAY}Raw Track Data {GRAY}|{RESET} {track3_match.group(0)}') 141 else: 142 raise ValueError(f'Track 3 data is not found (data: {data})') 143 144 145 146 if __name__ == '__main__': 147 swipe_data = getpass.getpass(prompt='Please swipe the card: ') 148 parse_magnetic_stripe(swipe_data)