diff --git 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
+
+ :param exp_date: The expiration date to format'''
- return exp_date[2:4] + '/' + exp_date[0:2]
+ 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
- '''
-
- 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')
+ '''
+ 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
+ '''
+
+ 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
- '''
-
- # 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})(.*?)\?$'
-
- # Generic patterns to capture raw data if specific parsing fails
- generic_track1_pattern = r'(%[AB][^\?]+\?)'
- generic_track2_pattern = r'(;[^\?]+\?)'
- generic_track3_pattern = r'(\+[^\?]+\?)'
-
- # 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)
-
- # 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')
-
- # 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))
-
- print('\n')
-
- # 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')
-
- # 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))
-
- 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.')
+ '''
+ Parse the magnetic stripe data and print the results
+
+ :param data: The raw magnetic stripe data to parse
+ '''
+
+ # 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})(.*?)\?$'
+
+ # Generic patterns to capture raw data if specific parsing fails
+ generic_track1_pattern = r'(%[AB][^\?]+\?)'
+ generic_track2_pattern = r'(;[^\?]+\?)'
+ generic_track3_pattern = r'(\+[^\?]+\?)'
+
+ # 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)
+
+ # 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')
+
+ # 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))
+
+ print('\n')
+
+ # 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')
+
+ # 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))
+
+ 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
+
+ :param title: The title of the table
+ :param fields: The fields to print
+ :param color_code: The color code to use for the table
+ '''
- max_len = max(len(name) for name, _ in fields)
+ max_len = max(len(name) for name, _ in fields)
- print(title)
+ print(title)
- for name, value in fields:
- print(color_code + name.ljust(max_len) + '\033[90m | \033[0m' + value)
+ 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)
|