48

I use SSH (OpenSSH 5.5p1 on Linux, to be precise). I have a key, on which i have a passphrase. I use this for the usual logging in to computers stuff.

Can i also use it to sign files?

As i understand it, an SSH key is an RSA (or DSA) key, and during the SSH login process, it is used to sign messages sent to the server. So in principle and in practice, it can be used to sign things - indeed, that is its sole purpose.

But as far as i can see, there is no way to use the key to sign an arbitrary file (as you would with PGP, say). Is there some way to do this?

Blacklight Shining
  • 2,348
  • 6
  • 34
  • 53
Tom Anderson
  • 1,743
  • 1
  • 13
  • 13
  • Doesn't OpenSSH use [Ed25519](https://en.wikipedia.org/wiki/EdDSA#Ed25519) as the [dat protocol](https://github.com/datproject/docs/blob/master/papers/dat-paper.pdf)? Seems just a matter of tools. – Pablo A Mar 15 '18 at 03:22

6 Answers6

35

I stumpled upon this old post looking for the same thing. As it turns out, ssh-keygen from the OpenSSH tools is nowadays directly capable of generating and validating signatures using existing SSH keys. This has been introduced in OpenSSH 8.1 (released on 2019-10-09).

TL;DR

Use ssh-keygen to sign and verify signatures:

echo "Hello, World!" | ssh-keygen -Y sign -n file -f id_rsa > content.txt.sig
echo "Hello, World!" | ssh-keygen -Y check-novalidate -n file -f id_rsa.pub -s content.txt.sig

Arguments for ssh-keygen

As I didn't find the ssh-keygen man page to be particularly helpful (yet), here is an overview of a few useful commands and their required arguments for signing content.

Signing content:

ssh-keygen 
-Y sign        # The 'sign' signature operation
-n <namespace> # Signature namespace (e.g. file or email)
-f <file>      # Path to the private key to sign the content with
<files>        # Paths to one or more files you want to sign (optional, by default reads from stdin)

Checking the signature of signed content:

ssh-keygen 
-Y check-novalidate # The 'check' signature operation
-n <namespace>      # Namespace the signature was generated in
-f <file>           # Path to the public key to validate the signature with
-s <file>           # Path to the signature file

Check signature and verify whether the signer is authorized to sign the content:

ssh-keygen 
-Y verify      # The 'verify' signature operation
-n <namespace> # Namespace the signature was generated in
-f <file>      # Path to the ALLOWED SIGNERS file
-s <file>      # Path to the signature file
-I <principal> # The expected identity principal used to generate the signature

Search the ALLOWED SIGNERS file for any principals that are applicable for the specified signature:

ssh-keygen 
-Y find-principals # The 'find principals' signature operation
-f <file>          # Path to the ALLOWED SIGNERS file
-s <file>          # Path to the signature file

Display your key's principal (usually <username>@<hostname> from when it was generated):

ssh-keygen 
-l        # Print the key's fingerprint
-f <file> # Path to the public key 

Generate a user certificate from an Certificate Authority (CA) SSH key:

ssh-keygen 
-I <cert_id>    # The certificate identity
-s <file>       # Path to the CA's private key
-n <principals> # Identity principals allowed by the generated certificate
<files>         # Path to one or more public keys to generate a certificate for

The ALLOWED SIGNERS file

This file contains a list of identities which are allowed to sign content, and is therefore used to determine whether a signature comes from an authorized source. The format is similar to the well-known authorized_keys file, and described in more detail in the ALLOWED SIGNERS section in the man page.

<princpal>[,<principal>,..] [cert-authority] [namespaces="<namespace>[,<namespace>,..]"] <public-key>

Examples:

user1@server,user2@server ssh-rsa AAAAX1...
*@server cert-authority ssh-rsa AAAAX1...

Using the signature operation commands

The simplest way of generating a signature and validating it:

# Generate a new SSH key
ssh-keygen -f id_rsa -N ''

# Create the original content to be signed
echo "Trust me, the author!" > content.txt

# Sign the content
cat content.txt | ssh-keygen -Y sign -n file -f id_rsa > content.txt.sig

# Check the signature of the content
cat content.txt | ssh-keygen -Y check-novalidate -f id_rsa.pub -n file -s content.txt.sig

Now try to check the signature with invalid content:

# Pretend the content has been tampered with by a malicious party
echo "Trust ME, the hacker!" > content-tampered.txt

# Check the signature of the content again (which will fail)
cat content-tampered.txt | ssh-keygen -Y check-novalidate -f id_rsa.pub -n file -s content.txt.sig

Note that the signature has been verified using the check-novalidate signature operation. The novalidate part sounds a little scary when you want to validate whether the signature matches with the content. However, when this check succeeds, it does mean that the signature matches with the original content. You just have no information about whether the signer was authorized to sign the content.

When you have a public key you trust and expect to be used to sign the content, you can make use of the ALLOWED SIGNERS file:

# Export your key's principal
ssh-keygen -l -f id_rsa.pub | cut -d ' ' -f3 > identity

# Create an ALLOWED SIGNERS file
echo "$(cat identity) $(cat id_rsa.pub)" > allowed_signers

# Check signature and verify the signer 
cat content.txt | ssh-keygen -Y verify -f allowed_signers -I $(cat identity) -n file -s content.txt.sig

As you already might have seen in the overview above, OpenSSH is nowadays also capable of signing other SSH keys. This means we can use the SSH key to act as a Certificate Authority (CA), which is pretty cool!

What this allows you to do for signing is only having to specify the CA's public key in the ALLOWED SIGNERS file, while all SSH keys signed by the CA are capable of signing valid content:

# Generate a new CA key
ssh-keygen -f ca -C 'SSH Certificate Authority' -N ''

# Replace the ALLOWED SIGNERS file contents with just the CA's public key
echo "$(cat identity) cert-authority $(cat ca.pub)" > allowed_signers

# Sign your public key using the CA, creating a user certificate
ssh-keygen -I $(cat identity) -s ca -n $(cat identity) id_rsa

# Sign the content by specifying the user certificate (which uses the private key)
cat content.txt | ssh-keygen -Y sign -n file -f id_rsa-cert.pub > content.txt.sig

# Validate the signature using the CA's public key
cat content.txt | ssh-keygen -Y verify -f allowed_signers -I $(cat identity) -n file -s content.txt.sig

Now that we have a CA, we can also make use of custom arbitrary principals, as they are embedded in the certificate

# Sign your public key using the CA, adding a custom principal
ssh-keygen -I $(cat identity) -s ca -n $(cat identity),trusted-authors id_rsa

# Replace the ALLOWED SIGNERS file contents, allowing only signatures generated by keys with the trusted-authors principal
echo "trusted-authors cert-authority $(cat ca.pub)" > allowed_signers

# Sign the content by specifying the user certificate 
cat content.txt | ssh-keygen -Y sign -n file -f id_rsa-cert.pub > content.txt.sig

# Validate the signature using the CA's public key and the expected custom principal
cat content.txt | ssh-keygen -Y verify -f allowed_signers -I trusted-authors -n file -s content.txt.sig

Resulting files

If you've followed along with the commands above, you'll end up with these files in your directory:

allowed_signers      # List of principal(s) and key combinations authorized to sign the content 
ca                   # Private key of the Certificate Authority
ca.pub               # Public key of the Certificate Authority
content-tampered.txt # The content that got tampered with
content.txt          # The original content
content.txt.sig      # A signature of the original content
id_rsa               # Our private key - used to generate the signature
id_rsa-cert.pub      # Certificate signed by the CA - used to check and verify the signature
id_rsa.pub           # Our public key  - used to check the signature
identity             # Helper file which contains the identity principal of our private key

Hope it helps!

jvpernis
  • 451
  • 4
  • 3
32

There may not be a way to do this with the OpenSSH tools alone.

But it can be done quite easily with the OpenSSL tools. In fact, there are at least two ways to do it. In the examples below, ~/.ssh/id_rsa is your private key.

One way is using dgst:

openssl dgst -sign ~/.ssh/id_rsa some-file

The other is using pkeyutl:

openssl pkeyutl -sign -inkey ~/.ssh/id_rsa -in some-file

Both of these write a binary signature to standard output. dgst takes a -hex option will print a textual representation, with some details about the form of the signature. pkeyutl takes a -hexdump option which is a bit less useful. Both will accept both RSA and DSA keys. I have no idea what the format of the output is. The two commands produce different formats. I get the impression that pkeyutl is considered more modern than dgst.

To verify those signatures:

openssl dgst -verify $PUBLIC_KEY_FILE -signature signature-file some-file

and:

openssl pkeyutl -verify -inkey $PUBLIC_KEY_FILE -sigfile signature-file -in some-file

The problem here is $PUBLIC_KEY_FILE. OpenSSL can't read OpenSSH's public key format, so you can't just use id_rsa.pub. You have a few options, none ideal.

If you have a version of OpenSSH of 5.6 or later, you can apparently do this:

ssh-keygen -e -f ~/.ssh/id_rsa.pub -m pem

Which will write the public key to standard output in PEM format, which OpenSSL can read.

If you have the private key, and it's an RSA key, then you can extract the public key from it (I assume the PEM-encoded private key file includes a copy of the public key, since it is not possible to derive the public key from the private key itself), and use that:

openssl rsa -in ~/.ssh/id_rsa -pubout

I don't know if there's a DSA equivalent. Note that this approach requires some cooperation from the owner of the private key, who will have to extract the public key and send it to the would-be verifier.

Lastly, you can use a Python program written by a chap called Lars to convert the public key from OpenSSH to OpenSSL format.

Tom Anderson
  • 1,743
  • 1
  • 13
  • 13
  • 2
    I'd just like to note that “it is not possible to derive the public key from the private key itself” is not true. In practice (that is, in all the cryptosystems that are actually used) public key is easily derived from the private key most of the time. – kirelagin Apr 07 '14 at 19:35
  • @kirelagin: I didn't know that. Could you tell me, or link me to, more information about how that can be done? – Tom Anderson Apr 08 '14 at 11:20
  • 1
    I'm not sure if there is some particular reading on this topic… Let's just think about it. Take any discrete log based cryptosystem (ElGamal). In this case, private key is (group size, generator, power) and public key is (group size, generator, generator^power). So, log is difficult, but power is not, you just calculate it. – kirelagin Apr 08 '14 at 16:28
  • In case of RSA this inversion is actually difficult, but here the situation is slightly different. Public key is (n, d) and private key is (n, d^(-1) mod phi(n)). Inverting d would also be difficult if you didn't store phi(n), but here is the trick: almost everyone uses e = 65537 (when you generate a key there is an option to change this default, but I've never seen anyone using it because it doesn't make any practical sense), so deriving a public key from a private one is trivial. – kirelagin Apr 08 '14 at 16:31
  • With Elliptic curves it's actually the same as with discrete log and power, inverting is easy. That said, I'm not sure about other cryptosystems, but those three are the ones that are used in practice. – kirelagin Apr 08 '14 at 16:36
  • I found this write up very helpful as well http://blog.oddbit.com/2011/05/09/signing-data-with-ssh-agent/. – David Xia Oct 21 '15 at 02:53
13

@Tom's answer helped get me started, but didn't work out-of-the box.

These commands will work with:

  • OpenSSL 1.0.1 14 Mar 2012
  • OpenSSH_5.9p1

Using pkeyutl

# openssl pkeyutl -sign -inkey ~/.ssh/id_sample -in $1 > $1.sig
# ssh-keygen -e -f ~/.ssh/id_sample.pub -m PKCS8 > pub
# openssl pkeyutl -verify -pubin -inkey pub -in $1 -sigfile $1.sig
Signature Verified Successfully

Using dgst

# openssl dgst -sign ~/.ssh/id_sample $1 > $1.sig
# ssh-keygen -e -f ~/.ssh/id_sample.pub -m PKCS8 > pub
# openssl dgst -verify pub -signature $1.sig $1
Verified OK

The pkeyutl version can only sign small sized files. While dgst can sign arbitrarily large files, because it takes a digest before signing the result.

stephen.z
  • 373
  • 3
  • 6
1

jvpernis's answer is fine, but lacks one important thing:

The -Y check-novalidate option just verifies the structure of the signature and whether the message digest matches the one contained within the signature.

It does NOT verify the signature against the public key file provided in the -f argument!

peyr0l
  • 11
  • 1
0

Someone noticed that when signing we use public keys in the "allowed_signers" file? But we use a signature, and therefore, on the contrary, it is necessary that such a file remain secret. Correct me if I'm wrong.

devtv
  • 1
  • This does not really answer the question. If you have a different question, you can ask it by clicking [Ask Question](https://superuser.com/questions/ask). To get notified when this question gets new answers, you can [follow this question](https://meta.stackexchange.com/q/345661). Once you have enough [reputation](https://superuser.com/help/whats-reputation), you can also [add a bounty](https://superuser.com/help/privileges/set-bounties) to draw more attention to this question. - [From Review](/review/late-answers/1163580) – Rohit Gupta Dec 25 '22 at 03:25
  • Perhaps you should post it on [Information Security Site] (https://security.stackexchange.com/) – Rohit Gupta Dec 25 '22 at 03:26
-4

To verify those signatures - easier solution:

Easier way to make sure a signed document is the same, is to re-generate the digital signature file and then use diff to check if the two signature files are the same.

  • 3
    You are thinking of *hashes*, not *signatures*. Similar, but not the same: the hash only verifies that the file didn't change; a signature also verifies whence it came. – Piskvor left the building Oct 20 '11 at 14:00