Created
March 12, 2022 10:37
-
-
Save svareille/fda49baf5f3e15b5c88e25560aeb2822 to your computer and use it in GitHub Desktop.
Python script to display raw private PGP keys for importing in OnlyKey
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
""" | |
Parse the private keys out of OpenPGP keys (ed25519 or RSA). | |
It will display the raw subkeys. Only run this on a secure trusted system. | |
""" | |
import sys | |
import argparse | |
import fileinput | |
import getpass | |
import pgpy | |
from Crypto.Util.number import long_to_bytes | |
BLACK = "\033[0;30m" | |
RED = "\033[0;31m" | |
GREEN = "\033[0;32m" | |
BLUE = "\033[0;34m" | |
END = "\033[0m" | |
def get_key_type(key:pgpy.PGPKey): | |
""" | |
Get the key's type. | |
Parameters | |
---------- | |
key : pgpy.PGPKey | |
The key from which to get the type. | |
Returns | |
------- | |
str | |
The type of the key. One of RSA, DSA, ElGamal, ECDSA, EdDSA or ECDH. | |
""" | |
if isinstance(key._key.keymaterial, pgpy.packet.fields.RSAPriv): | |
return 'RSA' | |
elif isinstance(key._key.keymaterial, pgpy.packet.fields.DSAPriv): | |
return 'DSA' | |
elif isinstance(key._key.keymaterial, pgpy.packet.fields.ElGPriv): | |
return 'ElGamal' | |
elif isinstance(key._key.keymaterial, pgpy.packet.fields.ECDSAPriv): | |
return 'ECDSA' | |
elif isinstance(key._key.keymaterial, pgpy.packet.fields.EdDSAPriv): | |
return 'EdDSA' | |
elif isinstance(key._key.keymaterial, pgpy.packet.fields.ECDHPriv): | |
return 'ECDH' | |
return '' | |
def get_key_value(key:pgpy.PGPKey): | |
""" | |
Get the private key's value and size. | |
Parameters | |
---------- | |
key : pgpy.PGPKey | |
The private key. | |
Raises | |
------ | |
NotImplementedError | |
Key type not supported for DSA and ElGamal. | |
Returns | |
------- | |
value : str | |
hex string representing the raw private key. | |
size : int | |
The size of the raw private key in bits. | |
""" | |
key_type = get_key_type(key) | |
if key_type == 'RSA': | |
p = long_to_bytes(key._key.keymaterial.p) | |
q = long_to_bytes(key._key.keymaterial.q) | |
value = "".join([f"{c:02x}" for c in p]) +\ | |
"".join([f"{c:02x}" for c in q]) | |
size = (len(p) + len(q)) * 8 | |
elif key_type in ('ECDSA', 'EdDSA', 'ECDH'): | |
s = long_to_bytes(key._key.keymaterial.s) | |
value = "".join([f"{c:02x}" for c in s]) | |
size = len(s)*8 | |
else: | |
raise NotImplementedError(f"Get value from {key_type} key is not " | |
f"supported") | |
return (value, size) | |
def get_key_flags(key:pgpy.PGPKey): | |
""" | |
Get the key's usage flags. | |
Parameters | |
---------- | |
key : pgpy.PGPKey | |
The key. | |
Returns | |
------- | |
str | |
Usage flags of the key. | |
Flags: | |
C : Certification | |
S : Signature | |
E : Encryption | |
A : Authentication | |
""" | |
flags = [] | |
strs = {pgpy.constants.KeyFlags.Certify : 'C', | |
pgpy.constants.KeyFlags.Sign : 'S', | |
pgpy.constants.KeyFlags.EncryptCommunications : 'E', | |
pgpy.constants.KeyFlags.Authentication : 'A'} | |
for sig in key.self_signatures: | |
if not sig.is_expired: | |
flags += sig.key_flags | |
return "".join(strs.get(flag, '') for flag in flags) | |
if __name__ == '__main__': | |
parser = argparse.ArgumentParser( | |
description='Extract secret subkeys from a OpenPGP key.\n\n' | |
'This script will display the raw subkeys. ' | |
'Only run this on a secure trusted\n' | |
'system.', | |
epilog='''Example: | |
gpg --export-secret-keys -a keyid | ./PGPparseprivate.py - | |
./PGPparseprivate.py ~/mykey.asc --no-expired | |
''', | |
formatter_class=argparse.RawDescriptionHelpFormatter) | |
parser.add_argument('keyfile', type=str, | |
help="path to the secret PEM-encoded key file, or " | |
"'-' for stdin.'") | |
parser.add_argument('--no-expired', action='store_true', | |
help='do not show expired subkeys') | |
parser.add_argument('--no-colors', action='store_true', | |
help='do not output with colors. Usefull for piping ' | |
'output and use in scripts.') | |
parser.add_argument('-p', '--passphrase', type=str, | |
help="the passphrase of the key. Don't forget bash's " | |
"history keeps everything !") | |
args = parser.parse_args() | |
if args.no_colors: | |
BLACK = "" | |
RED = "" | |
GREEN = "" | |
BLUE = "" | |
END = "" | |
# Parse input - either a file or stdin - for private key block | |
armored_key = None | |
with fileinput.input(files=args.keyfile) as keyfile: | |
for line in keyfile: | |
if line == "-----BEGIN PGP PRIVATE KEY BLOCK-----\n": | |
armored_key = line | |
elif line == "-----END PGP PRIVATE KEY BLOCK-----\n": | |
armored_key += line | |
break | |
elif armored_key is not None: | |
armored_key += line | |
primary_key, _ = pgpy.PGPKey.from_blob(armored_key) | |
assert primary_key.is_protected | |
assert primary_key.is_unlocked is False | |
try: | |
password = args.passphrase if args.passphrase else getpass.getpass( | |
"Enter key password: ") | |
if primary_key._key.keymaterial.encbytes == b'': | |
print("No secret primary key") | |
else: | |
with primary_key.unlock(password): | |
# primary_key is now unlocked | |
assert primary_key.is_unlocked | |
print('primary key is now unlocked') | |
print('Load these raw key values to OnlyKey by using the ' | |
'OnlyKey App --> Advanced -> Add Private Key') | |
key_value, key_size = get_key_value(primary_key) | |
print(f'primary key id: {primary_key.fingerprint.keyid}') | |
print(f'primary key type: {get_key_type(primary_key)}') | |
print(f'primary key usage: {get_key_flags(primary_key)}') | |
print(f'{GREEN}primary key value:{END} {key_value}') | |
print(f'primary key size: {key_size} bits') | |
print("Extracting subkeys...") | |
for key_id, subkey in primary_key.subkeys.items(): | |
with subkey.unlock(password): | |
assert subkey.is_unlocked | |
if args.no_expired and subkey.is_expired: | |
continue | |
print(f'subkey id: {key_id}') | |
print(f'subkey type: {get_key_type(subkey)}') | |
print(f'subkey usage: {get_key_flags(subkey)}') | |
if subkey.is_expired: | |
print(f'{RED}/!\\ subkey has expired !{END}') | |
key_value, key_size = get_key_value(subkey) | |
print(f'{GREEN}subkey value:{END} {key_value}') | |
print(f'subkey size: {key_size} bits') | |
print() | |
except pgpy.errors.PGPDecryptionError: | |
print("Wrong password") | |
sys.exit(1) | |
# primary_key is no longer unlocked | |
assert primary_key.is_unlocked is False |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment