From 833e35de706463898a304e22bab300c76bab4928 Mon Sep 17 00:00:00 2001 From: Bullet64 Date: Sat, 10 Aug 2024 15:32:28 +0200 Subject: [PATCH] initial commit --- converter.py | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++ example.json | 55 ++++++++++++++++++++ import.txt | 1 + 3 files changed, 194 insertions(+) create mode 100644 converter.py create mode 100644 example.json create mode 100644 import.txt diff --git a/converter.py b/converter.py new file mode 100644 index 0000000..8f28db1 --- /dev/null +++ b/converter.py @@ -0,0 +1,138 @@ +############################################### +# Standard library imports +############################################### +import argparse +import uuid +import json +from urllib.parse import urlparse, parse_qs, unquote + + +############################################### +# Functions +############################################### +def generate_uuid(): + """ + Generates a unique identifier (UUID) and returns it as a string. + + :return: A string representation of the generated UUID. + """ + return str(uuid.uuid4()) + + +def parse_totp_url(totp_url): + """ + :param totp_url: The URL representing the TOTP (Time-based One-Time Password) authentication data. + :return: A dictionary containing the parsed TOTP information, including the issuer, account name, secret, algorithm, + digits, and period. + + This method takes a TOTP URL as input and parses it to extract the relevant information. It then returns a + dictionary containing the parsed TOTP information. + + The TOTP URL should be in the following format: + totp://issuer:account_name?secret=&algorithm=&digits=&period= + + Example usage: + totp_url = 'totp://MyIssuer:MyAccountName?secret=ABC123&algorithm=SHA256&digits=6&period=30' + parsed_info = parse_totp_url(totp_url) + # parsed_info will be {'issuer': 'MyIssuer', + 'account_name': 'MyAccountName', + 'secret': 'ABC123', + 'algorithm': 'SHA256', + 'digits': '6', + 'period': '30'} + """ + + # Parse the URL + parsed_url = urlparse(totp_url) + + # Extract the path and query components + path = parsed_url.path.strip('/') + query = parsed_url.query + + # URL-decode the entire path + decoded_path = unquote(path) + + # Split the path into issuer and account name + path_parts = decoded_path.split(':', 1) + issuer = path_parts[0] + account_name = path_parts[1] if len(path_parts) > 1 else '' + + query_params = parse_qs(query) + secret = query_params.get('secret', [''])[0] + algorithm = query_params.get('algorithm', [''])[0] + digits = query_params.get('digits', [''])[0] + period = query_params.get('period', [''])[0] + + return { + 'issuer': issuer, + 'account_name': account_name, + 'secret': secret, + 'algorithm': algorithm, + 'digits': digits, + 'period': period + } + + +############################################### +# Usage: python converter.py -i import.txt -o output.json +############################################### +def main(input_file, output_file): + """ + :param input_file: The path to the input file containing TOTP URLs. + :param output_file: The path to the output file where the generated JSON will be written. + :return: None + + Reads TOTP URLs from the input file, parses them, and generates a JSON file with the parsed information. + The generated JSON file includes entries for each parsed URL, with each entry having a generated UUID, + a login with TOTP and username fields, a name field extracted from the issuer, and a type field set to 1. + The final JSON object is written to the output file. + Additionally, the number of generated entries and the output file path are printed to the console. + """ + # List to hold all entries + entries = [] + + with open(input_file, 'r') as file: + for line in file: + totp = line.strip() + + if not totp: + continue # Skip empty lines + + # Parse the URL + parsed_data = parse_totp_url(totp) + + # Create a new entry with a generated UUID + entry = { + "favorite": False, + "id": generate_uuid(), + "login": { + "totp": totp, + "username": parsed_data['account_name'] + }, + "name": parsed_data['issuer'], + "type": 1 + } + + entries.append(entry) + + # Construct the final JSON object + data = { + "encrypted": False, + "items": entries + } + + # Output JSON to a file + with open(output_file, 'w') as json_file: + json.dump(data, json_file, indent=4) + + print(f"Total entries: {len(entries)}") + print(f"Output written to {output_file}") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Parse TOTP URLs and export to JSON.') + parser.add_argument('-i', '--input', required=True, help='Input file containing TOTP URLs') + parser.add_argument('-o', '--output', required=True, help='Output JSON file') + + args = parser.parse_args() + main(args.input, args.output) \ No newline at end of file diff --git a/example.json b/example.json new file mode 100644 index 0000000..7cec1e8 --- /dev/null +++ b/example.json @@ -0,0 +1,55 @@ +{ + "encrypted": false, + "items": [ + { + "favorite": false, + "id": "52A4DFB0-F19E-4C9D-82A1-BBEE95BBEF81", + "login": { + "totp": "otpauth://totp/Amazon:alice@bitwarden.com?secret=IIO5SCP3766LMSAB5HJCQPNDCCNAZ532&issuer=Amazon&algorithm=SHA1&digits=6&period=30", + "username": "alice@bitwarden.com" + }, + "name": "Amazon", + "type": 1 + }, + { + "favorite": false, + "id": "DC81A830-ED98-4F45-9B73-B147E40134AB", + "login": { + "totp": "otpauth://totp/Apple:alice@bitwarden.com?secret=IIO5SCQ3766LMSBB5HJCQPNDCCNAZ532&issuer=Apple&algorithm=SHA1&digits=6&period=30", + "username": "alice@bitwarden.com" + }, + "name": "Apple", + "type": 1 + }, + { + "favorite": false, + "id": "4EF44090-4B6A-4E98-A94C-CF7B0F2CC35D", + "login": { + "totp": "otpauth://totp/Bitwarden:alice@bitwarden.com?secret=IIO5SCP3766LMSBB5HJCQPNDCCNAZ532&issuer=Bitwarden&algorithm=SHA1&digits=6&period=30", + "username": "alice@bitwarden.com" + }, + "name": "Bitwarden", + "type": 1 + }, + { + "favorite": false, + "id": "59B09168-502A-4D38-B218-FACF66E6A365", + "login": { + "totp": "otpauth://totp/Microsoft:alice@bitwarden.com?secret=IIO5SCP3766LMSBB5HJCHPNDCCNAZ532&issuer=Microsoft&algorithm=SHA1&digits=6&period=30", + "username": "alice@bitwarden.com" + }, + "name": "Microsoft", + "type": 1 + }, + { + "favorite": false, + "id": "789F095B-95B2-4816-A5F7-01095116C10E", + "login": { + "totp": "otpauth://totp/Reddit:alice@bitwarden.com?secret=IIO5SCP3766LNSBB5HJCQPNDCCNAZ532&issuer=Reddit&algorithm=SHA1&digits=6&period=30", + "username": "alice@bitwarden.com" + }, + "name": "Reddit", + "type": 1 + } + ] +} \ No newline at end of file diff --git a/import.txt b/import.txt new file mode 100644 index 0000000..55db288 --- /dev/null +++ b/import.txt @@ -0,0 +1 @@ +otpauth://totp/Radforum%20D%C3%BCsseldorf%3Afrankm%40radforum-duesseldorf.de?secret=&algorithm=SHA1&digits=6&period=30 \ No newline at end of file