Skip to content

Instantly share code, notes, and snippets.

@svareille
Created March 12, 2022 10:37
Show Gist options
  • Save svareille/fda49baf5f3e15b5c88e25560aeb2822 to your computer and use it in GitHub Desktop.
Save svareille/fda49baf5f3e15b5c88e25560aeb2822 to your computer and use it in GitHub Desktop.
Python script to display raw private PGP keys for importing in OnlyKey
#!/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