Created
April 25, 2022 05:02
-
-
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).
This file contains 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 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