· 4 years ago · Apr 26, 2021, 12:40 AM
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4# keychanger.py - a script to create PGP keys with fingerprints begining
5# with a custom hex string.
6# Requires GnuPG 2.2.x or later and Python 3.4 or later
7#
8#
9# Max number of iterations to try before giving up:
10limit = 1209600
11
12# If the script fails to find a match for your target, try increasing
13# this number. Note that this is equivalent to the maximum number of
14# seconds to subtract from the timestamp. The default of 1209600 is 2
15# weeks and should be sufficient to find any 4 character or less hex
16# string. One year is 31557600 seconds. Longer string lengths require a
17# greater number of iterations.
18
19########################################################################
20# ! ! ! DON'T CHANGE ANYTHING FROM THIS POINT ON ! ! ! #
21# ! ! ! UNLESS YOU REALLY KNOW WHAT YOU'RE DOING. ! ! ! #
22########################################################################
23
24# Python version check (3.4+ required for cleanup function)
25import sys
26pv = sys.version_info >= (3, 4)
27def cleanup(pv,public_key,secret_key):
28 if pv == False:
29 print("\nPlease manually delete these files:\n"+public_key+"\n"+secret_key)
30 sys.exit()
31 import pathlib
32 pathlib.Path(public_key).unlink()
33 pathlib.Path(secret_key).unlink()
34 sys.exit()
35
36import hashlib, time, subprocess, uuid
37
38# Algorithm strings recognized by GnuPG
39algos = {'rsa', 'rsa1024', 'rsa2048', 'rsa3072', 'rsa4096', 'dsa', 'dsa1024', 'dsa2048', 'ed25519', 'nistp256', 'nistp384', 'nistp521', 'brainpoolP256r1', 'brainpoolP384r1', 'brainpoolP512r1', 'secp256k1'}
40
41# Message prompts for user input
42P = ["\nEnter the target hex string you want the key fingerprint to start with.\nIf you want to match a string longer than 4 characters, you'll need to increase to iteration 'limit' value in the script.\n\nEnter hex string target> ","\nChoose the algorithm for the signing key. Valid signing key algorithms are:\n rsa rsa1024 rsa2048 rsa3072 rsa4096\n dsa dsa1024 dsa2048 dsa3072\nGnuPG also allows elliptic curve key algorithms:\n ed25519\n nistp256 nistp384 nistp521\n brainpoolP256r1 brainpoolP384r1 brainpoolP512r1\n secp256k1\n(note: some elliptic curve algorithms might not be available in your version of GnuPG)\n\nEnter key algorithm> ", "\n * * * * * NOTICE * * * * *\n GnuPG *will* prompt you 3 times to confirm key deletion.\n Enter y at each of these prompts.\n If a Pinentry window pups up, the passphrase is '' (two single quotes).\n\n Press enter to continue..."]
43
44def hexcheck(target):
45 digits = '0123456789abcdef'
46 target = target.lower()
47 return set(list(target)).issubset(list(digits))
48
49# Prompt user for target hex string
50target = input(P[0])
51while hexcheck(target) == False:
52 print("String contains non-hex digits. Try again.")
53 target = input(P[0])
54target = target.lower()
55
56# Prompt user for key algorithm
57algo = input(P[1])
58while algo not in algos:
59 print("Algorithm not in list. Try again.\n")
60 algo = input(P[1])
61
62# Set user ID and generated file names to random strings.
63# This is to ensure none of them already exist
64user_id = str(uuid.uuid4())
65public_key = str(uuid.uuid4())
66secret_key = str(uuid.uuid4())
67
68# Inform user of GnuPG behavior during script execution
69input(P[2])
70
71# Commands to generate, export, then delete the generated keys
72cmd1 = ["gpg --quiet --batch --pinentry-mode loopback --passphrase '' --quick-gen-key "+user_id+" "+algo, "gpg --quiet -o "+public_key+" --export "+user_id,"gpg --quiet -o "+secret_key+" --export-secret-keys "+user_id, "gpg --quiet --yes --delete-secret-and-public-keys "+user_id]
73for c in cmd1:
74 subprocess.call(c, shell=True)
75
76
77# Functions to read gpg exported key data and list individual packets
78def header_info(x):
79 if x[0]&64 == 64:
80 tag = x[0]&63
81 if x[1] < 192: [hlen,plen] = [2,x[1]]
82 elif x[1] <= 223: [hlen,plen] = [3,((x[1] - 192) << 8) + x[2] + 192]
83 elif x[1] == 255: [hlen,plen] = [5,int.from_bytes(x[2:6],byteorder='big',signed=False)]
84 else:
85 tag = (x[0]&60)>>2
86 if x[0]&3 == 0: [hlen,plen] = [2,x[1]]
87 elif x[0]&3 == 1: [hlen,plen] = [3,int.from_bytes(x[1:3],byteorder='big',signed=False)]
88 elif x[0]&3 == 2: [hlen,plen] = [5,int.from_bytes(x[1:5],byteorder='big',signed=False)]
89 return [tag, hlen, plen]
90
91def splitpackets(x):
92 packets = []
93 i = 0
94 while i < (len(x)-1):
95 h = header_info(x[i:i+5])
96 tag = h[0]
97 packet = x[i:i+h[1]+h[2]]
98 i += (h[1]+h[2])
99 packets.append([tag,packet])
100 return packets
101
102
103# Rewrite secret_key file without signature packet
104with open(secret_key, 'rb') as f:
105 key = bytes(f.read())
106key = splitpackets(key)
107key = b''.join([x[1] for x in key if x[0] in {5,13}])
108with open(secret_key, 'wb') as f:
109 f.write(key)
110
111# Get public_key packet
112with open(public_key,'rb') as f:
113 key = bytes(f.read())
114
115# Get public_key header and packet length.
116[hlen,plen] = header_info(key[0:5])[1:3]
117
118# Format header for fingerprinting.
119h = b'\x99'+plen.to_bytes(2,byteorder='big',signed=False)
120
121# Create initial input for SHA1
122key = bytearray(h+key[hlen:hlen+plen])
123
124# Extract current timestamp
125timestamp = int.from_bytes(key[4:8],byteorder='big',signed=True)
126
127# Set length of string to match
128L = len(target)
129
130# Set the initial hash value and iteration counter.
131fingerprint = hashlib.sha1()
132fingerprint.update(key)
133current = fingerprint.hexdigest()[0:L]
134i = 0
135
136# Timer to measure duration of search.
137start = time.time()
138
139# Begin search for fingerprint string match
140while current != target and i < limit:
141 fingerprint = hashlib.sha1()
142 timestamp -= 1
143 i += 1
144 key[4:8] = timestamp.to_bytes(4,byteorder='big',signed=True)
145 fingerprint.update(key)
146 current = fingerprint.hexdigest()[0:L]
147
148# Stop time for timer.
149end = time.time()
150
151if current == target:
152 # Success :)
153 with open(secret_key,'r+b') as f:
154 # Go to start of timestamp data in exported secret key packet
155 f.seek(hlen+1)
156 # Overwrite timestamp with the one found in the while loop
157 f.write(bytes(key[4:8]))
158 print("\nMatch found in "+str(i)+" iterations. Total time: "+str(end-start)+" seconds.")
159 input("Press enter to continue")
160 print("\nKey fingerprint will be "+fingerprint.hexdigest().upper())
161 answer = input("import this key to GnuPG (y/n)?")
162 if answer.lower() == 'y':
163 pass
164 else:
165 print("Exiting")
166 cleanup(pv,public_key,secret_key)
167 # List of commands to import key and display fingerprint
168 cmd2 = ["gpg --quiet --allow-non-selfsigned-uid --import "+secret_key, "gpg -K "+user_id]
169 for c in cmd2:
170 subprocess.call(c, shell=True)
171 print("\nYOU NEED TO EDIT THIS KEY.\nEnter 'gpg --edit-key "+user_id+"' then use the following commands after the 'gpg>' prompt in the order shown:\n gpg> adduid ...add a new user ID by following the prompts.\n gpg> 1 ...selects the '"+user_id+"' user id\n gpg> deluid ...delete the selected user ID then confirm.\n gpg> change-usage ...toggle usage options until it shows 'Current allowed actions: Sign Certify' then enter Q.\n gpg> passwd ...set a password\n gpg> addkey ...follow prompts to add encryption subkey\n gpg> save ...save the changes made in editing mode")
172 cleanup(pv,public_key,secret_key)
173else:
174 # Failure :(
175 print("Failed to match "+str(target)+" in "+str(i)+" iterations. Total time: "+str(end-start)+" seconds")
176 cleanup(pv,public_key,secret_key)
177