diff --git a/msr90.py b/msr90.py index a04be58..a2c8b92 100644 --- a/msr90.py +++ b/msr90.py @@ -7,162 +7,162 @@ import sys def format_pan(pan: str) -> str: - ''' - Format the Primary Account Number (PAN) by grouping the digits in sets of 4 - - :param pan: The Primary Account Number (PAN) to format - ''' + ''' + Format the Primary Account Number (PAN) by grouping the digits in sets of 4 - return ' '.join(pan[i:i+4] for i in range(0, len(pan), 4)) + :param pan: The Primary Account Number (PAN) to format + ''' + + return ' '.join(pan[i:i+4] for i in range(0, len(pan), 4)) def format_exp_date(exp_date: str) -> str: - ''' - Format the expiration date by to be MM/YY - - :param exp_date: The expiration date to format''' + ''' + Format the expiration date by to be MM/YY - return exp_date[2:4] + '/' + exp_date[0:2] + :param exp_date: The expiration date to format''' + + return exp_date[2:4] + '/' + exp_date[0:2] def service_code_descriptions(digit, code): - ''' - Get the description for the service code digit - - :param digit: The digit number (1, 2, or 3) - :param code: The code value for the digit - ''' + ''' + Get the description for the service code digit - descriptions = { - '1': { - '1': 'International interchange OK', - '2': 'International interchange, use IC (chip) where feasible', - '5': 'National interchange only except under bilateral agreement', - '6': 'National interchange only, use IC (chip) where feasible', - '7': 'No interchange except under bilateral agreement (closed loop)', - '9': 'Test' - }, - '2': { - '0': 'Normal', - '2': 'Contact issuer via online means', - '4': 'Contact issuer via online means except under bilateral agreement' - }, - '3': { - '0': 'No restrictions, PIN required', - '1': 'No restrictions', - '2': 'Goods and services only (no cash)', - '3': 'ATM only, PIN required', - '4': 'Cash only', - '5': 'Goods and services only (no cash), PIN required', - '6': 'No restrictions, use PIN where feasible', - '7': 'Goods and services only (no cash), use PIN where feasible' - } - } + :param digit: The digit number (1, 2, or 3) + :param code: The code value for the digit + ''' - return descriptions[str(digit)].get(code, 'Unknown') + descriptions = { + '1': { + '1': 'International interchange OK', + '2': 'International interchange, use IC (chip) where feasible', + '5': 'National interchange only except under bilateral agreement', + '6': 'National interchange only, use IC (chip) where feasible', + '7': 'No interchange except under bilateral agreement (closed loop)', + '9': 'Test' + }, + '2': { + '0': 'Normal', + '2': 'Contact issuer via online means', + '4': 'Contact issuer via online means except under bilateral agreement' + }, + '3': { + '0': 'No restrictions, PIN required', + '1': 'No restrictions', + '2': 'Goods and services only (no cash)', + '3': 'ATM only, PIN required', + '4': 'Cash only', + '5': 'Goods and services only (no cash), PIN required', + '6': 'No restrictions, use PIN where feasible', + '7': 'Goods and services only (no cash), use PIN where feasible' + } + } + + return descriptions[str(digit)].get(code, 'Unknown') def parse_magnetic_stripe(data: str): - ''' - Parse the magnetic stripe data and print the results - - :param data: The raw magnetic stripe data to parse - ''' + ''' + Parse the magnetic stripe data and print the results - # Patterns for specific track data parsing - track1_pattern = r'^%([AB])(\d{1,19})\^([^\^]{2,26})\^(\d{4})(\d{3})([^\?]*?)\?(\w?)' - track2_pattern = r';(\d{1,19})=(\d{4})(\d{3})(.*?)\?$' + :param data: The raw magnetic stripe data to parse + ''' - # Generic patterns to capture raw data if specific parsing fails - generic_track1_pattern = r'(%[AB][^\?]+\?)' - generic_track2_pattern = r'(;[^\?]+\?)' - generic_track3_pattern = r'(\+[^\?]+\?)' + # Patterns for specific track data parsing + track1_pattern = r'^%([AB])(\d{1,19})\^([^\^]{2,26})\^(\d{4})(\d{3})([^\?]*?)\?(\w?)' + track2_pattern = r';(\d{1,19})=(\d{4})(\d{3})(.*?)\?$' - # Attempt to match the track data - track1_match = re.search(track1_pattern, data) - track2_match = re.search(track2_pattern, data) - track3_match = re.search(generic_track3_pattern, data) + # Generic patterns to capture raw data if specific parsing fails + generic_track1_pattern = r'(%[AB][^\?]+\?)' + generic_track2_pattern = r'(;[^\?]+\?)' + generic_track3_pattern = r'(\+[^\?]+\?)' - # Track 1 specific parsing - if track1_match: - format_code, pan, name, exp_date, service_code, discretionary_data, lrc = track1_match.groups() - if format_code == 'A': - print('Error: Unsupported format code \'A\'. Exiting.') - sys.exit(1) - formatted_pan = format_pan(pan) - formatted_exp_date = format_exp_date(exp_date) - desc1 = service_code_descriptions(1, service_code[0]) - desc2 = service_code_descriptions(2, service_code[1]) - desc3 = service_code_descriptions(3, service_code[2]) - print_fields('Track 1 Data', [ - ('Format Code', f'{format_code} - Financial cards (ISO/IEC 7813)'), - ('Primary Account Number (PAN)', formatted_pan), - ('Cardholder Name', name), - ('Expiration Date', formatted_exp_date), - ('Service Code Digit 1', f'{service_code[0]}: {desc1}'), - ('Service Code Digit 2', f'{service_code[1]}: {desc2}'), - ('Service Code Digit 3', f'{service_code[2]}: {desc3}'), - ('Discretionary Data', discretionary_data), - ('LRC (optional)', lrc), - ('Raw Track Data', track1_match.group(0)) - ], '\033[93m') + # Attempt to match the track data + track1_match = re.search(track1_pattern, data) + track2_match = re.search(track2_pattern, data) + track3_match = re.search(generic_track3_pattern, data) - # Fallback generic track 1 - elif re.search(generic_track1_pattern, data): - print('Track 1 Data:') - print('Raw: ' + re.search(generic_track1_pattern, data).group(1)) + # Track 1 specific parsing + if track1_match: + format_code, pan, name, exp_date, service_code, discretionary_data, lrc = track1_match.groups() + if format_code == 'A': + print('Error: Unsupported format code \'A\'. Exiting.') + sys.exit(1) + formatted_pan = format_pan(pan) + formatted_exp_date = format_exp_date(exp_date) + desc1 = service_code_descriptions(1, service_code[0]) + desc2 = service_code_descriptions(2, service_code[1]) + desc3 = service_code_descriptions(3, service_code[2]) + print_fields('Track 1 Data', [ + ('Format Code', f'{format_code} - Financial cards (ISO/IEC 7813)'), + ('Primary Account Number (PAN)', formatted_pan), + ('Cardholder Name', name), + ('Expiration Date', formatted_exp_date), + ('Service Code Digit 1', f'{service_code[0]}: {desc1}'), + ('Service Code Digit 2', f'{service_code[1]}: {desc2}'), + ('Service Code Digit 3', f'{service_code[2]}: {desc3}'), + ('Discretionary Data', discretionary_data), + ('LRC (optional)', lrc), + ('Raw Track Data', track1_match.group(0)) + ], '\033[93m') - print('\n') + # Fallback generic track 1 + elif re.search(generic_track1_pattern, data): + print('Track 1 Data:') + print('Raw: ' + re.search(generic_track1_pattern, data).group(1)) - # Track 2 specific parsing - if track2_match: - pan, exp_date, service_code, discretionary_data = track2_match.groups() - formatted_pan = format_pan(pan) - formatted_exp_date = format_exp_date(exp_date) - print_fields('Track 2 Data', [ - ('Primary Account Number (PAN)', formatted_pan), - ('Expiration Date', formatted_exp_date), - ('Service Code', service_code), - ('Discretionary Data', discretionary_data), - ('Raw Track Data', track2_match.group(0)) - ], '\033[96m') + print('\n') - # Fallback generic track 2 - elif re.search(generic_track2_pattern, data): - print('Track 2 Data:') - print('Raw: ' + re.search(generic_track2_pattern, data).group(1)) + # Track 2 specific parsing + if track2_match: + pan, exp_date, service_code, discretionary_data = track2_match.groups() + formatted_pan = format_pan(pan) + formatted_exp_date = format_exp_date(exp_date) + print_fields('Track 2 Data', [ + ('Primary Account Number (PAN)', formatted_pan), + ('Expiration Date', formatted_exp_date), + ('Service Code', service_code), + ('Discretionary Data', discretionary_data), + ('Raw Track Data', track2_match.group(0)) + ], '\033[96m') - print('\n') + # Fallback generic track 2 + elif re.search(generic_track2_pattern, data): + print('Track 2 Data:') + print('Raw: ' + re.search(generic_track2_pattern, data).group(1)) - # Track 3 generic data if found - if track3_match: - print('Track 3 Data:') - print('Raw: ' + track3_match.group(1)) - else: - print('Track 3 Data: No data found.') + print('\n') + + # Track 3 generic data if found + if track3_match: + print('Track 3 Data:') + print('Raw: ' + track3_match.group(1)) + else: + print('Track 3 Data: No data found.') def print_fields(title: str, fields: list, color_code: str): - ''' - Print the fields in a formatted table - - :param title: The title of the table - :param fields: The fields to print - :param color_code: The color code to use for the table - ''' + ''' + Print the fields in a formatted table - max_len = max(len(name) for name, _ in fields) + :param title: The title of the table + :param fields: The fields to print + :param color_code: The color code to use for the table + ''' - print(title) + max_len = max(len(name) for name, _ in fields) - for name, value in fields: - print(color_code + name.ljust(max_len) + '\033[90m | \033[0m' + value) + print(title) + + for name, value in fields: + print(color_code + name.ljust(max_len) + '\033[90m | \033[0m' + value) if __name__ == '__main__': - try: - swipe_data = getpass.getpass(prompt='Please swipe the card: ') - parse_magnetic_stripe(swipe_data) - except Exception as e: - print('Error:', e) + try: + swipe_data = getpass.getpass(prompt='Please swipe the card: ') + parse_magnetic_stripe(swipe_data) + except Exception as e: + print('Error:', e)