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)