Skip to content

110.3 Securing data with encryption

Weight: 4

The candidate should be able to use public key techniques to secure data and communication.

Objectives

  • Perform basic OpenSSH 2 client configuration and usage.
  • Understand the role of OpenSSH 2 server host keys.
  • Perform basic GnuPG configuration, usage and revocation.
  • Use GPG to encrypt, decrypt, sign and verify files.
  • Understand SSH port tunnels (including X11 tunnels).

Terms

ssh, ssh-keygen, ssh-agent, ssh-add, ~/.ssh/id_rsa and id_rsa.pub, ~/.ssh/id_dsa, ~/.ssh/id_ecdsa, ~/.ssh/id_ed25519 (and their .pub), /etc/ssh/ssh_host_rsa_key and ssh_host_rsa_key.pub, /etc/ssh/ssh_host_dsa_key, /etc/ssh/ssh_host_ecdsa_key, /etc/ssh/ssh_host_ed25519_key, ~/.ssh/authorized_keys, ssh_known_hosts, gpg, gpg-agent, ~/.gnupg/

Key pairs

Old-style (symmetric) encryption uses one shared password: the same password locks and unlocks the data. The problem is that both sides need the password, so you must find a safe way to share it.

Public key (asymmetric) cryptography solves this with a key pair: two keys made together, so that whatever one key encrypts, only the other can decrypt.

  • The private key stays with you and is never shared.
  • The public key can be given to anyone, even published on the internet.
 ENCRYPT (privacy)                         SIGN (proof of who wrote it)

 friend uses YOUR public key               you use YOUR private key
          |                                          |
          v                                          v
   encrypted file  ----any channel---->      signed file  ----any channel---->
          |                                          |
          v                                          v
 only YOUR private key opens it            anyone checks it with YOUR public key

People can see that you received "some data", but without your private key they cannot read it. Both SSH and GnuPG are built on this idea.

OpenSSH: connecting and known hosts

SSH (Secure Shell) logs you in to another machine over an encrypted connection. It replaces old, insecure tools like telnet and rlogin. It uses keys to prove the identity of the server, and optionally of the user, and then encrypts everything. The current protocol version is SSH 2, and OpenSSH is the free implementation you will meet on Linux.

The examples use two machines:

Role Host name IP address User
client debian 192.168.1.55 carol
server halof 192.168.1.77 ina

Connect with ssh user@host. The first time, SSH does not know the server yet, so it shows the fingerprint of the server's key and asks you to trust it:

carol@debian:~$ ssh ina@192.168.1.77
The authenticity of host '192.168.1.77 (192.168.1.77)' can't be established.
ECDSA key fingerprint is SHA256:5JF7anupYipByCQm2BPvDHRVFJJixeslmppi2NwATYI.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.1.77' (ECDSA) to the list of known hosts.
Password:
Last login: Sat Jun 20 10:52:45 2020 from 192.168.1.4
Have a lot of fun...
ina@halof:~>

When you answer yes, the server's public key is saved in ~/.ssh/known_hosts. From then on, SSH checks the server against that key on every connection, silently. The system-wide list of known host keys is the file ssh_known_hosts.

ina@halof:~> exit
logout
Connection to 192.168.1.77 closed.
carol@debian:~$ ls .ssh/
known_hosts

~/.ssh/ is where all your personal SSH files live. A few handy forms:

Command Does
ssh ina@halof log in as ina
ssh halof log in with the same user name you have locally
ssh ina@halof ls run one command on the server and come back

When the host key changes

If the server answers with a different key than the one saved in known_hosts, SSH refuses to connect and shows a loud warning. It could be a man-in-the-middle attack, someone pretending to be your server:

carol@debian:~$ ssh john@192.168.1.77
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the ECDSA key sent by the remote host is
SHA256:KH4q3vP6C7e0SEjyG8Wlz9fVlf+jmWJ5139RBxBh3TY.
Please contact your system administrator.
Add correct host key in /home/carol/.ssh/known_hosts to get rid of this message.
Offending ECDSA key in /home/carol/.ssh/known_hosts:1
  remove with:
  ssh-keygen -f "/home/carol/.ssh/known_hosts" -R "192.168.1.77"
ECDSA host key for 192.168.1.77 has changed and you have requested strict checking.
Host key verification failed.

There are innocent reasons too: the server was reinstalled, or another machine got the same IP address from DHCP. Only after you are sure it is not an attack, delete the old key with ssh-keygen -R, and connect again:

carol@debian:~$ ssh-keygen -R 192.168.1.77

Server host keys

The server's own files are in /etc/ssh/:

halof:~ # tree /etc/ssh
/etc/ssh
├── moduli
├── ssh_config
├── ssh_host_dsa_key
├── ssh_host_dsa_key.pub
├── ssh_host_ecdsa_key
├── ssh_host_ecdsa_key.pub
├── ssh_host_ed25519_key
├── ssh_host_ed25519_key.pub
├── ssh_host_rsa_key
├── ssh_host_rsa_key.pub
└── sshd_config

ssh_config configures the client and sshd_config configures the server (the d is for daemon). The rest are host keys: one key pair for each algorithm, created when the OpenSSH server is installed. The server uses them to prove its identity to clients. They are the keys whose fingerprints you saw on your first connection.

File Is
ssh_host_<algorithm>_key the private key, for example ssh_host_rsa_key
ssh_host_<algorithm>_key.pub the public key, for example ssh_host_rsa_key.pub

The private keys can only be read by root (0600). The public keys can be read by everyone (0644):

halof:~ # ls -l /etc/ssh/ssh_host_*
-rw------- 1 root root 1381 Dec 21 20:35 /etc/ssh/ssh_host_dsa_key
-rw-r--r-- 1 root root  605 Dec 21 20:35 /etc/ssh/ssh_host_dsa_key.pub
-rw------- 1 root root  505 Dec 21 20:35 /etc/ssh/ssh_host_ecdsa_key
-rw-r--r-- 1 root root  177 Dec 21 20:35 /etc/ssh/ssh_host_ecdsa_key.pub
-rw------- 1 root root  411 Dec 21 20:35 /etc/ssh/ssh_host_ed25519_key
-rw-r--r-- 1 root root   97 Dec 21 20:35 /etc/ssh/ssh_host_ed25519_key.pub
-rw------- 1 root root 1823 Dec 21 20:35 /etc/ssh/ssh_host_rsa_key
-rw-r--r-- 1 root root  397 Dec 21 20:35 /etc/ssh/ssh_host_rsa_key.pub

A fingerprint is a short hash of a public key, easier to compare than the key itself. ssh-keygen -l -f file shows it, and adding -v also draws the "random art" picture of the key:

halof:~ # ssh-keygen -l -f /etc/ssh/ssh_host_ed25519_key.pub
256 SHA256:8cnPrinC49ZHc+/9Ai5pV+1JfZ4WBRZhd3rDOsc2zlA root@halof (ED25519)
halof:~ # ssh-keygen -lv -f /etc/ssh/ssh_host_ed25519_key.pub
256 SHA256:8cnPrinC49ZHc+/9Ai5pV+1JfZ4WBRZhd3rDOsc2zlA root@halof (ED25519)
+--[ED25519 256]--+
|            +oo  |
|           .+o.  |
|      .    ..E.  |
|       + .  +.o  |
|      S +  + *o  |
|        ooo Oo=  |
|   . . . =o+.==  |
|     = o =oo o=o |
|   o.o +o+..o.+  |
+----[SHA256]-----+

This is how you check, the first time you connect, that the fingerprint SSH shows really belongs to your server: compare it with the output of ssh-keygen -l run on the server itself.

Creating your own key pair

ssh-keygen creates a key pair for you, the user. -t chooses the algorithm:

carol@debian:~/.ssh$ ssh-keygen -t ecdsa
Generating public/private ecdsa key pair.
Enter file in which to save the key (/home/carol/.ssh/id_ecdsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/carol/.ssh/id_ecdsa.
Your public key has been saved in /home/carol/.ssh/id_ecdsa.pub.
The key fingerprint is:
SHA256:tlamD0SaTquPZYdNepwj8XN4xvqmHCbe8g5FKKUfMo8 carol@debian
The key's randomart image is:
+---[ECDSA 256]---+
|       .         |
|      o .        |
|    = o o        |
|      B *        |
|    E B S o      |
|      o & O      |
|       @ ^ =     |
|      *.@ @.     |
|    o.o+B+o      |
+----[SHA256]-----+
carol@debian:~/.ssh$ ls
id_ecdsa  id_ecdsa.pub  known_hosts

You get two files: id_ecdsa is your private key and id_ecdsa.pub is your public key. The name follows the algorithm:

Algorithm -t Your files Notes
RSA rsa ~/.ssh/id_rsa, id_rsa.pub the default when you give no -t. Secure and widely used. Minimum 1024 bits
DSA dsa ~/.ssh/id_dsa, id_dsa.pub insecure, deprecated since OpenSSH 7.0. Always exactly 1024 bits
ECDSA ecdsa ~/.ssh/id_ecdsa, id_ecdsa.pub elliptic curve, better than DSA. 256, 384 or 521 bits
Ed25519 ed25519 ~/.ssh/id_ed25519, id_ed25519.pub considered the most secure. Always 256 bits

-b sets the size in bits, for example ssh-keygen -t ecdsa -b 521 or ssh-keygen -t rsa -b 4096. With Ed25519:

$ ssh-keygen -t ed25519
Enter file in which to save the key (/home/nagato/.ssh/id_ed25519):
Your identification has been saved in /home/nagato/.ssh/id_ed25519
Your public key has been saved in /home/nagato/.ssh/id_ed25519.pub

This writes the private key ~/.ssh/id_ed25519 and the public key ~/.ssh/id_ed25519.pub. If you set a passphrase, you are asked for it each time the key is used.

Use a passphrase. It is optional, but without one, anyone who copies your private key file can log in to every server that trusts it.

Key-based login

Logging in with your key instead of a password is the preferred way. It is more secure, and it lets you log in to many servers or automate copies with scp without typing passwords.

The idea: put your public key in the file ~/.ssh/authorized_keys of the user you log in as on the server. When you connect, your SSH client proves it has the matching private key, and the server lets you in.

 client (carol)                               server (ina@halof)
 ~/.ssh/id_ecdsa       (private, stays)
 ~/.ssh/id_ecdsa.pub   --- copied once --->   ~/.ssh/authorized_keys

You can copy the key with a USB stick, with scp, or by piping it through ssh:

carol@debian:~/.ssh$ cat id_ecdsa.pub |ssh ina@192.168.1.77 'cat >> .ssh/authorized_keys'
Password:

ssh-copy-id does the same job for you:

$ ssh-copy-id 192.168.70.2
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
Password:

Number of key(s) added:        1

Now try logging into the machine, with:   "ssh '192.168.70.2'"
and check to make sure that only the key(s) you wanted were added.

The server must allow key logins: PubkeyAuthentication yes in /etc/ssh/sshd_config. Now two things can happen when you connect:

  • No passphrase on the key: you are logged in at once. Convenient, but risky if someone steals the key file.
  • With a passphrase: you type the passphrase instead of the password. Safer, but just as much typing.
carol@debian:~/.ssh$ ssh ina@192.168.1.77
Enter passphrase for key '/home/carol/.ssh/id_ecdsa':
Last login: Thu Jun 25 20:39:30 2020 from 192.168.1.55
Have a lot of fun...
ina@halof:~>

For an administrator this is also simpler: to give someone access, you never share a password. You just add their public key to authorized_keys.

ssh-agent and ssh-add

The SSH agent gives you both safety and convenience. It holds your unlocked private keys in memory, so you type each passphrase once per session. Start a shell under the agent, then load your keys with ssh-add:

carol@debian:~/.ssh$ ssh-agent /bin/bash
carol@debian:~/.ssh$ ssh-add
Enter passphrase for /home/carol/.ssh/id_ecdsa:
Identity added: /home/carol/.ssh/id_ecdsa (carol@debian)

Another common way to start the agent in the current shell:

$ eval $(ssh-agent)
$ ssh-add
Identity added: /home/nagato/.ssh/id_rsa (nagato@debian)
$ ssh-add -l          # list loaded keys

From now on, every server that has your public key lets you in without asking. Desktops often do this at login, and the keys stay in memory until you shut down or remove them.

The agent can also forward your keys. If you log in to server A and from there need to reach server B with your key, the agent makes your key available on A without ever copying the private key file to A.

SSH port tunnels

SSH can carry other network traffic inside its encrypted connection. This is called port forwarding or tunnelling. It lets you:

  • reach ports that a firewall blocks,
  • reach a machine inside a private network from outside,
  • encrypt a protocol that has no encryption of its own.

Local port tunnel (-L)

Local forwarding (-L): you open a port on your own machine. Whatever connects to it travels through the SSH connection, and the SSH server passes it on to the destination:

ssh -L 8585:www.gnu.org:80 -Nf ina@192.168.1.77

 your browser                SSH tunnel (encrypted)               destination
 localhost:8585  ==========> ina@192.168.1.77 (halof) ----------> www.gnu.org:80

The format is -L local_port:destination_host:destination_port, followed by the SSH server to go through:

carol@debian:~$ ssh -L 8585:www.gnu.org:80 -Nf ina@192.168.1.77
Enter passphrase for key '/home/carol/.ssh/id_ecdsa':
carol@debian:~$ lynx http://localhost:8585
(...)
   * Back to Savannah Homepage
   * Not Logged in
   * Login
(...)

Opening localhost:8585 on the client showed the www.gnu.org page, fetched through halof. Two options are almost always used with tunnels:

Option Means
-N do not open a shell on the server, only forward ports
-f go to the background

Another example:

$ ssh -L 9000:hckrnews.com:80 root@5.161.197.79

Now localhost:9000 on your machine reaches hckrnews.com:80 through that server.

A real-world use: a program on your server that only answers local requests. A local tunnel lets you use it from your own machine as if it ran there.

Remote port tunnel (-R)

Remote forwarding (-R) is the other direction, also called reverse port forwarding. A port opens on the SSH server, and connections to it come back through the tunnel to your machine. Here, port 8585 on halof leads to the Apache web server on the client:

ssh -R 8585:localhost:80 -Nf ina@192.168.1.77

 someone on halof              SSH tunnel (encrypted)           your machine
 halof localhost:8585  =======================================> localhost:80 (Apache)
carol@debian:~$ ssh -R 8585:localhost:80 -Nf ina@192.168.1.77
Enter passphrase for key '/home/carol/.ssh/id_ecdsa':

carol@halof:~$ lynx localhost:8585
(...)
     Debian Logo Apache2 Debian Default Page
     It works!
(...)

Another example: requests to port 8000 on the remote server reach port 80 on your machine:

$ ssh -R 8000:localhost:80 root@5.161.197.79

By default the server opens the port on its localhost only. To open it on all of the server's network addresses, set GatewayPorts yes in the server's sshd_config (the default is no), then ask for 0.0.0.0:

carol@debian:~$ ssh -R 0.0.0.0:8585:localhost:80 -Nf ina@192.168.1.77
carol@debian:~$ lynx 192.168.1.77:8585
(...)
    It works!

Dynamic tunnel (-D)

Dynamic forwarding (-D), useful when your host has no direct Internet but the remote one does:

$ ssh -D 1080 192.168.70.2      # apps set to SOCKS localhost:1080 go out via the server

ssh -D 1080 192.168.70.2 turns port 1080 on your machine into a SOCKS proxy. Applications set to use localhost:1080 as their proxy send all their traffic out through the SSH server. This is a more complex type of forwarding; for the exam it is enough to know what -D does.

X11 tunnels

The X Window System (106.1) can also travel through SSH. With -X, you run a graphical program on the server and its window appears on your screen:

carol@debian:~$ ssh -X ina@halof
ina@halof:~$ firefox

Firefox runs on halof, but you see and use it on debian. The same with a small test program:

$ ssh -X 192.168.70.2
$ xeyes                          # window opens locally
-x (lowercase) turns X11 forwarding off, and then graphical programs fail because they have nowhere to draw:

carol@debian:~$ ssh -x ina@halof
ina@halof:~$ firefox
(firefox-esr:1779): Gtk-WARNING **: 18:45:45.603: Locale not supported by C library.
      Using the fallback 'C' locale.
Error: no DISPLAY environment variable specified

The server decides what is allowed, in sshd_config:

Directive Controls
AllowTcpForwarding local port forwarding
GatewayPorts remote forwarded ports open on all interfaces, not just localhost
X11Forwarding X11 forwarding (X11Forwarding yes is needed for ssh -X)

GnuPG: creating your keys

GnuPG (the GNU Privacy Guard, command gpg) is a free implementation of PGP (Pretty Good Privacy), following the OpenPGP standard. SSH protects a connection; GnuPG protects files and messages. It uses the same key pair idea: you encrypt files so only the recipient can read them, and you sign files so others can check they really came from you and were not changed.

Create a key pair with gpg --gen-key. It asks for your name and email (this becomes your USER-ID), then a passphrase:

carol@debian:~$ gpg --gen-key
gpg (GnuPG) 2.2.12; Copyright (C) 2018 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

gpg: directory '/home/carol/.gnupg' created
gpg: keybox '/home/carol/.gnupg/pubring.kbx' created
Note: Use "gpg --full-generate-key" for a full featured key generation dialog.

GnuPG needs to construct a user ID to identify your key.

Real name: carol
Email address: carol@debian
You selected this USER-ID:
    "carol <carol@debian>"

Change (N)ame, (E)mail, or (O)kay/(Q)uit? O
(...)
gpg: /home/carol/.gnupg/trustdb.gpg: trustdb created
gpg: key 19BBEFD16813034E marked as ultimately trusted
gpg: directory '/home/carol/.gnupg/openpgp-revocs.d' created
gpg: revocation certificate stored as '/home/carol/.gnupg/openpgp-revocs.d/D18FA0021F644CDAF57FD0F919BBEFD16813034E.rev'
public and secret key created and signed.

pub   rsa3072 2020-07-03 [SC] [expires: 2022-07-03]
      D18FA0021F644CDAF57FD0F919BBEFD16813034E
uid                      carol <carol@debian>
sub   rsa3072 2020-07-03 [E] [expires: 2022-07-03]

Everything is stored in ~/.gnupg/:

carol@debian:~/.gnupg$ ls -l
total 16
drwx------ 2 carol carol 4096 Jul  3 23:34 openpgp-revocs.d
drwx------ 2 carol carol 4096 Jul  3 23:34 private-keys-v1.d
-rw-r--r-- 1 carol carol 1962 Jul  3 23:34 pubring.kbx
-rw------- 1 carol carol 1240 Jul  3 23:34 trustdb.gpg
File Holds
openpgp-revocs.d/ the revocation certificate made with your key. Anyone who has it can revoke your key, so it is kept private
private-keys-v1.d/ your private keys
pubring.kbx your public keyring: your public key and every public key you import
trustdb.gpg the trust database

Older versions (before GnuPG 2.1) used secring.gpg and pubring.gpg instead.

gpg --list-keys shows your public keyring:

carol@debian:~/.gnupg$ gpg --list-keys
/home/carol/.gnupg/pubring.kbx
------------------------------
pub   rsa3072 2020-07-03 [SC] [expires: 2022-07-03]
      D18FA0021F644CDAF57FD0F919BBEFD16813034E
uid           [ultimate] carol <carol@debian>
sub   rsa3072 2020-07-03 [E] [expires: 2022-07-03]

The long hexadecimal string is your key's fingerprint. The KEY-ID is its last 8 digits (6813034E). gpg --fingerprint carol prints the fingerprint again.

Sharing and revoking GnuPG keys

Others need your public key to encrypt files for you and to check your signatures. Export it to a file:

carol@debian:~/.gnupg$ gpg --export carol > carol.pub.key

The file is binary. Add -a (--armor) for an ASCII version you can paste into an email: gpg --export --armor carol > carol.pub.key. Then send it, for example with scp:

carol@debian:~/.gnupg$ scp carol.pub.key ina@halof:/home/ina/
carol.pub.key                       100% 1740   775.8KB/s   00:00

The receiver imports it into their keyring:

ina@halof:~> gpg --import carol.pub.key
gpg: /home/ina/.gnupg/trustdb.gpg: trustdb created
gpg: key 19BBEFD16813034E: public key "carol <carol@debian>" imported
gpg: Total number processed: 1
gpg:               imported: 1

You can also publish keys on a key server: gpg --keyserver keyserver-name --send-keys KEY-ID to upload, and gpg --keyserver keyserver-name --recv-keys KEY-ID to download.

Revoking a key

If your private key is stolen or you stop using it, revoke it, so others know not to trust it anymore. First create a revocation certificate with --gen-revoke:

sonya@debian:~/.gnupg$ gpg --output revocation_file.asc --gen-revoke sonya

sec  rsa3072/0989EB7E7F9F2066 2020-07-03 sonya <sonya@debian>

Create a revocation certificate for this key? (y/N) y
Please select the reason for the revocation:
  0 = No reason specified
  1 = Key has been compromised
  2 = Key is superseded
  3 = Key is no longer used
  Q = Cancel
(Probably you want to select 1 here)
Your decision? 1
Enter an optional description; end it with an empty line:
> My laptop was stolen.
>
Reason for revocation: Key has been compromised
My laptop was stolen.
Is this okay? (y/N) y
ASCII armored output forced.
Revocation certificate created.

Then import the certificate, which marks your key as revoked:

sonya@debian:~/.gnupg$ gpg --import revocation_file.asc
gpg: key 9E570DEB22C27871: "sonya <sonya@debian>" revocation certificate imported
gpg: Total number processed: 1
gpg:    new key revocations: 1
sonya@debian:~/.gnupg$ gpg --list-keys
/home/sonya/.gnupg/pubring.kbx
pub   rsa3072 2020-07-04 [SC] [revoked: 2020-07-04]
      8885637C39E7A627858B4C2F9E570DEB22C27871
uid           [ revoked] sonya <sonya@debian>

Finally, send the revoked key to everyone who has your public key, and to the key servers.

Encrypting and decrypting files

To send carol a secret file, ina encrypts it with carol's public key. Only carol's private key can open it:

ina@halof:~> echo "This is the message ..." > unencrypted-message
ina@halof:~> gpg --output encrypted-message --recipient carol --armor --encrypt unencrypted-message
gpg: 0227347CC92A5CB1: There is no assurance this key belongs to the named user
(...)
Use this key anyway? (y/N) y
Option Means
--output encrypted-message name of the file to create
--recipient carol whose public key to use (their USER-ID)
--armor ASCII output, safe to paste into an email
--encrypt unencrypted-message the file to encrypt

gpg asks "Use this key anyway?" because ina has imported carol's key but has not signed it to say the key is trusted.

The encrypted file can travel over any channel. Without the key it is unreadable:

carol@debian:~$ cat encrypted-message
-----BEGIN PGP MESSAGE-----

hQGMAwInNHzJKlyxAQv/brJ8Ubs/xya35sbv6kdRKm1C7ONLxL3OueWA4mCs0Y/P
GBna6ZEUCrMEgl/rCyByj3Yq74kuiTmzxAIRUDdvHfj0TtrOWjVAqIn/fPSfMkjk
(...)
=g1jw
-----END PGP MESSAGE-----

carol decrypts it with --decrypt, typing the passphrase of carol's private key. Without --output, the text is printed on screen:

carol@debian:~$ gpg --decrypt encrypted-message
gpg: encrypted with 3072-bit RSA key, ID 0227347CC92A5CB1, created 2020-07-03
      "carol <carol@debian>"
This is the message ...
carol@debian:~$ gpg --output unencrypted-message --decrypt encrypted-message
carol@debian:~$ cat unencrypted-message
This is the message ...

Signing and verifying files

Signing works the other way round. carol uses carol's private key, and everyone checks the result with carol's public key. Since only carol has that private key, a good signature proves that carol signed the file and that it was not changed.

carol@debian:~$ echo "This is the message to sign ..." > message
carol@debian:~$ gpg --output message.sig --sign message

--sign compresses the file and signs it. The result is binary. ina, who has carol's public key, checks it with --verify:

ina@halof:~> gpg --verify message.sig
gpg: Signature made Sat 04 jul 2020 14:34:41 CEST
gpg:                using RSA key D18FA0021F644CDAF57FD0F919BBEFD16813034E
gpg: Good signature from "carol <carol@debian>" [unknown]

--verify only checks the signature. To also get the original text out, use --decrypt:

ina@halof:~> gpg --output message --decrypt message.sig
gpg: Signature made Sat 04 jul 2020 14:34:41 CEST
gpg:                using RSA key D18FA0021F644CDAF57FD0F919BBEFD16813034E
gpg: Good signature from "carol <carol@debian>" [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg:          There is no indication that the signature belongs to the owner.
ina@halof:~> cat message
This is the message to sign ...

The warning means only that ina has not marked carol's key as trusted. The signature itself is good.

--clearsign is the friendly variant: it creates file.asc, with the original text readable at the top and the signature below it. Anyone can read the message, and anyone who cares can --verify it:

$ gpg --clearsign message.txt
$ cat message.txt.asc
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

I'm glad that you reached the end of your LPIC study
-----BEGIN PGP SIGNATURE-----

iQGzBAEBCgAdFiEE4nloHcCTGKtbI9NZyQY90mNlmGoFAmUG/xYACgkQyQY90mNl
(...)
=q81y
-----END PGP SIGNATURE-----
You want Command
encrypt for someone gpg --output out --recipient USER --encrypt file
decrypt something sent to you gpg --output out --decrypt file
sign (binary output) gpg --output file.sig --sign file
sign, keeping the text readable gpg --clearsign file
check a signature gpg --verify file.sig
sign into a separate .sig, leaving the original untouched gpg --detach-sign file

--detach-sign makes a separate .sig file that sits beside the untouched original. The whole GnuPG workflow, in short:

$ gpg --export -a nagato > nagato.pub.key
$ gpg --import nagato.pub.key                              # what a recipient runs
$ gpg --recipient nagato@example.com --encrypt file.txt    # writes file.txt.gpg
$ gpg --output out.txt --decrypt file.txt.gpg              # needs your private key
$ gpg --output message.sig --sign message.txt              # signed (and compressed) file
$ gpg --verify message.sig                                 # check the signature
gpg: Good signature from "Nagato M <nagato@example.com>"
$ gpg --clearsign message.txt        # message.txt.asc, human-readable + signature
$ gpg --detach-sign report.pdf       # report.pdf.sig, verify against report.pdf
$ gpg --output nagato.revoke.asc --gen-revoke nagato@example.com

gpg-agent

gpg-agent does for GnuPG what ssh-agent does for SSH: it is the daemon that manages your private keys and remembers your passphrase for a while, so you do not type it for every operation. gpg starts it on demand. gpg-agent --help lists its options:

carol@debian:~$ gpg-agent --help
gpg-agent (GnuPG) 2.2.4
(...)
Syntax: gpg-agent [options] [command [args]]
Secret key management for GnuPG

Options:

     --daemon         run in daemon mode (background)
     --server         run in server mode (foreground)
     --supervised     run in supervised mode
 -v, --verbose        verbose
 -q, --quiet          be somewhat more quiet
 -s, --sh             sh-style command output
 -c, --csh            csh-style command output
(...)

Summary

Everything here rests on the key pair: a secret private key and a shareable public key. What the public key encrypts only the private key opens, and what the private key signs anyone can check with the public key. I keep private keys secret and hand out public keys freely.

With SSH, the first connection to a server shows its host key fingerprint, and accepting it saves the key in ~/.ssh/known_hosts (system-wide in ssh_known_hosts). If the key later changes, SSH warns of a possible man-in-the-middle attack, and I take that seriously: only once I am sure it is safe do I remove the old entry with ssh-keygen -R host. The server's host keys are the files /etc/ssh/ssh_host_*_key, that is ssh_host_<algorithm>_key (private, mode 0600) and .pub (public, 0644), and ssh-keygen -l -f shows their fingerprints. I create my own keys with ssh-keygen -t rsa|ecdsa|ed25519 (RSA is the default, DSA is deprecated), which gives me ~/.ssh/id_<algorithm> and id_<algorithm>.pub, such as ~/.ssh/id_rsa and id_rsa.pub or id_ed25519. For password-less, key-based login, my public key goes into ~/.ssh/authorized_keys on the server, by hand or with ssh-copy-id. I protect the private key with a passphrase and let ssh-agent with ssh-add remember it for the session.

SSH tunnels carry other traffic encrypted. -L local_port:host:port opens a port on my machine that leads through the server to the destination, -R port:host:port opens a port on the server that leads back to me (all interfaces only with GatewayPorts yes), -D makes a SOCKS proxy, and -N -f forwards without a shell, in the background. ssh -X runs graphical programs on the server with their windows on my screen, which needs X11Forwarding yes; -x turns it off.

GnuPG does the same for files. gpg --gen-key creates my keys in ~/.gnupg/, --list-keys shows my keyring, --export (with --armor for text) shares my public key and --import adds someone else's. If my key is compromised, I create a certificate with --gen-revoke, import it, and publish the revoked key. I encrypt with --recipient and --encrypt, decrypt with --decrypt, sign with --sign, --clearsign or --detach-sign to prove origin and integrity, and check signatures with --verify. gpg-agent remembers my GnuPG passphrase, just as ssh-agent does for SSH.