Skip to content

Instantly share code, notes, and snippets.

@giannitedesco
Created August 28, 2018 12:21
Show Gist options
  • Save giannitedesco/870a2ce73f23413c40cce9cab3f15822 to your computer and use it in GitHub Desktop.
Save giannitedesco/870a2ce73f23413c40cce9cab3f15822 to your computer and use it in GitHub Desktop.
Parse openssh public and private keys, only ed25519
#!/usr/bin/python3
try:
from nacl.signing import SigningKey
_nacl = True
except:
_nacl = False
from base64 import b64decode
from struct import unpack
from sys import argv
from operator import itemgetter
import hashlib
def _get_string(buf):
sz = unpack('>I', buf[:4])[0]
s = buf[4:4 + sz]
buf = buf[4 + sz:]
return (s, buf)
def _get_u32(buf):
ret = unpack('>I', buf[:4])[0]
buf = buf[4:]
return (ret, buf)
def _get_strings(buf):
lst = list()
while len(buf) >= 4:
(s, buf) = _get_string(buf)
lst.append(s)
lst = tuple(lst)
return lst
class SshPublicKey(tuple):
__slots__ = ()
algo = property(itemgetter(0))
key = property(itemgetter(1))
def __new__(_cls, algo, key):
return super().__new__(_cls, (str(algo), bytes(key)))
def save_as_nacl(self, fn):
with open(fn, 'wb') as f:
f.write(self.key)
@classmethod
def load(_cls, buf):
cipher, key, *rest = buf.split()
kpem = b64decode(key)
cipher2, key2, *_ = _get_strings(kpem)
cipher2 = cipher2.decode('ascii')
assert(cipher == cipher2)
return _cls(cipher2, key2)
@classmethod
def fromfile(_cls, fn):
return _cls.load(open(fn).read())
class SshPrivateKey(tuple):
__slots__ = ()
algo = property(itemgetter(0))
key = property(itemgetter(1))
def __new__(_cls, algo, key):
return super().__new__(_cls, (str(algo), bytes(key)))
def save_as_nacl(self, fn):
with open(fn, 'wb') as f:
f.write(self.key)
@classmethod
def load(_cls, buf):
begin, *middle, end, _ = buf.split('\n')
assert(not _)
assert(begin == '-----BEGIN OPENSSH PRIVATE KEY-----')
assert(end == '-----END OPENSSH PRIVATE KEY-----')
data = b64decode(''.join(middle))
tag = b'openssh-key-v1\x00'
begin, data = data[:len(tag)], data[len(tag):]
assert(begin == tag)
ciphername, data = _get_string(data)
kdfname, data = _get_string(data)
kdf, data = _get_string(data)
nkeys, data = _get_u32(data)
pubkey, data = _get_string(data)
encrypted_len, data = _get_u32(data)
assert(ciphername == b'none')
assert(kdfname == b'none')
assert(encrypted_len == len(data))
assert(encrypted_len >= 8)
check1, data = _get_u32(data)
check2, data = _get_u32(data)
assert(check1 == check2)
cipher, data = _get_string(data)
pubkey, data = _get_string(data)
privkey, data = _get_string(data)
comment, data = _get_string(data)
if _nacl:
#print('private', cipher.decode('ascii'), comment.decode('ascii'))
seed = SigningKey(privkey[:32])
sk = bytes(seed.to_curve25519_private_key())
pk = bytes(seed.verify_key)
#print('sk', bytes(sk))
#print('pk', pk)
#print(bytes(seed.to_curve25519_private_key().public_key))
assert(pk == pubkey)
return _cls(cipher.decode('ascii'), sk)
@classmethod
def fromfile(_cls, fn):
return _cls.load(open(fn).read())
def main():
pubkey = SshPublicKey.fromfile(argv[1])
pubkey.save_as_nacl('ssh-pubkey')
pubkey = SshPrivateKey.fromfile(argv[2])
pubkey.save_as_nacl('ssh-privkey')
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment