← Back
Script House — Linux Admin

Chapter 11: Encryption Lab

Hands-on practice with GPG, openssl, and SSH key management

Lab Overview

Encryption is a core sysadmin competency. This lab covers the three primary Linux encryption toolchains: GPG for file and email encryption, OpenSSL for symmetric file encryption and certificate work, and SSH key pairs for passwordless authentication. LUKS disk encryption is covered conceptually — live disk exercises require a dedicated test VM.

1

GPG Symmetric Encryption

Encrypt and decrypt files with a passphrase using GPG

BEGINNER
Symmetric vs. Asymmetric Symmetric encryption uses the same key to encrypt and decrypt. It is fast and suitable for local file protection or sharing over a secure channel. GPG's -c flag enables symmetric mode — no key pair required.
1

Create a workspace and a secret test file:

mkdir -p ~/lab-encryption cd ~/lab-encryption echo "TOP SECRET: Server root password is Hunter2!" > secret.txt cat secret.txt
TOP SECRET: Server root password is Hunter2!
2

Encrypt the file symmetrically with GPG. Use --batch --passphrase to avoid the interactive prompt in scripts:

gpg --symmetric --cipher-algo AES256 \ --batch --passphrase "MyStrongPass123!" \ --output secret.txt.gpg \ secret.txt ls -lh secret.txt secret.txt.gpg
-rw-rw-r-- 1 user user 46 Jan 15 09:10 secret.txt -rw-rw-r-- 1 user user 111 Jan 15 09:10 secret.txt.gpg

The .gpg file is binary ciphertext — its contents are unreadable without the passphrase.

3

Verify the file is genuinely encrypted (should show gibberish or error):

file secret.txt.gpg
secret.txt.gpg: GPG symmetrically encrypted data (AES256 cipher)
4

Decrypt the file — confirm you recover the original content:

gpg --decrypt \ --batch --passphrase "MyStrongPass123!" \ --output secret-decrypted.txt \ secret.txt.gpg cat secret-decrypted.txt
gpg: AES256 encrypted data gpg: encrypted with 1 passphrase TOP SECRET: Server root password is Hunter2!
5

Try to decrypt with the wrong passphrase and observe the error:

gpg --decrypt \ --batch --passphrase "WrongPassword" \ --output /dev/null \ secret.txt.gpg 2>&1
gpg: AES256 encrypted data gpg: encrypted with 1 passphrase gpg: decryption failed: Bad session key

This confirms the encryption is working correctly.

2

GPG Key Pair Operations

Generate keys, export/import, encrypt for a recipient, and digitally sign

INTERMEDIATE
Asymmetric Workflow Public key cryptography uses a key pair: the public key encrypts (share freely), the private key decrypts (keep secret). GPG also supports digital signatures — signing with your private key lets recipients verify with your public key.
1

Generate a GPG key pair in batch mode (no interactive prompts):

cat > ~/lab-encryption/keygen-params.txt <<'EOF' %no-protection Key-Type: RSA Key-Length: 4096 Name-Real: Test User Name-Email: testuser@lab.local Expire-Date: 0 %commit EOF gpg --batch --gen-key ~/lab-encryption/keygen-params.txt
gpg: key A1B2C3D4E5F6 marked as ultimately trusted gpg: directory '/home/user/.gnupg/openpgp-revocs.d' created gpg: revocation certificate stored as '.../A1B2C3D4E5F6.rev'
2

List your keyring to confirm the key was created:

gpg --list-keys testuser@lab.local
pub rsa4096 2025-01-15 [SC] A1B2C3D4E5F67890ABCDEF1234567890A1B2C3D4 uid [ultimate] Test User <testuser@lab.local> sub rsa4096 2025-01-15 [E]

The pub line is the public key. The sub line is the encryption subkey. [SC] = Sign+Certify, [E] = Encrypt.

3

Export the public key (this is what you share with others):

gpg --armor --export testuser@lab.local > ~/lab-encryption/testuser-public.asc head -3 ~/lab-encryption/testuser-public.asc
-----BEGIN PGP PUBLIC KEY BLOCK----- mQINBGDa...
4

Encrypt a file for a recipient using their public key (-r = recipient email):

echo "Classified project details for testuser" > ~/lab-encryption/message.txt gpg --encrypt --armor \ -r testuser@lab.local \ --output ~/lab-encryption/message.txt.gpg \ ~/lab-encryption/message.txt head -4 ~/lab-encryption/message.txt.gpg
-----BEGIN PGP MESSAGE----- hQIMA7x8... ...
5

Decrypt using the private key (which lives in the keyring):

gpg --decrypt ~/lab-encryption/message.txt.gpg
gpg: encrypted with rsa4096 key, ID 7890ABCD, created 2025-01-15 "Test User <testuser@lab.local>" Classified project details for testuser
6

Digitally sign a file to prove authenticity (recipients verify with your public key):

echo "Official announcement from testuser" > ~/lab-encryption/announcement.txt gpg --clearsign \ --batch --pinentry-mode loopback \ ~/lab-encryption/announcement.txt cat ~/lab-encryption/announcement.txt.asc
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA512 Official announcement from testuser -----BEGIN PGP SIGNATURE----- iQIzBAAB... -----END PGP SIGNATURE-----
# Verify the signature gpg --verify ~/lab-encryption/announcement.txt.asc
gpg: Signature made Wed Jan 15 09:12:00 2025 UTC gpg: using RSA key A1B2C3D4E5F67890ABCDEF1234567890A1B2C3D4 gpg: Good signature from "Test User <testuser@lab.local>" [ultimate]
3

OpenSSL File Encryption

Encrypt files with AES-256-CBC and generate hashes for integrity checking

INTERMEDIATE
OpenSSL vs GPG openssl enc is a low-level symmetric cipher tool — fast, scriptable, and part of every Linux system. GPG is preferred for key-based workflows. OpenSSL shines for quick file encryption, TLS certificate work, and scripted pipelines.
1

Generate a hash of a file before encryption (to verify integrity later):

cd ~/lab-encryption sha256sum secret.txt
8d0e7c2a5f3b9d1e4a6c8b0f2e7d5c3a9b1f4e6d secret.txt

Record this hash — you will compare it after decryption to confirm no corruption.

2

Encrypt the file with AES-256-CBC. The -pbkdf2 flag uses a modern key derivation function that resists brute-force attacks:

openssl enc -aes-256-cbc -pbkdf2 -iter 100000 \ -in secret.txt \ -out secret.txt.enc \ -pass pass:"SecureLabPass99!" ls -lh secret.txt.enc
-rw-rw-r-- 1 user user 80 Jan 15 09:13 secret.txt.enc
3

Verify the encrypted file is binary ciphertext (not readable text):

file secret.txt.enc xxd secret.txt.enc | head -3
secret.txt.enc: openssl enc'd data with salted password 00000000: 5361 6c74 6564 5f5f 2c8f a1b4 e5d2 9c7f Salted__,...... 00000010: 3a71 2d8e f4c3 a590 1be7 82d6 4fca 1239 :q-.........O..9

The Salted__ header is a signature that openssl adds to encrypted files.

4

Decrypt with the same parameters and verify hash integrity:

openssl enc -d -aes-256-cbc -pbkdf2 -iter 100000 \ -in secret.txt.enc \ -out secret-recovered.txt \ -pass pass:"SecureLabPass99!" sha256sum secret-recovered.txt
8d0e7c2a5f3b9d1e4a6c8b0f2e7d5c3a9b1f4e6d secret-recovered.txt

The hashes match — the file survived encryption and decryption without modification.

5

Generate a random encryption key (instead of a passphrase) and use it as a key file:

# Generate 32 bytes of random key material (256 bits) openssl rand -hex 32 > ~/lab-encryption/keyfile.key cat ~/lab-encryption/keyfile.key
a3f7c2d9e1b4856f0e3a7c5d2b8f9e1a4c7d3b2f9e5a1c8d4b7f2e9a3c6d1b8
# Encrypt using the key file openssl enc -aes-256-cbc -pbkdf2 -iter 100000 \ -in secret.txt \ -out secret-keyfile.enc \ -kfile ~/lab-encryption/keyfile.key echo "Encrypted successfully using key file"
Encrypted successfully using key file
Key File Security Key files must be protected as carefully as the data they encrypt. Store key files on separate media, use strict permissions (chmod 400 keyfile.key), and never store them alongside the encrypted data.
4

SSH Key Generation and Management

Generate ed25519 and RSA key pairs, understand permissions, and manage the authorized_keys file

INTERMEDIATE
SSH Key Authentication Flow 1. Client has private key (protected, never shared). 2. Server has public key in ~/.ssh/authorized_keys. 3. On connection, server challenges with a random message. 4. Client signs with private key. 5. Server verifies signature with public key. No password ever crosses the network.
1

Create a test directory and generate a modern ed25519 key pair:

mkdir -p ~/lab-encryption/ssh-keys # -t ed25519: algorithm (preferred, compact, fast) # -C: comment (label identifying the key) # -f: output file location # -N "": no passphrase (for lab; use one in production!) ssh-keygen -t ed25519 \ -C "lab-test-key" \ -f ~/lab-encryption/ssh-keys/id_ed25519 \ -N ""
Generating public/private ed25519 key pair. Your identification has been saved in /home/user/lab-encryption/ssh-keys/id_ed25519 Your public key has been saved in /home/user/lab-encryption/ssh-keys/id_ed25519.pub The key fingerprint is: SHA256:xK7mN2pQ9vR3sT5wY8zA1bC4dF6gH0iJ lab-test-key The key's randomart image is: +--[ED25519 256]--+ | .o+. | ...
2

Examine both keys. Notice the dramatic size difference:

echo "=== PRIVATE KEY (first 3 lines) ===" head -3 ~/lab-encryption/ssh-keys/id_ed25519 echo "" echo "=== PUBLIC KEY (entire key) ===" cat ~/lab-encryption/ssh-keys/id_ed25519.pub
=== PRIVATE KEY (first 3 lines) === -----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAA... ... === PUBLIC KEY (entire key) === ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJk7mP2... lab-test-key

The public key is a single line — this is what you put in authorized_keys. The private key is multi-line and stays on the client.

3

Generate an RSA 4096-bit key pair for comparison (required by some older systems):

ssh-keygen -t rsa -b 4096 \ -C "lab-rsa-key" \ -f ~/lab-encryption/ssh-keys/id_rsa \ -N "" ls -lh ~/lab-encryption/ssh-keys/
-rw------- 1 user user 3.4K Jan 15 09:15 id_ed25519 -rw-r--r-- 1 user user 100 Jan 15 09:15 id_ed25519.pub -rw------- 1 user user 3.2K Jan 15 09:15 id_rsa -rw-r--r-- 1 user user 739 Jan 15 09:15 id_rsa.pub

Note the permissions: private keys are 600 (owner read/write only). SSH will refuse to use keys with looser permissions.

4

Inspect the correct SSH file permissions structure:

ls -la ~/.ssh/ 2>/dev/null || echo "~/.ssh does not exist yet" # Demonstrate permission requirements: # ~/.ssh directory: 700 (drwx------) # private keys: 600 (-rw-------) # authorized_keys file: 600 (-rw-------) # public keys (.pub): 644 (-rw-r--r--) stat -c "%a %n" ~/lab-encryption/ssh-keys/id_ed25519 stat -c "%a %n" ~/lab-encryption/ssh-keys/id_ed25519.pub
600 /home/user/lab-encryption/ssh-keys/id_ed25519 644 /home/user/lab-encryption/ssh-keys/id_ed25519.pub
5

Simulate adding a public key to authorized_keys (the deployment step):

mkdir -p ~/lab-encryption/fake-server/.ssh chmod 700 ~/lab-encryption/fake-server/.ssh # cat the public key >> authorized_keys (append, not overwrite) cat ~/lab-encryption/ssh-keys/id_ed25519.pub \ >> ~/lab-encryption/fake-server/.ssh/authorized_keys chmod 600 ~/lab-encryption/fake-server/.ssh/authorized_keys echo "Contents of authorized_keys:" cat ~/lab-encryption/fake-server/.ssh/authorized_keys
Contents of authorized_keys: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJk7mP2... lab-test-key
6

View the key fingerprint (useful for verifying key identity without revealing the full key):

ssh-keygen -lf ~/lab-encryption/ssh-keys/id_ed25519.pub
256 SHA256:xK7mN2pQ9vR3sT5wY8zA1bC4dF6gH0iJ lab-test-key (ED25519)

Ed25519

Elliptic curve algorithm. Compact keys (68 chars public), fast operations, excellent security. Modern standard — use this when possible.

RSA 4096

Traditional algorithm. Longer keys (739 chars public), slower but widely compatible with older SSH servers and systems requiring FIPS compliance.

5

LUKS Disk Encryption (Concept + Safe Practice)

Understand the LUKS workflow and practice with a loop device

ADVANCED
Lab Safety LUKS formats block devices permanently. NEVER run cryptsetup luksFormat against a real disk or partition (/dev/sda, /dev/nvme0n1) without understanding the consequences. This exercise uses a loop device backed by a file — safe for any system.
LUKS Architecture LUKS (Linux Unified Key Setup) is a specification for full disk/partition encryption. The cryptsetup tool implements it. Workflow: 1) luksFormat — writes LUKS header and encrypts. 2) luksOpen — creates a device mapper entry at /dev/mapper/name. 3) Use the mapper device normally (mkfs, mount, read/write). 4) luksClose — removes the mapper entry and secures the device.
1

Create a 50MB file to use as a loop device (safe substitute for a real disk):

dd if=/dev/zero bs=1M count=50 of=~/lab-encryption/luks-test.img ls -lh ~/lab-encryption/luks-test.img
50+0 records in 50+0 records out 52428800 bytes (52 MB) copied -rw-rw-r-- 1 user user 50M Jan 15 09:16 luks-test.img
2

Set up a loop device backed by the image file:

LOOP=$(sudo losetup --find --show ~/lab-encryption/luks-test.img) echo "Loop device created: $LOOP"
Loop device created: /dev/loop7
3

Format the loop device with LUKS encryption:

echo "SecureDiskPass99!" | sudo cryptsetup luksFormat \ --type luks2 \ --cipher aes-xts-plain64 \ --key-size 256 \ --batch-mode \ $LOOP
(no output on success)
sudo cryptsetup luksDump $LOOP | head -20
LUKS header information Version: 2 Epoch: 3 Metadata area: 16384 [bytes] Keyslots area: 16744448 [bytes] UUID: a1b2c3d4-e5f6-7890-abcd-ef1234567890 Label: (no label) Cipher: aes-xts-plain64 Cipher key: 256 bits PBKDF: argon2id
4

Open (unlock) the LUKS volume and create a filesystem on the mapper device:

echo "SecureDiskPass99!" | sudo cryptsetup luksOpen $LOOP luks-lab ls /dev/mapper/luks-lab sudo mkfs.ext4 /dev/mapper/luks-lab -q echo "Filesystem created successfully"
/dev/mapper/luks-lab Filesystem created successfully
5

Mount the encrypted volume, write a file, then close and verify data is inaccessible:

sudo mkdir -p /mnt/luks-lab sudo mount /dev/mapper/luks-lab /mnt/luks-lab echo "Secret data inside encrypted volume" | sudo tee /mnt/luks-lab/inside.txt # Unmount and close (lock) sudo umount /mnt/luks-lab sudo cryptsetup luksClose luks-lab # Try to read the raw image — should show gibberish strings ~/lab-encryption/luks-test.img | grep "Secret" || \ echo "Data is encrypted — not readable from raw image"
Secret data inside encrypted volume Data is encrypted — not readable from raw image
6

Clean up the loop device:

sudo losetup -d $LOOP echo "Loop device released. Loop devices in use:" sudo losetup -l
Loop device released. NAME SIZELIMIT OFFSET AUTOCLEAR RO BACK-FILE DIO LOG-SEC (no loop devices)
Key Slot Management LUKS supports up to 8 key slots — multiple passphrases can all unlock the same volume. Use cryptsetup luksAddKey to add recovery passphrases, cryptsetup luksKillSlot to revoke individual keys. If all key slots are wiped, the data is permanently unrecoverable.

Lab Complete

You encrypted files with GPG (symmetric and asymmetric), used OpenSSL for AES-256-CBC encryption with integrity verification, generated and managed SSH key pairs, and practiced LUKS full-disk encryption with a loop device.

This lab is already marked complete.