Skip to content

Instantly share code, notes, and snippets.

@tylerneylon
Created April 25, 2022 05:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tylerneylon/2c0bdc6091d2ee613984c316c4615ae2 to your computer and use it in GitHub Desktop.
Save tylerneylon/2c0bdc6091d2ee613984c316c4615ae2 to your computer and use it in GitHub Desktop.
Relatively simple Python to decode a PEM private key file (traditional file format).
#!/usr/bin/env python3
# coding: utf-8
"""
read_keyfile.py
Usage:
read_keyfile.py <key_file>
This file can read private PEM key files generated in the traditional file
format. You can generate files like this by using the `-m pem` flag with the
`ssh-keygen` tool when you create your keys.
Handy references:
* https://stackoverflow.com/questions/5355046/where-is-the-pem-file-format-specified
* https://www.rfc-editor.org/rfc/rfc7468
* https://www.rfc-editor.org/rfc/rfc5208
* https://www.rfc-editor.org/rfc/rfc5958
* https://en.wikipedia.org/wiki/X.690
"""
# ______________________________________________________________________
# Imports
import base64
import sys
# ______________________________________________________________________
# Globals
type_names = {
2: 'integer',
16: 'string'
}
# My source on these names is:
# * https://www.rfc-editor.org/rfc/rfc5208
# * reverse engineering from output of `openssl rsa ...` on pem files
field_names = [
'encoding version',
'modulus',
'publicExponent',
'privateExponent',
'prime1',
'prime2',
'exponent1',
'exponent2',
'coefficient'
]
field_num = 0
# ______________________________________________________________________
# Functions
def decode_b64_from_keyfile(filename):
with open(filename) as f:
lines = [line.strip() for line in f]
b64_encoded_data = ''.join(lines[1:-1]) # Ignore the header/footer.
return base64.b64decode(b64_encoded_data)
def get_length(data, index):
if not (data[index] >> 7):
# This is a short-form indication of length.
return index + 1, data[index]
# This is a long-form indication of length.
stop_at_index = index + (data[index] & ~0x80)
n = 0
while index < stop_at_index:
index += 1
n *= 256
n += data[index]
return index + 1, n
def print_ber_decoding(data, index=0, prefix=''):
global type_names, field_names, field_num
while index < len(data):
is_constructed = bool(data[index] & 0x20)
type_num = data[index] & 0x1F
type_str = 'constructed' if is_constructed else type_names[type_num]
index, n = get_length(data, index + 1)
if is_constructed:
sub_data = data[index : index + n]
print_ber_decoding(sub_data, index=0, prefix=prefix + ' ')
index += n
elif type_num == 2: # Integer.
value = 0
while n > 0:
value *= 256
value += data[index]
n -= 1
index += 1
print(f'{field_names[field_num]}: ', end='')
field_num += 1
if value < 1e10:
print(value)
else:
print('(in hex)', hex(value))
elif type_num == 16: # String.
str_bytes = data[index : index + n]
s = str_bytes.decode('ascii')
# print(f'{prefix} Read in the string value {s}')
index += n
else:
print(f'Error: unknown type_num {type_num}.')
sys.exit(1)
def print_keyfile_data(filename):
data = decode_b64_from_keyfile(filename)
print_ber_decoding(data)
# ______________________________________________________________________
# Main
if __name__ == '__main__':
if len(sys.argv) < 2:
print(__doc__)
sys.exit(0)
print_keyfile_data(sys.argv[1])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment