Skip to content

Instantly share code, notes, and snippets.

@giannitedesco
Created August 30, 2018 08:13
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save giannitedesco/54dba84caf904f165bd66035225e9d65 to your computer and use it in GitHub Desktop.
Save giannitedesco/54dba84caf904f165bd66035225e9d65 to your computer and use it in GitHub Desktop.
Send secret messages to someone using their public ssh key
#!/usr/bin/python3
__copyright__ = "Copyright (c) 2018 Gianni Tedesco"
__licence__ = "GPLv3"
__doc__ = "Tool for sending secret messages using ssh keys"
from argparse import ArgumentParser
try:
import nacl.utils
from nacl.signing import SigningKey,VerifyKey
from nacl.public import PrivateKey,PublicKey,Box
_nacl = True
except:
_nacl = False
from base64 import b64decode,b64encode
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)
def as_nacl(self):
if not _nacl:
raise NotImplementedError
return VerifyKey(self.key).to_curve25519_public_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)
def as_nacl(self):
if not _nacl:
raise NotImplementedError
return SigningKey(self.key).to_curve25519_private_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:
#sk = SigningKey(privkey[:32]).to_curve25519_private_key()
#pk = VerifyKey(privkey[32:]).to_curve25519_public_key()
#print('sk', bytes(sk))
#print('pk', pk)
#print(bytes(seed.to_curve25519_private_key().public_key))
#assert(pk == sk)
pass
return _cls(cipher.decode('ascii'), privkey[:32])
@classmethod
def fromfile(_cls, fn):
return _cls.load(open(fn).read())
def main():
opts = ArgumentParser(description='gash - Gianni\'s Ad-Hoc Secure Hell')
opts.add_argument('message',
metavar = 'message',
nargs = '*',
help = 'UTF-8 text to encrypt/decrypt')
opts.add_argument('--secret', '-s',
metavar = 'path',
type = str,
default = 'id_ed25519',
help = 'Path to secret key')
opts.add_argument('--public', '-p',
metavar = 'path',
type = str,
default = 'id_ed25519.pub',
help = 'Path to public key')
opts.add_argument('--decrypt', '-d',
default = False,
action = 'store_true',
help = 'Decrypt')
args = opts.parse_args()
sk = SshPrivateKey.fromfile(args.secret).as_nacl()
pk = SshPublicKey.fromfile(args.public).as_nacl()
box = Box(sk, pk)
if args.decrypt:
for m in args.message:
ciphertext = b64decode(m)
plaintext = box.decrypt(ciphertext)
print(plaintext.decode('utf-8'))
else:
nonce = nacl.utils.random(Box.NONCE_SIZE)
plaintext = ' '.join(args.message).encode('utf-8')
ciphertext = box.encrypt(plaintext, nonce)
message = b64encode(ciphertext)
print(message.decode('utf-8'))
if __name__ == '__main__':
main()
@giannitedesco
Copy link
Author

It needs to be an ed25519 key, and the private key should be unpassworded.

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