[Crypto] Encrypt/decrypt with SSH keys

You may have heard that it's possible to sign/verify files using SSH keys. See also my previous blog post: Using HTTPS certificates to sign/encrypt arbitrary data.

In fact, it's possible to encrypt using SSH keys. Why not?

My toy SSH client can download public key from arbitrary SSH server, I added an option for it. Here I access my live webserver actually:

% ./toyssh_v5.py -h vps7.yurichev.org -save_serv_pub_key -server_host_algo rsa-sha2-256

Server's public key saved to vps7.yurichev.org.ssh_host_rsa_key.pub
Fatal error: can't go further without username/passsword/pubkey/prikey. exiting.

(-server_host_algo option is important, otherwise ECDSA or ED25519 key would be saved instead.)

The resulting file is almost the same as on my webserver, excluding the last user@host part:

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCxWpYVDk6hDsgwSODF0jHx1SmGnhuGzswlkqyua5NN1t9u4k5zQJi0WI/iiZH8wE51u2htujxf7ie/DZ26fh0uuHfz6EA7qPdtCB+q4kT8tlXVAWzUw3TslnAKotk36N4euwt9vGSslBHE3GHP7uIOULajtSrfdFU7GpqQRogu0zs2jr3S5Sgmx0CVX9rS3ZurwZ816UJCCOmkoanmM1CSwVfL18iLcs+asxuPAZVu1ZvqWwv6MrM3vdmU92DANxIF4wrrwYiJhrIKVhDfNHxNIn2O5akad73JVzCgSzZil9GIagdIo0LO/dyH2r6BoaGjuqbK2vEBdS8tMrhL+BbD5EUUUN/sHopbHwl1Gska4fs7zIoh1clO+1bHfze/8U8lxezxPka+ctl5vzOYTUFc+BS8t8jCRqBCC5iN/YtAPmPWbfxA2VzXCh1Ny4a+e9HB6ZchNvUq5N8sfchCdIeasEtmQ2EVfaLbObjnMRVGDRiH5myiEQWzxO80ti1cf5k= root@vps7.yurichev.org

It's no secret. It's the same as on my webserver:

root@vps7:~# cat /etc/ssh/ssh_host_rsa_key.pub

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCxWpYVDk6hDsgwSODF0jHx1SmGnhuGzswlkqyua5NN1t9u4k5zQJi0WI/iiZH8wE51u2htujxf7ie/DZ26fh0uuHfz6EA7qPdtCB+q4kT8tlXVAWzUw3TslnAKotk36N4euwt9vGSslBHE3GHP7uIOULajtSrfdFU7GpqQRogu0zs2jr3S5Sgmx0CVX9rS3ZurwZ816UJCCOmkoanmM1CSwVfL18iLcs+asxuPAZVu1ZvqWwv6MrM3vdmU92DANxIF4wrrwYiJhrIKVhDfNHxNIn2O5akad73JVzCgSzZil9GIagdIo0LO/dyH2r6BoaGjuqbK2vEBdS8tMrhL+BbD5EUUUN/sHopbHwl1Gska4fs7zIoh1clO+1bHfze/8U8lxezxPka+ctl5vzOYTUFc+BS8t8jCRqBCC5iN/YtAPmPWbfxA2VzXCh1Ny4a+e9HB6ZchNvUq5N8sfchCdIeasEtmQ2EVfaLbObjnMRVGDRiH5myiEQWzxO80ti1cf5k= root@ubuntu-4gb-hel1-1

Now anyone can get my RSA public key. And I have corresponding RSA private key. Can anyone send me an encrypted message? Yes. But I don't know any standard tool for that. I wrote mine.

#!/usr/bin/env python3

# May need to be upgraded:
# python3 -m pip install --upgrade cryptography
import cryptography.hazmat.primitives.serialization
import cryptography.hazmat.primitives.asymmetric.padding
import cryptography.hazmat.primitives.hashes
import sys

# load pub key from server:
f=open(sys.argv[1], "rb")
contents=f.read()
f.close()

pub_key=cryptography.hazmat.primitives.serialization.load_ssh_public_key(contents)

plaintext=b"Hi, Dennis. The Secret Meetup is today, at The Venue 123. 1800 UTC."

# https://cryptography.io/en/latest/hazmat/primitives/asymmetric/rsa/#encryption
algorithm=cryptography.hazmat.primitives.hashes.SHA256()
mgf=cryptography.hazmat.primitives.asymmetric.padding.MGF1(algorithm=algorithm)
ciphertext=pub_key.encrypt(plaintext,
    cryptography.hazmat.primitives.asymmetric.padding.OAEP(mgf, algorithm, label=None))

f=open("cipher.bin", "wb")
f.write(ciphertext)
f.close()
% ./ssh_encrypt.py vps7.yurichev.org.ssh_host_rsa_key.pub

The resulting cipher.bin file is of 384 bytes. I can decrypt it on my webserver with the following code:

#!/usr/bin/env python3

# May need to be upgraded:
# python3 -m pip install --upgrade cryptography
import cryptography.hazmat.primitives.serialization
import cryptography.hazmat.primitives.asymmetric.padding
import cryptography.hazmat.primitives.hashes
import sys

# load ciphertext:
f=open(sys.argv[1], "rb")
ciphertext=f.read()
f.close()

f=open("/etc/ssh/ssh_host_rsa_key", "rb")
contents=f.read()
f.close()

pri_key=cryptography.hazmat.primitives.serialization.load_ssh_private_key(contents, password=b"")

# https://cryptography.io/en/latest/hazmat/primitives/asymmetric/rsa/#decryption
algorithm=cryptography.hazmat.primitives.hashes.SHA256()
mgf=cryptography.hazmat.primitives.asymmetric.padding.MGF1(algorithm=algorithm)
plainext=pri_key.decrypt(ciphertext,
    cryptography.hazmat.primitives.asymmetric.padding.OAEP(mgf, algorithm, label=None))

print (plainext)
root@vps7:~/1# ./ssh_decrypt.py cipher.bin

b'Hi, Dennis. The Secret Meetup is today, at The Venue 123. 1800 UTC.'

Neat. But be warned. I did this as a coding exercise, as a proof-of-concept. As it happens in cryptography often, this solution may be insecure if implemented incorrectly.

Why ssh-keygen can sign/verify but not encrypt/decrypt? I don't know but maybe because all this is possible only with RSA keys. But today, some sysadmins disable RSA keys in favor of ECDSA and ED25519 keys, which only allows signing/verifying. ssh-keygen's feature of encryption, if implemented, would not be universal. (This is only my speculation.)

(UPD: 20240616 12:18:10 CEST. As seen on reddit: 1, 2.)

(the post first published at 20240529.)


List of my other blog posts.

Subscribe to my news feed,

Yes, I know about these lousy Disqus ads. Please use adblocker. I would consider to subscribe to 'pro' version of Disqus if the signal/noise ratio in comments would be good enough.