Skip to content

Instantly share code, notes, and snippets.

@zougloub
Created January 26, 2016 06:30
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save zougloub/3058d56857ba400b7ec3 to your computer and use it in GitHub Desktop.
Save zougloub/3058d56857ba400b7ec3 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
# -*- coding: utf-8 vi:noet
# Dump a clear-text version of an SSH key from gpg-agent
__author__ = "Jérôme Carretero <cJ-tub@zougloub.eu>"
__licence__ = "MIT"
import sys, io, subprocess, re, ctypes
libgcrypt = ctypes.CDLL("libgcrypt.so")
libgcrypt.gcry_cipher_open.argtypes = (ctypes.POINTER(ctypes.c_void_p), ctypes.c_int, ctypes.c_int, ctypes.c_int)
libgcrypt.gcry_cipher_open.restype = ctypes.c_int
libgcrypt.gcry_cipher_setkey.argtypes = (ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int)
libgcrypt.gcry_cipher_setkey.restype = ctypes.c_int
libgcrypt.gcry_cipher_decrypt.argtypes = (ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int, ctypes.c_char_p, ctypes.c_int)
libgcrypt.gcry_cipher_decrypt.restype = ctypes.c_int
libgcrypt.gcry_cipher_encrypt.argtypes = (ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int, ctypes.c_char_p, ctypes.c_int)
libgcrypt.gcry_cipher_encrypt.restype = ctypes.c_int
libgcrypt.gcry_cipher_close.argtypes = (ctypes.c_void_p,)
GCRY_CIPHER_AES128 = 7
GCRY_CIPHER_MODE_AESWRAP = 7
def unwrap_key(kek, crypted):
cipherhd = ctypes.c_void_p()
res = libgcrypt.gcry_cipher_open(ctypes.byref(cipherhd), GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_AESWRAP, 0)
assert res == 0, res
res = libgcrypt.gcry_cipher_setkey(cipherhd, kek, len(kek))
assert res == 0, res
xl = len(crypted)-8
x = ctypes.create_string_buffer(xl)
res = libgcrypt.gcry_cipher_decrypt(cipherhd, x, xl, crypted, len(crypted))
assert res == 0, res
libgcrypt.gcry_cipher_close(cipherhd)
return x.raw
def gpg_agent(*args):
cmd = ['gpg-connect-agent'] + list(args) + ['/bye']
out = subprocess.check_output(cmd)
return out
if __name__ == '__main__':
if len(sys.argv) == 1:
print("Select a keygrab (after KEYINFO) from the following" \
"and pass it as argument to this script")
cmd = ('ssh-add', '-l')
keys = subprocess.check_output(cmd).decode().splitlines()
keylist = gpg_agent("KEYINFO --ssh-list").decode().splitlines()[:-1]
for idx_key, (key_ssh, key_gpg) in enumerate(zip(keys, keylist)):
print("% 3d: %s %s" % (idx_key, key_gpg, key_ssh))
raise SystemExit()
keygrab = sys.argv[1]
out = gpg_agent(
"OPTION lc-ctype=C",
"OPTION lc-messages=C",
"KEYWRAP_KEY --export",
"EXPORT_KEY %s" % keygrab,
)
pat = re.compile(br"^OK\nOK\nD (?P<keywrap_key>.*)\nOK\nD (?P<wrapped>.*)\nOK\n$", re.DOTALL)
m = re.match(pat, out)
assert m is not None
def deassuan(x):
out = []
idx_c = 0
while idx_c < len(x):
if x[idx_c] == b"%":
if x[idx_c:idx_c+3] == b"%0A":
out.append(b"\n")
elif x[idx_c:idx_c+3] == b"%0D":
out.append(b"\r")
elif x[idx_c:idx_c+3] == b"%25":
out.append(b"%")
else:
raise NotImplementedError()
idx_c += 3
else:
out.append(x[idx_c])
idx_c += 1
return bytes(bytearray(out))
keywrap_key = deassuan(m.group("keywrap_key"))
ciphertext = deassuan(m.group("wrapped"))
key = unwrap_key(keywrap_key, ciphertext)
with io.open("%s.key" % keygrab, "wb") as f:
f.write(key)
print("Wrote the key into %s.key.\n" \
"Now you can add this key back to ~/.gnupg/private-keys-v1.d/" \
" and execute the PASSWD command of ssh-connect-agent to protect it.")
@zougloub
Copy link
Author

Notice the missing % keygrab at the end...

@agowa
Copy link

agowa commented Jan 8, 2024

Doesn't work for em.

I've three keys that are within private-keys-v1.d, but somehow are not within pubring.kbx. I can still use them to ssh into remote servers. But gpg won't allow me to export them, nor does it list them as even existing.

When I try this script on them I get:

Traceback (most recent call last):
  File "/home/user/.gnupg/./a.py", line 94, in <module>
    key = unwrap_key(keywrap_key, ciphertext)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/user/.gnupg/./a.py", line 35, in unwrap_key
    assert res == 0, res
           ^^^^^^^^
AssertionError: 16777261

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment