If you don't want to install GPG/PGP on your device to use its commands, you can try Docker setup below. With the current setup, you can encrypt and decrypt messages. The only thing that could be added is that, file encryption and decryption which should be easy.


Whether you are a sender or receiver, you will be prompted to enter your passphrase for the make commands. Each users' (test) personal details are set in the .env.dist using example values. For the real users this file must be duplicated as .env and contain real values. This file is excluded from VCS commits as well as the /users/ folder!


Structure


This is the original state.


├── .dockerignore
├── .env.dist
├── .gitignore
├── Dockerfile
├── Makefile
├── Readme.md
├── docker-compose.yaml
├── gpg.cfg
├── gpg.sh
└── users
├── ali
│   └── msg-to-encrypt.txt
└── bob
└── msg-to-encrypt.txt

This is what you would get after using ali/bob as sender/receiver and running the commands below.


├── .dockerignore
├── .env.dist
├── .gitignore
├── Dockerfile
├── Makefile
├── Readme.md
├── docker-compose.yaml
├── gpg.cfg
├── gpg.sh
└── users
├── ali
│   ├── decrypted-msg.txt
│   ├── encrypted-msg.txt
│   ├── msg-to-decrypt.txt
│   ├── msg-to-encrypt.txt
│   ├── private.key
│   ├── public.key
│   └── trust.db
└── bob
├── decrypted-msg.txt
├── encrypted-msg.txt
├── msg-to-decrypt.txt
├── msg-to-encrypt.txt
├── private.key
├── public.key
└── trust.db

Files


ali/msg-to-encrypt.txt


Hello from Ali!

bob/msg-to-encrypt.txt


Hello from Bob!

.dockerignore


*
!/gpg.sh
!/gpg.cfg

.env.dist


ALI_NAME_REAL=Ali
ALI_NAME_COMMENT=Ali's account
ALI_NAME_EMAIL=ali@example.com
ALI_PASSPHASE=ali

BOB_NAME_REAL=Bob
BOB_NAME_COMMENT=Bob's account
BOB_NAME_EMAIL=bob@example.com
BOB_PASSPHASE=bob

.gitignore


users/*
!users/*/msg-to-encrypt.txt
.env

docker-compose.yaml


services:
gpg:
container_name: gpg
build:
context: "."
env_file:
- ".env"
environment:
- ACTION=${ACTION}
- USER=${USER}
- SENDER=${SENDER}
- RECEIVER=${RECEIVER}
volumes:
- "./users:/users"

Dockerfile


FROM alpine:3.16.0
COPY . .
RUN apk update && apk fetch gnupg && apk add gnupg && chmod +x gpg.sh
ENTRYPOINT ["./gpg.sh"]

gig.cfg


Key-Type: RSA
Key-Length: 2048
Subkey-Type: RSA
Subkey-Length: 2048
Name-Real: ${NAME_REAL}
Name-Comment: ${NAME_COMMENT}
Name-Email: ${NAME_EMAIL}
Expire-Date: 0
Passphrase: ${PASSPHASE}

gpg.sh


#!/bin/sh

set -eu

CYAN="\033[0;36m"
CLEAR="\033[0m"
GPG_DIR="/root/.gnupg"
USERS_DIR="/users"
PUBLIC_KEY_NAME="public.key"
PRIVATE_KEY_NAME="private.key"
TRUST_DB_NAME="trust.db"
DECRYPTED_MSG_NAME="decrypted-msg.txt"
ENCRYPTED_MSG_NAME="encrypted-msg.txt"
MESSAGE_TO_DECRYPT_NAME="msg-to-decrypt.txt"
MESSAGE_TO_ENCRYPT_NAME="msg-to-encrypt.txt"

gen_key() {
printf "${CYAN}> GENERATE USER's PUBLIC and PRIVATE KEYS (begin) ----------------------------------------${CLEAR}\n"

printf "${CYAN}Set local variables${CLEAR}\n"
USER_NAME_REAL="$(echo ${USER} | tr 'a-z' 'A-Z')_NAME_REAL"
USER_NAME_COMMENT="$(echo ${USER} | tr 'a-z' 'A-Z')_NAME_COMMENT"
USER_NAME_EMAIL="$(echo ${USER} | tr 'a-z' 'A-Z')_NAME_EMAIL"
USER_PASSPHASE="$(echo ${USER} | tr 'a-z' 'A-Z')_PASSPHASE"
USER_PUB_KEY="${USERS_DIR}/${USER}/${PUBLIC_KEY_NAME}"
USER_PRV_KEY="${USERS_DIR}/${USER}/${PRIVATE_KEY_NAME}"
USER_TRUST_DB="${USERS_DIR}/${USER}/${TRUST_DB_NAME}"
CONFIG_KEY="/gpg.cfg"
echo "done"

printf "${CYAN}Prepare configuration file${CLEAR}\n"
sed -i \
-e 's/${NAME_REAL}/'"$(printenv -- ${USER_NAME_REAL})"'/g' \
-e 's/${NAME_COMMENT}/'"$(printenv -- ${USER_NAME_COMMENT})"'/g' \
-e 's/${NAME_EMAIL}/'"$(printenv -- ${USER_NAME_EMAIL})"'/g' \
-e 's/${PASSPHASE}/'"$(printenv -- ${USER_PASSPHASE})"'/g' \
${CONFIG_KEY}
echo "done"

printf "${CYAN}Create gpg directory and get into it${CLEAR}\n"
gpg2 --list-keys
cd ${GPG_DIR}

printf "${CYAN}Generate keys${CLEAR}\n"
gpg2 --verbose --batch --gen-key ${CONFIG_KEY}

printf "${CYAN}Backup public and private keys${CLEAR}\n"
gpg2 --yes --output ${USER_PUB_KEY} --armor --export-options export-backup --export $(printenv -- ${USER_NAME_EMAIL})
gpg2 --yes --output ${USER_PRV_KEY} --armor --export-options export-backup --export-secret-keys $(printenv -- ${USER_NAME_EMAIL})
echo "done"

printf "${CYAN}Backup trust database${CLEAR}\n"
gpg2 --export-ownertrust > ${USER_TRUST_DB}
echo "done"

printf "${CYAN}Cleanup${CLEAR}\n"
cleanup
echo "done"

printf "${CYAN}> GENERATE USER's PUBLIC and PRIVATE KEYS (end) ------------------------------------------${CLEAR}\n"
}

enc_msg() {
printf "${CYAN}> SENDER ENCRYPTS a PLAIN MESSAGE for RECEIVER to DECRYPT (begin) ------------------------${CLEAR}\n"

printf "${CYAN}Set local variables${CLEAR}\n"
SENDER_PUB_KEY="${USERS_DIR}/${SENDER}/${PUBLIC_KEY_NAME}"
SENDER_PRV_KEY="${USERS_DIR}/${SENDER}/${PRIVATE_KEY_NAME}"
SENDER_TRUST_DB="${USERS_DIR}/${SENDER}/${TRUST_DB_NAME}"
SENDER_MSG_TO_ENCRYPT="${USERS_DIR}/${SENDER}/${MESSAGE_TO_ENCRYPT_NAME}"
SENDER_ENCRYPTED_MSG="${USERS_DIR}/${SENDER}/${ENCRYPTED_MSG_NAME}"
RECEIVER_PUB_KEY="${USERS_DIR}/${RECEIVER}/${PUBLIC_KEY_NAME}"
RECEIVER_EMAIL="$(echo ${RECEIVER} | tr 'a-z' 'A-Z')_NAME_EMAIL"
echo "done"

printf "${CYAN}Create gpg directory and get into it${CLEAR}\n"
gpg2 --list-keys
cd ${GPG_DIR}

printf "${CYAN}Restore sender's public key${CLEAR}\n"
gpg2 --import-options import-restore --import ${SENDER_PUB_KEY}

printf "${CYAN}Restore sender's private key${CLEAR}\n"
gpg2 --import-options import-restore --import ${SENDER_PRV_KEY}

printf "${CYAN}Restore sender's trust database${CLEAR}\n"
rm trustdb.gpg
gpg2 --import-ownertrust < ${SENDER_TRUST_DB}

printf "${CYAN}Restore receiver's public key${CLEAR}\n"
gpg2 --import-options import-restore --import ${RECEIVER_PUB_KEY}

printf "${CYAN}Verify receiver's public key${CLEAR}\n"
gpg2 --fingerprint $(printenv -- ${RECEIVER_EMAIL})

printf "${CYAN}Sign receiver's public key${CLEAR}\n"
gpg2 --sign-key $(printenv -- ${RECEIVER_EMAIL})

printf "${CYAN}Sender encrypts message for receiver${CLEAR}\n"
gpg2 --batch --yes --output ${SENDER_ENCRYPTED_MSG} --encrypt --sign --armor -r $(printenv -- ${RECEIVER_EMAIL}) ${SENDER_MSG_TO_ENCRYPT}
echo "done"

printf "${CYAN}Cleanup${CLEAR}\n"
cleanup
echo "done"

printf "${CYAN}> SENDER ENCRYPTS a PLAIN MESSAGE for RECEIVER to DECRYPT (end) --------------------------${CLEAR}\n"
}

dec_msg() {
printf "${CYAN}> RECEIVER DECRYPTS SENDER's ENCRYPTED MESSAGE (begin) -----------------------------------${CLEAR}\n"

printf "${CYAN}Set local variables${CLEAR}\n"
RECEIVER_PUB_KEY="${USERS_DIR}/${RECEIVER}/${PUBLIC_KEY_NAME}"
RECEIVER_PRV_KEY="${USERS_DIR}/${RECEIVER}/${PRIVATE_KEY_NAME}"
RECEIVER_TRUST_DB="${USERS_DIR}/${RECEIVER}/${TRUST_DB_NAME}"
SENDER_PUB_KEY="${USERS_DIR}/${SENDER}/${PUBLIC_KEY_NAME}"
SENDER_EMAIL="$(echo ${SENDER} | tr 'a-z' 'A-Z')_NAME_EMAIL"
RECEIVER_MSG_TO_DECYPT="${USERS_DIR}/${RECEIVER}/${MESSAGE_TO_DECRYPT_NAME}"
RECEIVER_DECRYPTED_MSG="${USERS_DIR}/${RECEIVER}/${DECRYPTED_MSG_NAME}"
echo "done"

printf "${CYAN}Create gpg directory and get into it${CLEAR}\n"
gpg2 --list-keys
cd ${GPG_DIR}

printf "${CYAN}Restore receiver's public key${CLEAR}\n"
gpg2 --import-options import-restore --import ${RECEIVER_PUB_KEY}

printf "${CYAN}Restore receiver's private key${CLEAR}\n"
gpg2 --import-options import-restore --import ${RECEIVER_PRV_KEY}

printf "${CYAN}Restore receiver's trust database${CLEAR}\n"
rm trustdb.gpg
gpg2 --import-ownertrust < ${RECEIVER_TRUST_DB}

printf "${CYAN}Restore sender's public key${CLEAR}\n"
gpg2 --import-options import-restore --import ${SENDER_PUB_KEY}

printf "${CYAN}Verify sender's public key${CLEAR}\n"
gpg2 --fingerprint $(printenv -- ${SENDER_EMAIL})

printf "${CYAN}Sign sender's public key${CLEAR}\n"
gpg2 --sign-key $(printenv -- ${SENDER_EMAIL})

printf "${CYAN}Receiver decrypts sender's encrypted message${CLEAR}\n"
gpg2 --decrypt ${RECEIVER_MSG_TO_DECYPT} > ${RECEIVER_DECRYPTED_MSG}

printf "${CYAN}Cleanup${CLEAR}\n"
cleanup
echo "done"

printf "${CYAN}> RECEIVER DECRYPTS SENDER's ENCRYPTED MESSAGE (end) -------------------------------------${CLEAR}\n"
}

cleanup() {
cd /
rm -rf ${GPG_DIR}
rm gpg.sh gpg.cfg
}

if [ ${ACTION} == "gen-key" ]; then gen_key; fi
if [ ${ACTION} == "enc-msg" ]; then enc_msg; fi
if [ ${ACTION} == "dec-msg" ]; then dec_msg; fi

Makefile


.PHONY: help
help: ## Display available commands.
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)

.PHONY: gen-key
gen-key: ## Generate public and private keys for a user. Usage: `make gen-key USER=ali`
make down
ACTION=gen-key USER=${USER} docker-compose run --rm gpg

.PHONY: enc-msg
enc-msg: ## Sender encrypts plain message for receiver to decrypt. Usage: `make enc-msg SENDER=ali RECEIVER=bob`
make down
ACTION=enc-msg SENDER=${SENDER} RECEIVER=${RECEIVER} docker-compose run --rm gpg

.PHONY: dec-msg
dec-msg: ## Receiver decrypts sender's encrypted message. `make dec-msg SENDER=ali RECEIVER=bob`
make down
ACTION=dec-msg SENDER=${SENDER} RECEIVER=${RECEIVER} docker-compose run --rm gpg

.PHONY: config
config: ## Dump docker-compose configuration.
docker-compose config

.PHONY: down
down: ## Clean up docker artefacts.
docker-compose down
@-docker rmi gpg_gpg
docker system prune --volumes --force

Commands


Generate key


It creates public and private keys then backs them up into the /users/{name}/ folder including the "trust" database. Files created are private.key, public.key and trust.db. The public.key can then be shared with third parties to decrypt messages.


$ make gen-key USER=Ali

> GENERATE USERs PUBLIC and PRIVATE KEYS (begin) ----------------------------------------
Set local variables
done
Prepare configuration file
done
Create gpg directory and get into it
gpg: directory /root/.gnupg created
gpg: keybox /root/.gnupg/pubring.kbx created
gpg: /root/.gnupg/trustdb.gpg: trustdb created
Generate keys
gpg: no running gpg-agent - starting /usr/bin/gpg-agent
gpg: waiting for the agent to come up ... (5s)
gpg: connection to agent established
gpg: writing self signature
gpg: RSA/SHA512 signature from: "C8338E605AB8E7E4 [?]"
gpg: writing key binding signature
gpg: RSA/SHA512 signature from: "C8338E605AB8E7E4 [?]"
gpg: RSA/SHA512 signature from: "04FD871034B69D9D [?]"
gpg: writing public key to /root/.gnupg/pubring.kbx
gpg: using pgp trust model
gpg: directory /root/.gnupg/openpgp-revocs.d created
gpg: writing to /root/.gnupg/openpgp-revocs.d/B653C89FC6B35C84A5B85CA7C8338E605AB8E7E4.rev
gpg: RSA/SHA512 signature from: "C8338E605AB8E7E4 Ali (Alis account) <ali@example.com>"
gpg: revocation certificate stored as /root/.gnupg/openpgp-revocs.d/B653C89FC6B35C84A5B85CA7C8338E605AB8E7E4.rev
Backup public and private keys
done
Backup trust database
done
Cleanup
done
> GENERATE USERs PUBLIC and PRIVATE KEYS (end) ------------------------------------------

Encrypt key


It helps the sender to encrypt a plain message for the receiver to decrypt. The sender's plain message must be put into the /users/{sender_name}/msg-to-encrypt.txt file. Successful operation will put encrypted message into /users/{sender_name}/encrypted-msg.txt file. This file can be shared with the receiver who has your public key for decryption.


$ make enc-msg SENDER=ali RECEIVER=bob

> SENDER ENCRYPTS a PLAIN MESSAGE for RECEIVER to DECRYPT (begin) ------------------------
Set local variables
done
Create gpg directory and get into it
gpg: directory /root/.gnupg created
gpg: keybox /root/.gnupg/pubring.kbx created
gpg: /root/.gnupg/trustdb.gpg: trustdb created
Restore senders public key
gpg: key C8338E605AB8E7E4: public key "Ali (Alis account) <ali@example.com>" imported
gpg: Total number processed: 1
gpg: imported: 1
Restore senders private key
gpg: key C8338E605AB8E7E4: "Ali (Alis account) <ali@example.com>" not changed
gpg: key C8338E605AB8E7E4: secret key imported
gpg: Total number processed: 1
gpg: unchanged: 1
gpg: secret keys read: 1
gpg: secret keys imported: 1
Restore senders trust database
gpg: /root/.gnupg/trustdb.gpg: trustdb created
gpg: inserting ownertrust of 6
Restore receivers public key
gpg: key 6BA9B4E9279E259C: public key "Bob (Bobs account) <bob@example.com>" imported
gpg: Total number processed: 1
gpg: imported: 1
Verify receivers public key
gpg: checking the trustdb
gpg: marginals needed: 3 completes needed: 1 trust model: pgp
gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u
pub rsa2048 2022-06-03 [SCEA]
E464 CA1D C0A4 A8F5 D473 0160 6BA9 B4E9 279E 259C
uid [ unknown] Bob (Bobs account) <bob@example.com>
sub rsa2048 2022-06-03 [SEA]

Sign receivers public key

pub rsa2048/6BA9B4E9279E259C
created: 2022-06-03 expires: never usage: SCEA
trust: unknown validity: unknown
sub rsa2048/8C840512707375BB
created: 2022-06-03 expires: never usage: SEA
[ unknown] (1). Bob (Bobs account) <bob@example.com>


pub rsa2048/6BA9B4E9279E259C
created: 2022-06-03 expires: never usage: SCEA
trust: unknown validity: unknown
Primary key fingerprint: E464 CA1D C0A4 A8F5 D473 0160 6BA9 B4E9 279E 259C

Bob (Bobs account) <bob@example.com>

Are you sure that you want to sign this key with your
key "Ali (Alis account) <ali@example.com>" (C8338E605AB8E7E4)

Really sign? (y/N) y

Sender encrypts message for receiver
gpg: checking the trustdb
gpg: marginals needed: 3 completes needed: 1 trust model: pgp
gpg: depth: 0 valid: 1 signed: 1 trust: 0-, 0q, 0n, 0m, 0f, 1u
gpg: depth: 1 valid: 1 signed: 0 trust: 1-, 0q, 0n, 0m, 0f, 0u
done
Cleanup
done
> SENDER ENCRYPTS a PLAIN MESSAGE for RECEIVER to DECRYPT (end) --------------------------

Decrypt key


It helps the receiver to decrypt the sender's encrypted message. The sender's encrypted message must be put into the /users/{receiver_name}/msg-to-decrypt.txt file. Successful operation will put decrypted message into /users/{receiver_name}/decrypted-msg.txt file.


$ make dec-msg SENDER=ali RECEIVER=bob

> RECEIVER DECRYPTS SENDERs ENCRYPTED MESSAGE (begin) -----------------------------------
Set local variables
done
Create gpg directory and get into it
gpg: directory /root/.gnupg created
gpg: keybox /root/.gnupg/pubring.kbx created
gpg: /root/.gnupg/trustdb.gpg: trustdb created
Restore receivers public key
gpg: key 6BA9B4E9279E259C: public key "Bob (Bobs account) <bob@example.com>" imported
gpg: Total number processed: 1
gpg: imported: 1
Restore receivers private key
gpg: key 6BA9B4E9279E259C: "Bob (Bobs account) <bob@example.com>" not changed
gpg: key 6BA9B4E9279E259C: secret key imported
gpg: Total number processed: 1
gpg: unchanged: 1
gpg: secret keys read: 1
gpg: secret keys imported: 1
Restore receivers trust database
gpg: /root/.gnupg/trustdb.gpg: trustdb created
gpg: inserting ownertrust of 6
Restore senders public key
gpg: key C8338E605AB8E7E4: public key "Ali (Alis account) <ali@example.com>" imported
gpg: Total number processed: 1
gpg: imported: 1
Verify senders public key
gpg: checking the trustdb
gpg: marginals needed: 3 completes needed: 1 trust model: pgp
gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u
pub rsa2048 2022-06-03 [SCEA]
B653 C89F C6B3 5C84 A5B8 5CA7 C833 8E60 5AB8 E7E4
uid [ unknown] Ali (Alis account) <ali@example.com>
sub rsa2048 2022-06-03 [SEA]

Sign senders public key

pub rsa2048/C8338E605AB8E7E4
created: 2022-06-03 expires: never usage: SCEA
trust: unknown validity: unknown
sub rsa2048/04FD871034B69D9D
created: 2022-06-03 expires: never usage: SEA
[ unknown] (1). Ali (Alis account) <ali@example.com>


pub rsa2048/C8338E605AB8E7E4
created: 2022-06-03 expires: never usage: SCEA
trust: unknown validity: unknown
Primary key fingerprint: B653 C89F C6B3 5C84 A5B8 5CA7 C833 8E60 5AB8 E7E4

Ali (Alis account) <ali@example.com>

Are you sure that you want to sign this key with your
key "Bob (Bobs account) <bob@example.com>" (6BA9B4E9279E259C)

Really sign? (y/N) y

Receiver decrypts senders encrypted message
gpg: encrypted with 2048-bit RSA key, ID 8C840512707375BB, created 2022-06-03
"Bob (Bobs account) <bob@example.com>"
gpg: Signature made Fri Jun 3 20:44:12 2022 UTC
gpg: using RSA key 4B874F8387E97D9044FF8A4B04FD871034B69D9D
gpg: checking the trustdb
gpg: marginals needed: 3 completes needed: 1 trust model: pgp
gpg: depth: 0 valid: 1 signed: 1 trust: 0-, 0q, 0n, 0m, 0f, 1u
gpg: depth: 1 valid: 1 signed: 0 trust: 1-, 0q, 0n, 0m, 0f, 0u
gpg: Good signature from "Ali (Alis account) <ali@example.com>" [full]
Cleanup
done
> RECEIVER DECRYPTS SENDERs ENCRYPTED MESSAGE (end) -------------------------------------