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!