54

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?

Tom Anderson
  • 1,833

6 Answers6

52

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
  • 621
  • 5
  • 4
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,833
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
  • 383
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
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
-5

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.