34 min read

On the LPIC-1 Exam 102: Security

This is a riveting series:

And, so is this one!

When studying for the Linux Professional Institute LPIC-1 certification, I took a bunch of notes when reading the docs and doing an online course. Note that this is not exhaustive, so do not depend on this article to get you prepared for the exam.

The menu items below are not in any order.

This roughly covers Topic 110: Security.

Caveat emptor.

Exam Details

LPIC-1 Exam 102

  • Exam Objectives Version: 5.0
  • Exam Code: 102-500

Topic 110: Security

Special File Permissions

Of course, I covered the special file permissions SUID and SGID waaaaaaaaaaaaay back in the LPIC-1 101 docs, but I’ll cover it again here, because it’s all about repetition, not perfection.


The SUID bit will allow the file to be executed with the privileges of the file’s owner. It is represented numerically by 4000 and symbolically by either s or S.

The classic example of an executable file with the SUID permission set is passwd:

$ ls -l $(which passwd)
-rwsr-xr-x 1 root root 63960 Feb  7  2020 /usr/bin/passwd

Let’s take a quick look at setting the SUID bit on a new file:

$ > derp
$ ls -l derp
-rw------- 1 btoll btoll 0 May 16 00:50 derp
$ chmod 4600 derp
$ ls -l derp
-rwS------ 1 btoll btoll 0 May 16 00:56 derp

Note that the SUID bit is set, but it is an uppercase S because the execute bit isn’t set (and it needs to be). Let’s do that now:

$ chmod u+x derp
$ ls -l derp
-rws------ 1 btoll btoll 0 May 16 00:56 derp

The lowercase s indicates that the execute bit is indeed set and that the file is now executable.

Kool moe dee.


The SGID bit can be set both on files and directories. With files, its behavior is equivalent to that of SUID but the privileges are those of the group owner.

When set on a directory, however, it will allow all files created therein to inherit the ownership of the directory’s group.

Note that the executable bit does not need to be set for the group to allow the files created in the directory to inherit the ownership of directory’s group.


$ mkdir derp
$ ls -ld derp
drwx------ 2 btoll btoll 4096 May 16 01:23 derp
$ chmod g+s derp
$ ls -ld derp
drwx--S--- 2 btoll btoll 4096 May 16 01:23 derp
$ sudo touch derp/berp
$ ls -l derp
total 0
-rw------- 1 root btoll 0 May 16 01:23 berp


Here’s a wrapup, the kind that usually only occurs on xmas eve:

  • to set both the SUID and the SGID
    • 6000
    • u+sx,g+s
  • just the SUID
    • 4000
    • u+sx
  • just the SGID
    • 2000
    • g+s

Finding File Permissions

It’s good to check for binaries with either SUID or SGID set (or both). Here are examples of checking for them in both symbolic and octal modes.


$ find /usr/?bin -perm /u=s
$ find /usr/?bin -perm /4000


$ find /usr/?bin -perm /g=s
$ find /usr/?bin -perm /2000

Both SUID and SGID:

$ find /usr/?bin -perm /u=s,g=s
$ find /usr/?bin -perm /6000

Note that you can use a hyphen (-) instead of the forward slash (/), which I believe is the older way of searching for permissions.

The substitution works for all except searching for both SUID and SGID using symbolic mode (/u=s,g=s).

Only omit both the - and the / when exactly matching the permissions. For example, study the following examples.

$ > homer
$ chmod u+s homer
$ ls -l homer
-rwS------ 1 btoll btoll 0 May 16 01:44 homer

The following command will find the file (note the second command matches exactly):

$ find -perm /4000
$ find -perm 4600

The following won’t (note that the / has been omitted):

$ find -perm 4000

Password Management and Aging


Get status of my account (-S or --status):

$ password -S
btoll P 01/02/2023 0 99999 7 -1

Let’s breakdown those fields like a bad Tom Petty cover band:

  • btoll
    • user login name
  • P
    • user has a valid password
    • other possible values for this field could be:
      • L = locked password
      • NP = no password
  • 01/02/2023
    • date of the last password change
  • 0
    • minimum age in days (the minimum number of days between password changes)
    • 0 means the password may be changed at any time
  • 99999
    • maximum age in days (the maximum number of days the password is valid for)
    • 99999 will disable password expiration
  • 7
    • warning period in days (the number of days prior to password expiration that a user will be warned)
  • -1
    • password inactivity period in days (the number of inactive days after password expiration before the account is locked)
    • a value of -1 will remove an account’s inactivity

In addition, here are some other useful options for managing accounts (must be have privileged permissions):

Option Description
-l, --lock lock the password of the named account (will put a ! before the encrypted password in /etc/shadow)
-u, --unlock unlock the password of the named account
-e, --expire immediately expire an account’s password (this forces a user to change their password at the user’s next login)
-d, --delete immediately expire an account’s password (this forces a user to change their password at the user’s next login)

Note that locking a user’s account does not disable the account. The user may still be able to login using another authentication token (e.g. an SSH key).

To disable the account, administrators should use usermod --expiredate 1 (this set the account’s expire date to Jan 2, 1970).

Users with a locked password are not allowed to change their password.


Here’s another view of the same information using chage:

$ chage -l btoll
Last password change                                    : Jan 02, 2023
Password expires                                        : never
Password inactive                                       : never
Account expires                                         : never
Minimum number of days between password change          : 0
Maximum number of days between password change          : 99999
Number of days of warning before password expires       : 7

To enter interactive mode, run without options:

$ sudo chage btoll


As its name implies, the usermod utility also allows you to modify user information like locking and password expiry information.

Option Description
-L, --lock lock the password of the named account (will put a ! before the encrypted password in /etc/shadow)
-U, --unlock unlock the password of the named account
-f, --inactive the number of days after a password expires until the account is permanently disabled

Discovering Open Ports


You can list open files using the lsof tool. Since everything in Linux is a file, this can return a huge amount of information (regular files, device files, sockets, named pipes, etc.).

Check for all Internet network files:

$ lsof -i

Check for all Internet network files for only IPv4:

$ lsof -i4

Check for all Internet network files for only IPv6:

$ lsof -i6

List open network files on a particular IP address (it can also do name resolution):

$ lsof -i@
$ lsof -i@
$ lsof -i@lucullus

Or, a port(s):

$ lsof -i:8000
$ lsof -i:8000,22

If not all of the ports are listed, use the sudo command to escalate your privileges.

$ sudo lsof -i@,22

Or, both:

$ lsof -i@

List only v4 network files with TCP state LISTEN:

$ lsof -i4tcp -stcp:listen

List by service:

$ sudo lsof -i@,ssh

Of course, you can separate multiple ports using commas (,) and ranges using hyphens (-).

There are so many great uses for lsof that are beyond the scope (it seems) of what’s expected to be known for the LPIC-1 certification exam.

You can open files by:

  • user
  • command (i.e., ssh)
  • process
  • a directory (i.e., the files contained within the directory)

Of course, with the support for logical conditions (AND and OR), most of these (all?) can be mixed and matched.


Next up, the fuser tool is used to identify processes using files or sockets. You can think of its purpose is to find a “file’s user”.

Here are some examples using the cwd:

$ fuser .
/home/btoll:          5887c  5889c  5904c  5911c  5943c  5965c  5966c  5984c  5988c  6289c  6295c  6340c  6342c  6343c  6345c  6346c  6349c  6350c  6352c  6357c  6376c  6377c  6378c  6447c  6508c  6561c  6566c  6794c  6945c  8072c  8086c  8282c
$ fuser . -v
                     USER        PID ACCESS COMMAND
/home/btoll:         btoll      5887 ..c.. pipewire
                     btoll      5889 ..c.. bash
                     btoll      5904 ..c.. dbus-daemon
                     btoll      5911 ..c.. pipewire-media-
                     btoll      5943 ..c.. startx
                     btoll      5965 ..c.. xinit
                     btoll      5966 ..c.. Xorg
                     btoll      5984 ..c.. sh
                     btoll      5988 ..c.. tmux: server
                     btoll      6289 ..c.. bash
                     btoll      6295 ..c.. i3
                     btoll      6340 ..c.. sh
                     btoll      6342 ..c.. kitty
                     btoll      6343 ..c.. sh
                     btoll      6345 ..c.. firefox-esr
                     btoll      6346 ..c.. sh
                     btoll      6349 ..c.. thunderbird

Ok, that’s my home directory, so there’s bound to be a lot of action there, much like the backseat of a car on prom night.

Let’s look at a more narrow example. The following is the output from the content directory of my website on my local machine:

$ fuser .
/home/btoll/projects/benjamintoll.com:  6430c 75409c 129562c 129645c
$ fuser . -v
                     USER        PID ACCESS COMMAND
                     btoll      6430 ..c.. bash
                     btoll     75409 ..c.. vim
                     btoll     129562 ..c.. bash
                     btoll     129645 ..c.. ssh

This may be a better example of a real-world example of when you’re trying to get a view of what could be happening in a particular corner of your filesystem.

As for the ACCESS column, here are the possible values and their meaning:

Option Description
-c Current directory.
-e Executable being run.
-f Open file (omitted in default display mode).
-F Open file for writing (omitted in default display mode).
-r Root directory.
-m mmap‘ed file or shared library.
. Placeholder (omitted in default display mode).

Here’s an example using the -n or --namespace option to find information about network ports and sockets. Just supply the protocol and port:

$ fuser -vn tcp 8000
                     USER        PID ACCESS COMMAND
8000/tcp:            btoll     19121 F.... python

You can also use fuser to kill a process with the -k or --kill switch.


The netstat tool is part of the net-tools package, at least on Debian. Per its man page, it can do all of the following things:

  • print network connections
  • routing tables
  • interface statistics
  • masquerade connections
  • multicast memberships

Pretty nifty, I’d say, and I do say.

Option Description
-l, --listening Show only listening sockets (omitted by default).
-p, --program Show the PID and name of the program to which each socket belongs.
-t, --tcp Show only TCP connections.
-u, --udp Show only TCP connections.
-e, --extend Display additional information. Use this option twice for maximum detail.
-n, --numeric Resolve hostnames.
-a, --all Show both listening and non-listening sockets.
--numeric-ports Shows numerical port numbers but does not affect the resolution of host or user names.

Note that if -l is ommitted, only the established connections are shown. Also, you can combine both -t and -u if you’re looking for a good time.


nmap is a network exploration tool and security / port scanner.

nmap allows you to scan:

  • single host
    • nmap kilgore-trout.local
  • multiple hosts
    • nmap localhost
  • host ranges
    • nmap
  • subnets
    • wildcard or CIDR notation TODO remove backslash below
    • nmap 192.168.1.\*
    • nmap

You can scan the entire Internet, if you want.

systemd sockets

There is another way to view the open ports on a system, although I don’t know if this is covered on the exam. Anyway, there could be a service that is listening on a systemd-socket:

$ systemctl list-unit-files --type=socket

The ListenStream option will define if the service is to use a Unix socket, port, IP address, et al., to use as its connection type.

The following example is taken from the article The End of the Road: systemd’s “Socket” Units.


# echo.socket
Description = Echo server

ListenStream = 4444
Accept = yes

WantedBy = sockets.target


Description=Echo server service



import sys
sys.stdout.write(sys.stdin.readline().strip().upper() + 'rn')

The utility socat is a great tool to test this. socat creates two bidirectional byte streams and transfers data between them.

$ chmod +x socketthing.py
$ socat - TCP:
hello computer


User Limits

The ulimit utility is a bash builtin. It provides control over the resources available to processes started by the shell, on systems that allow such control.

$ ulimit
Option Description
-H change and report the hard limit associated with a resource
-S change and report the soft limit associated with a resource
-b maximum socket buffer size
-f maximum size of files written by the shell and its children
-l maximum size that may be locked into memory
-m maximum resident set size (RSS) — the current portion of memory held by a process in main memory (RAM)
-s maximum stack size (in KB)
-v maximum amout of virtual memory
-u maximum number of processes available to a single user
$ ulimit -u
$ ulimit -s

Here’s a quick example. If the size of the stack isn’t increased, the following toy program will fail with a segmentation fault:

#include <stdlib.h>

int main() {
  char foo[10000000];
  foo[0] = 'a';

A hard limit cannot be increased by a non-root user once it is set, but a non-root can decrease a hard limit. A soft limit may be increased up to the value of the hard limit by the non-root user.

To get help on the command line, issue the command help ulimit.

To set new limits on a particular resource, specify either the -S or the -H option, followed by the corresponding resource option and the new value. This value can be a number or the special words soft (current soft limit), hard (current hard limit) or unlimited (no limit). If neither -S or -H is specified, both limits will be set.

Any changes made using ulimit do not persist across reboots. To make the changes permanent, edit the /etc/security/limits.conf file. Note that this file can also be used to restrict resources by user.

For example, here is a snippet of the file that shows how this per-user restriction can be accomplished. Note that the entries are commented-out:

#*               soft    core            0
#root            hard    core            100000
#*               hard    rss             10000
#@student        hard    nproc           20
#@faculty        soft    nproc           20
#@faculty        hard    nproc           50
#ftp             hard    nproc           0
#ftp             -       chroot          /ftp
#@student        -       maxlogins       4

Logged-In Users


The last utility is part of the util-linux package:

$ dpkg -S $(which last)
util-linux: /usr/bin/last

It shows a listing of last logged in users with the most recent on top:

$ last -n10
btoll    tty1                          Mon May 15 15:11   still logged in
reboot   system boot  5.10.0-21-amd64  Mon May 15 15:09   still running
btoll    tty1                          Wed May 10 21:27 - crash (4+17:42)
reboot   system boot  5.10.0-21-amd64  Wed May 10 21:27   still running
btoll    :0           :0               Wed May 10 20:47 - down   (00:34)
btoll    tty1                          Mon Apr 24 21:04 - down  (16+00:17)
reboot   system boot  5.10.0-21-amd64  Mon Apr 24 21:04 - 21:21 (16+00:17)
btoll    tty1                          Wed Apr  5 18:03 - down  (19+02:59)
reboot   system boot  5.10.0-21-amd64  Wed Apr  5 18:03 - 21:03 (19+03:00)
btoll    :0           :0               Wed Apr  5 18:00 - down   (00:01)

wtmp begins Mon Jan  2 17:06:48 2023

Note that the system was rebooted several times using kernel 5.10.0-21-amd64.

The last line begins with wtmp, which is the binary file /var/log/wtmp from which last gets its data.

Your system may not have that file. The last man page addresses this:

The files wtmp and btmp might not be found. The system only logs information in these files if they are present. This is a local configuration issue. If you want the files to be used, they can be created with a simple touch(1) command (for example, touch /var/log/wtmp).

To see all the logins for a particular user, provide the name after the command:

$ last btoll


The lastb utility is also part of the util-linux package:

$ dpkg -S $(which lastb)
util-linux: /usr/bin/lastb

lastb is the same as last, except that by default it shows a log of the /var/log/btmp file, which contains all the bad login attempts.

The LPIC-1 docs don’t seem to care about this tool.


The who utility shows who is currently logged into the system. There may be more users currently using the system that are not listed in the output, because not all programs use utmp logging.

Also this warning from the man page:

Warning: utmp must not be writable by the user class “other”, because many system programs (foolishly) depend on its integrity. You risk faked system logfiles and modifications of system files if you leave utmp writable to any user other than the owner and group owner of the file.

It is part of the coreutils package:

$ dpkg -S $(which who)
coreutils: /usr/bin/who

A file can be provided as an argument that is used to look up the information, but if one isn’t provided, who will use the binary file /var/run/utmp (it is common to use /var/log/wtmp).

$ who
btoll    tty1         2023-02-13 15:06

Check out the difference when the /var/log/wtmp file is given as an argument:

$ who | wc -l
$ who /var/log/wtmp | wc -l

Include the -a|--all switch to also print the current runlevel, time of last system boot, and more.

$ who -a
           system boot  2023-02-13 15:05
btoll    - tty1         2023-02-13 15:06  old         5897
           run-level 3  2023-02-13 15:06

Here are some useful options:

Option Description
-H, --heading print line of column headings
-T, -w, --mesg add user’s message status as +, - or ?
-a, --all same as -b -d --login -p -r -t -T -u
-b, --boot time of last system boot
-q, --count all login names and number of users logged on
-r, --runlevel print current runlevel


The w utility shows who is logged on and what they are doing. It also gives more verbose output than who.

It is part of the procps package:

$ dpkg -S $(which w)
procps: /usr/bin/w

Let’s take a look at its usage:

$ w
 02:21:38 up 1 day, 11:11,  1 user,  load average: 1.59, 1.21, 0.66
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
btoll    tty1     -                Mon15   35:10m 28:30   0.00s xinit /home/btoll/.xinitrc -- /etc/X11/xinit/xserverrc :0 vt1

The first line (the header) is familiar to tools such as uptime and top in that it gives information about:

  • the current time (02:21:38)
  • how long the system has been up and running (1 day)
  • the number of currently logged in users (1 user)
  • the load average numbers (load average: 1.59, 1.21, 0.66)
    • these values refer to the number of jobs in the run queue averaged over the last 1, 5 and 15 minutes, respectively.

The information in the header is the same as that given by the uptime utility.

Here is a list of the columns:

Column Description
USER Login name of user.
TTY Name of terminal the user is on.
FROM Remote host from which the user has logged on.
LOGIN@ Login time.
IDLE Idle time.
JCPU Time used by all processes attached to the tty (including currently running background jobs).
PCPU Time used by current process (the one showing under WHAT).
WHAT Command line of current process.

Lastly, as with who, w accepts a user name:

$ w btoll


Everybody knows about sudo. It even has a theme song.

The default security policy is sudoers, and the file that controls the policy is /etc/sudoers (your distribution may also have an @includedir directive in /etc/sudoers to also parse any files in /etc/sudoers.d/).

Indeed, /etc/sudoers is the place where a user’s sudo privileges are determined. In other words, here you will specify who can run what commands as what users on what machines, as well as other settings.

Here’s a sample /etc/sudoers file:

Defaults        env_reset
Defaults        mail_badpass
Defaults        secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
Defaults        editor=/usr/bin/vim
Defaults        timestamp_timeout=60

# Host alias specification

# User alias specification

# Cmnd alias specification

# User privilege specification
root    ALL=(ALL:ALL) ALL

# Allow members of group sudo to execute any command
%sudo   ALL=(ALL:ALL) ALL

# See sudoers(5) for more information on "@include" directives:

@includedir /etc/sudoers.d

Here’s how to read these entries. The privilege specification for the root user is ALL=(ALL:ALL) ALL. This translates as:

  • user root can log in from all hosts ALL, as all users and all groups (ALL:ALL), and run all commands ALL
  • the same is true for members of the sudo group (group names are identified by a preceding percent sign %)

Note that I set vim as the default visudo editor because nano is for losers.

You could also do it at runtime by augmenting the child process’ environment:

$ sudo EDITOR=/usr/bin/vim visudo

Of course, you don’t directly edit the sudoers config file, you instead use the visudo utility.

From /etc/sudoers.d/README:

Finally, please note that using the visudo command is the recommended way to update sudoers content, since it protects against many failure modes. See the man page for visudo for more information.


To prevent all users but root from logging into a system, create the file /etc/nologin. The file can contain a nice message about why you’re not worthy to log in, little fella.

In addition, there’s also the nologin binary. This can be used to prevent a user from logging in by setting it as the their default shell:

$ sudo usermod --shell /usr/sbin/nologin jeff_bezos


Seriously, xinetd? I don’t think anyone has used this in the past 500 years. Apparently, systemd-socket is the spiritual successor of the superdaemon.

Unnecessary Services

$ service --status-all | head
 [ - ]  acpid
 [ - ]  alsa-utils
 [ - ]  anacron
 [ + ]  apparmor
 [ + ]  atd
 [ + ]  avahi-daemon
 [ + ]  bluetooth
 [ - ]  console-setup.sh
 [ + ]  cron
 [ - ]  cryptdisks
$ sudo systemctl disable atd.service --now
Synchronizing state of atd.service with SysV service script with /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install disable atd
Removed /etc/systemd/system/multi-user.target.wants/atd.service.
$ systemctl status atd
● atd.service - Deferred execution scheduler
     Loaded: loaded (/lib/systemd/system/atd.service; disabled; vendor preset: enabled)
     Active: inactive (dead)
       Docs: man:atd(8)
$ sudo service --status-all | head
 [ - ]  acpid
 [ - ]  alsa-utils
 [ - ]  anacron
 [ + ]  apparmor
 [ - ]  atd
 [ + ]  avahi-daemon
 [ + ]  bluetooth
 [ - ]  console-setup.sh
 [ + ]  cron
 [ - ]  cryptdisks
# Debian
$ sudo update-rc.d SERVICE-NAME remove
# Red Hat
$ sudo chkconfig SERVICE-NAME off

The following two commands will also list listening network services. The first uses the legacy (deprecated?) utility netstat from the net-tools package:

$ netstat -ltu | head
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 localhost:1042*               LISTEN
tcp        0      0 localhost:1143*               LISTEN
tcp        0      0 localhost:ipp *               LISTEN
tcp        0      0 localhost:smtp*               LISTEN
tcp        0      0 localhost:9050*               LISTEN
tcp        0      0  *               LISTEN
tcp        0      0 localhost:1025*               LISTEN
udp        0      0   *

The second uses ss and is equivalent in functionality. Moving forward, this should be used instead of netstat:

$ ss -ltu | head
Netid State  Recv-Q Send-Q                      Local Address:Port          Peer Address:PortProcess
udp   UNCONN 0      0                             *
udp   UNCONN 0      0                           *
udp   UNCONN 0      0                            *
udp   UNCONN 0      0      [fe80::f167:8747:fb33:88bc]%wlp3s0:dhcpv6-client         [::]:*
udp   UNCONN 0      0                                    [::]:53611                 [::]:*
udp   UNCONN 0      0                                    [::]:mdns                  [::]:*
tcp   LISTEN 0      4096                       *
tcp   LISTEN 0      4096                       *
tcp   LISTEN 0      128                         *

TCP Wrappers

TCP Wrappers is a host-based access-control list (ACL) system that uses a library named libwrap to filter network access to servers.

Initially, only super servers like xinetd got wrapped, but it has evolved to allow for common network service daemons such as sshd to be linked to libwrap.

TCP Wrappers is used by tools like Fail2ban because they can add and remove rules at runtime, which gives TCP Wrappers the benefit of runtime ACL reconfiguration. This is beneficial for detecting errant behavior on-the-fly, and the services don’t need to be reloaded or restarted since the rules aren’t written to a daemon’s configuration file.

For example, Fail2ban may add the IP addresses to temporarily block to /etc/hosts.deny.

Determining if a binary has been compiled with support (that is, it needs to be linked against the libwrap.so shared object) for TCP Wrappers is easy:

$ ldd $(which sshd) | grep libwrap
        libwrap.so.0 => /lib/x86_64-linux-gnu/libwrap.so.0 (0x00007fc7bb86e000)
$ ldd $(which pulseaudio) | grep libwrap
        libwrap.so.0 => /lib/x86_64-linux-gnu/libwrap.so.0 (0x00007f856458b000)

To filter traffic, add configurations to /etc/hosts.allow and /etc/hosts.deny. The format for an entry is:

service: host/network [: <option>: <option>: ...]

Like other filtering tools, if a definition is found in /etc/hosts.allow, the tool stops searching and will not then also look in /etc/hosts.deny.

Here are some examples:


sshd: [2a02:2149:88f1:4c00:9991:9daa:b580:aee1]


sshd: .benjamintoll.com


ALL: LOCAL @some_netgroup
ALL: .foobar.edu EXCEPT terminalserver.foobar.edu

Another from /etc/hosts.deny (excerpted from my Debian bullseye machine):

# The PARANOID wildcard matches any host whose name does not match its
# address.
# You may wish to enable this to ensure any programs that don't
# validate looked up hostnames still leave understandable logs. In past
# versions of Debian this has been the default.


ssh client

Creating a Key

$ ssh-keygen -t ecdsa -b 521
Option Description
-b The size in bits
-t The algorithm

This will create the following two files in your .ssh folder:

  • id_ecdsa
  • id_ecdsa.pub

View the fingerprint of a key:

$ ssh-keygen -lf ~/.ssh/id_ed25519.pub
256 SHA256:SWKTTL379YjNoNItlvUftX5lfL17js22NMrG840Oibc kilgore-trout@example.com (ED25519)

To also view its art:

$ ssh-keygen -vlf ~/.ssh/id_ed25519.pub
256 SHA256:SWKTTL379YjNoNItlvUftX5lfL17js22NMrG840Oibc kilgore-trout@example.com (ED25519)
+--[ED25519 256]--+
|      ..         |
|     o ..        |
|      * ..       |
|     . +..       |
|        So     . |
|        * . .   =|
|       o.*.*.o =+|
|      ..+++ *=*=*|
|       ..Eoo+==X@|
Option Description
-f The input key file
-l Show fingerprint of specified public key file
-v Verbose mode (shows ASCII art)

Here are the four algorithms that can be used for the key:

  • RSA
    • Named after Ron Rivest, Adi Shamir and Leonard Adleman
  • DSA
    • Digital Signature Algorithm
    • No longer considered secure
    • Has a fixed length length of only 1024 bits
    • Deprecated as of OpenSSH 7.0
  • ecdsa
    • Elliptic Curve Digital Signature Algorithm
    • An improved implementation of DSA using elliptical curve cryptography
    • Key length is determined by three possible elliptic curve sizes (in bits):
      • 256
      • 384
      • 521
  • ed25519
    • Edwards-curve Digital Signature Algorithm
    • Considered to be the most secure
    • Has a fixed length of 256 bits

ssh-keygen uses RSA by default.


For logging into a server using your public key, here are some ways of getting the public key into the destination machine’s .ssh/authorized_keys file:

Using ssh-copy-id:

$ ssh-copy-id -i ~/.ssh/id_ecdsa.pub derp@

Using scp:

$ scp ~/.ssh/id_ecdsa.pub derp@

Then, you’d log in and copy the key into the file (or, create it if it doesn’t exist).


$ cat ~/.ssh/id_ecdsa.pub | ssh derp@ 'cat >> .ssh/authorized_keys'

Or, close your eyes and wish for it to happen.

Once your public key is in the host’s authorized_keys file, you are allowed to login (this is assuming that only public key access is configured in the sshd_config file), you will still be asked for a password1. Why?

SSH is asking for the passphrase that you used to protect your key. This is a good thing. Don’t use this as an excuse to then create a key without a passphrase, like a spoiled little baby.

Instead, use the ssh-agent.

1 This assumes that you’ve created an SSH key that is protected with a passphrase. Of course, you can create one without a passphrase that would be completely insecure and not prompt for a password when logging into a remote machine, but only a tech bro would do a completely imbecilic thing like that.


The ssh-agent can be used to cache the passphrase. This will then act as your agent with the remote ssh server and transparently log you in with the passphrase.

There are a couple of ways to do this. First, you can call the agent explicitly, which will create a new shell in which the agent is running. This subshell will have all of the agent’s environment variables exported into it and will then be inherited every other process that is created by the shell:

$ ssh-agent /bin/bash

You can have then have it cache your password by executing the ssh-add command, which will ask for the passphrase:

$ ssh-add

That’s it. You can then use ssh, and ssh-agent will authenticate you transparently. Note, though, that as soon as you exit the shell that the environment will go bye-bye and you will no longer enjoy the fruits of ssh-agent’s labor.

The second method is more more practical. Simply export the agent’s environment in one of your shell run command scripts, like a login script:

eval $(ssh-agent)

Or, after logging in to your local host, in one fell swoop:

$ eval $(ssh-agent) && ssh-add

What are the environment variables that are exported, you wonder? Wonder no more, friend:

$ ssh-agent -s
SSH_AUTH_SOCK=/tmp/ssh-pq9y8ZBxYqAw/agent.62284; export SSH_AUTH_SOCK;
echo Agent pid 62285;


Note that ssh-add only adds the passphrase to volatile memory, it does not write anything to disk.

Deleting a known_hosts Entry

If you get a big scary message when ssh-ing into a remote machine that looks something like this:

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.

You can remove all ECDSA public key fingerprints for the IP address or hostname or domain using the following command:

$ ssh-keygen -f "/home/btoll/.ssh/known_hosts" -R

To delete all keys for that same IP address:

$ ssh-keygen -R
Option Description
-f The input key file
-R The hostname

Port Forwarding

Here’s a nice article about SSH and port forwarding for your reading pleasure.


$ ssh -L 8585:www.gnu.org:80 debian
$ ssh -L 8585:www.gnu.org:80 -Nf poop@


$ ssh -R 8585:localhost:80 -Nf poop@


Not covered by the LPIC-1 exam, sadly.

X11 Forwarding

Through an X11 tunnel, the X Window System on the remote host is forwarded to your local machine.

$ ssh -X poop@dollop

Now, when you launch a web browser, the browser will be run on the remote server, but its display will be forwarded to your local machine. Note that it could be any graphical application, it doesn’t need to be a web browser. For example, it could be a sweet app like Nip Alert.

Also known as X11 tunneling.

Lastly, here are the three sshd configurations that control all of the above behavior:

  • AllowTcpForwarding
  • GatewayPorts
  • X11Forwarding


The gpg utility is an OpenPGP encryption and signing tool. It can use both symmetric and asymmetric keys for encryption.

To print a list of all options and commands:

$ gpg --dump-options

Also, a good command to know about is gpgconf in which you can query and modify configuration files in the gnupg home directory (usually $HOME/.gnupg):

$ gpgconf
gpg-agent:Private Keys:/usr/bin/gpg-agent
pinentry:Passphrase Entry:/usr/bin/pinentry


To generate a new keypair, use --gen-key|--generate-key or --full-gen-key|--ful-generate-key. The latter generates the keypair with dialogs for all of the options (it’s an extended version of --generate-key).

I’ve understood that it’s better to use --full-generate-key because you can specify the algorithm, key length and when the key should expire. These are the extensions to the --gen-key switch mentioned above.

Either option will not only create the keypair but sign it. A revocation certificate is also generated (on my Debian bullseye machine, it put the certificate in $HOME/.gnupg/openpgp-revocs.d).

Note that you may be asked to bang on the keyboard and mouse to increase the amount of entropy when creating the keypair. This may be because you don’t have an daemon to feed random from an entropy source (like a hardware random number generator) to an entropy sink (like a kernel entropy pool).

To remedy this horrifying situation, install rng-tools. This will install the rng-tools.service and start it.

If you want to manually point the daemon at another entropy source, you can run the following command before generating your keypair:

$ sudo rngd -r /dev/urandom

The -r switch is shorthand for --rng-device. The default file is /dev/hwrng.

There is also a --quick-gen-key|--quick-generate-key option that isn’t covered on the exam.


Export the public key in binary OpenPGP format:

$ gpg --export "Benjamin Toll" > benjam72.pub
$ file benjam72.pub
benjam72.pub: PGP/GPG key public ring (v4) created Tue Feb  7 15:16:39 2023 RSA (Encrypt or Sign) 3072 bits MPI=0xdf8388d842cbedfc...

Use the -a|--armor option to email the pubkey(s) (known as ASCII armor). The following command sends prints the pubkey to stdout:

$ gpg --export --armor "Benjamin Toll"



Import and merge keys. These can be public or private keys, as well as key revocation certificates.

Import your best friend’s public key:

gpg --import kilgore-trout.pub.key

Import a revocation certificate. This will automatically revoke the respective key.

$ gpg --import revocation_file.asc


To list all public keys in your keyring, use any of the following synonyms:

  • --list-keys
  • -k
  • --list-public-keys

To list all private keys:

  • --list-secret-keys
  • -K

Note that # after the initial tags sec or ssb means that the secret key or subkey is currently not usable. In addition, > after these tags indicate that the key is stored on a smartcard.

Use the --with-colons switch for safe use in scripts.


To create a revocation certificate:

$ gpg --output revocation_file.asc --gen-revoke benjam72@yahoo.com

A synonym of --gen-revoke is --generate-revocation.

To revoke the key, simply import (--import) the generated revocation certificate into your keyring. When listing the key again, you will now see that it’s been revoked:

$ gpg --import revocation_file.asc
$ gpg -k foo-manchu
pub   rsa3072 2023-02-17 [SC] [revoked: 2023-02-17]
uid           [ revoked] foo-manchu <foo-manchu@example.com>

(Re-)upload your key to a keyserver and/or distribute the revocation certificate to all of your friends and have them import it into their keyring, as well.

Note that you can just use the generated revocation certificate when the keypair was created instead of creating a new one. However, you may want to specify the specific reason for the revocation, which is a good reason to generate a new one.

Interestingly, there’s this safeguard in the one that it’s created upon key creation:

To avoid an accidental use of this file, a colon has been inserted
before the 5 dashes below.  Remove this colon with a text editor
before importing and publishing this revocation certificate.

Comment: This is a revocation certificate

This safeguard is not present if later creating a key revocation cert.



To encrypt using a symmetric cipher, use the -c|--symmetric option.

It will ask for a passphrase and then give the new encrypted file a .gpg extension.

$ echo whatever > foo-manchu.txt
$ gpg -c foo-manchu.txt
$ file foo-manchu.txt.gpg
foo-manchu.txt.gpg: GPG symmetrically encrypted data (AES256 cipher)

On my machine, it defaulted to using AES256, as seen from the output of the file command above. However, you can choose another cipher with the inclusion of the --cipher-algo option.

$ gpg -c --cipher-algo BLOWFISH foo-manchu.txt
$ file foo-manchu.txt.gpg
foo-manchu.txt.gpg: GPG symmetrically encrypted data (BLOWFISH cipher)

To see what ciphers are supported, use the previous command with the --version switch:

$ gpg -c --cipher-algo foo-manchu.txt --version
gpg (GnuPG) 2.2.27
libgcrypt 1.8.8
Copyright (C) 2021 Free Software Foundation, Inc.
License GNU GPL-3.0-or-later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Home: /home/btoll/.gnupg
Supported algorithms:
Hash: SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224
Compression: Uncompressed, ZIP, ZLIB, BZIP2


Encrypt so it is safe to email:

$ gpg --recipient benjam72@yahoo.com --encrypt --armor foo-manchu.txt
$ cat foo-manchu.txt.asc


Encrypt and sign (also armored). Note that the signature increases the size of the file.

$ gpg --recipient benjam72@yahoo.com --encrypt --sign --armor foo-manchu.txt
$ cat foo-manchu.txt.asc



Use either the --decrypt or -d switch.

To decrypt a nice encrypted message that was sent by a pal, issue the following command:

$ gpg -d foo-manchu.txt.asc

This will write to stdout unless you specify an out file destination.


A file can be signed and not encrypted. Let’s look at some examples to understand more fully.

Sign an unencrypted file with the default private key. This will create a binary file with the .gpg extension if the --output option isn’t given:

$ gpg --sign foo-manchu.txt
$ file foo-manchu.txt.gpg
foo-manchu.txt.gpg: data

Create a cleartext signature, that is, one that can be read without the need of any special software:

$ gpg --clearsign foo-manchu.txt
$ file foo-manchu.txt.asc
foo-manchu.txt.asc: PGP signed message
$ cat foo-manchu.txt.asc
Hash: SHA512



In contrast with the binary signature file, the cleartext signature was given an extension of .asc.

Note that --clearsign is a synonym for --clear-sign.

To make a detached signature, use --detach-sign or -b. This will a separate binary signature file that is given the .sig extension by default, and it can be verified using the --verify switch as usual:

$ file foo-manchu.txt.sig
foo-manchu.txt.sig: data

Use --default-key or --local-user to specify another private key to do the signing.

Sign and Encrypt

Symmetrically encrypt and sign:

$ gpg --symmetric --sign foo-manchu.txt
gpg: AES256 encryption will be used
$ file foo-manchu.txt.gpg
foo-manchu.txt.gpg: GPG symmetrically encrypted data (AES256 cipher)

Note that you can’t encrypt and clearsign. Of course, this makes sense:

$ gpg --symmetric --clearsign foo-manchu.txt
gpg: conflicting commands

Asymmetrically encrypt and sign for the benjam72 recipient:

$ gpg --recipient benjam72 --encrypt --sign foo-manchu.txt
$ file foo-manchu.txt.gpg
foo-manchu.txt.gpg: PGP RSA encrypted session key - keyid: 07E146DE 62126CB0 RSA (Encrypt or Sign) 3072b .

You can encrypt more than one file at once using the --multiline switch:

$ gpg --recipient benjam72@yahoo.com --encrypt --multifile [f,m]oo-manchu.txt
$ file [f,m]oo-manchu.txt.gpg
foo-manchu.txt.gpg: PGP RSA encrypted session key - keyid: 07E146DE 62126CB0 RSA (Encrypt or Sign) 3072b .
moo-manchu.txt.gpg: PGP RSA encrypted session key - keyid: 07E146DE 62126CB0 RSA (Encrypt or Sign) 3072b .


Use --delete-keys to delete a public key from the keyring. However, if there is also a secret key for the public key, then you will get a warning that you first must remove the secret private key:

$ gpg --delete-keys foo-manchu
gpg (GnuPG) 2.2.27; Copyright (C) 2021 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: there is a secret key for public key "foo-manchu"!
gpg: use option "--delete-secret-keys" to delete it first.

You will be asked three or four times if you’re sure that you want to remove the key:

$ gpg --delete-secret-keys foo-manchu
gpg (GnuPG) 2.2.27; Copyright (C) 2021 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.

sec  rsa3072/FD717513DE6940D8 2023-02-17 foo-manchu <foo-manchu@example.com>

Delete this key from the keyring? (y/N) y
This is a secret key! - really delete? (y/N) y

You will then be able to remove the public key:

$ gpg --delete-keys foo-manchu
gpg (GnuPG) 2.2.27; Copyright (C) 2021 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.

pub  rsa3072/FD717513DE6940D8 2023-02-17 foo-manchu <foo-manchu@example.com>

Delete this key from the keyring? (y/N) y

If you know that you want to remove both, then this is your lucky day. Use the --delete-secret-and-public-key to remove both in one fell swoop. It will remove the private key first, which means that it will ask you several times if you’re sure that you want to perform the action.

Uploading and Retrieving From a Keyserver

Upload a key to a keyserver:

$ gpg --keyserver keyserver-name --send-keys KEY-ID

For others to then get the key from a keyserver:

$ gpg --keyserver keyserver-name --recv-keys KEY-ID


The gpg-agent is a daemon that manages private keys and stores keys in memory. When gpg needs to perform an operation, it will ask the daemon for the key material, and if it doesn’t have it, will get it from the keyring, thus prompting for a password (and raising your pinentry program).

Essentially, it stores keys in memory so you don’t have to keep entering passphrases every time you invoke gpg.

Like its cousin gpg, the gpg-agent has a switch to dump all of its available options and commands:

$ gpg-agent --dump-options


Congratulations, you’re now an expert in what I think is important to know about the LPIC-1 exam.