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.


This piece work is dedicated to message encryption and decryption without user's interaction. Keys are not passphrase protected, hence it is intended for internal or your use only. For instance, message is encrypted using your details as usual and you are used as the receiver of the encrypted message which is normally someone else in real life scenarios. In short, you are sender and receiver at same time. The reason for this is because, we want to encrypt AWS IAM users' password in CI, send you the encrypted output so that you decrypt it to obtain the actual login password which should be changed immediatelly. CI only needs to know how to encrypt a message, nothing else!


Your details are set in the .env.dist using example values. However, for the real values this file must be duplicated as .env and contain real values. This file is excluded from VCS commits as well as the /data/ folder where GPG keys are placed after running make commands.


Structure


This is the original state.


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

This output is after running the commands below.


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

Files


msg-to-encrypt.txt


Hello!

.dockerignore


*
!/gpg.sh
!/gpg.cfg

.env.dist


NAME_REAL=Continuous Integration and Delivery
NAME_COMMENT=CI\/CD use only
NAME_EMAIL=cicd@example.com

.gitignore


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

docker-compose.yaml


services:
gpg:
container_name: gpg
build:
context: "."
env_file:
- ".env"
environment:
- ACTION=${ACTION}
volumes:
- "./data:/data"

Dockerfile


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

gpg.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
%no-protection

gig.sh


#!/bin/sh

set -eu

CYAN="\033[0;36m"
CLEAR="\033[0m"
GPG_DIR="/root/.gnupg"
DATA_DIR="/data"
PUBLIC_KEY_NAME="public.key"
PRIVATE_KEY_NAME="private.key"
DECRYPTED_MSG_NAME="decrypted-msg.txt"
ENCRYPTED_MSG_NAME="encrypted-msg.txt"
MESSAGE_TO_ENCRYPT_NAME="msg-to-encrypt.txt"

gen_key() {
printf "${CYAN}> GENERATE KEYS (begin) --------------------------${CLEAR}\n"

printf "${CYAN}Set local variables${CLEAR}\n"
PUB_KEY="${DATA_DIR}/${PUBLIC_KEY_NAME}"
PRV_KEY="${DATA_DIR}/${PRIVATE_KEY_NAME}"
CFG_KEY="/gpg.cfg"
echo "done"

printf "${CYAN}Prepare configuration file${CLEAR}\n"
sed -i \
-e 's/${NAME_REAL}/'"${NAME_REAL}"'/g' \
-e 's/${NAME_COMMENT}/'"${NAME_COMMENT}"'/g' \
-e 's/${NAME_EMAIL}/'"${NAME_EMAIL}"'/g' \
${CFG_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 ${CFG_KEY}

printf "${CYAN}Backup public and private keys${CLEAR}\n"
gpg2 --yes --output ${PUB_KEY} --armor --export-options export-backup --export ${NAME_EMAIL}
gpg2 --yes --output ${PRV_KEY} --armor --export-options export-backup --export-secret-keys ${NAME_EMAIL}
echo "done"

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

printf "${CYAN}> GENERATE KEYS (end) ----------------------------${CLEAR}\n"
}

enc_msg() {
printf "${CYAN}> ENCRYPT MESSAGE (begin) ------------------------${CLEAR}\n"

printf "${CYAN}Set local variables${CLEAR}\n"
PUB_KEY="${DATA_DIR}/${PUBLIC_KEY_NAME}"
PRV_KEY="${DATA_DIR}/${PRIVATE_KEY_NAME}"
ENCRYPTED_MSG="${DATA_DIR}/${ENCRYPTED_MSG_NAME}"
MESSAGE_TO_ENCRYPT="${DATA_DIR}/${MESSAGE_TO_ENCRYPT_NAME}"
echo "done"

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

printf "${CYAN}Restore public key${CLEAR}\n"
gpg2 --import-options import-restore --import ${PUB_KEY}

printf "${CYAN}Restore private key${CLEAR}\n"
gpg2 --import-options import-restore --import ${PRV_KEY}

printf "${CYAN}Auto trust and verify keys${CLEAR}\n"
echo -e "5\ny\n" | gpg2 --command-fd 0 --expert --edit-key ${NAME_EMAIL} trust

printf "${CYAN}Encrypt message${CLEAR}\n"
gpg2 --batch --yes --output ${ENCRYPTED_MSG} --encrypt --sign --armor -r ${NAME_EMAIL} ${MESSAGE_TO_ENCRYPT}
echo "done"

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

printf "${CYAN}> ENCRYPT MESSAGE (end) --------------------------${CLEAR}\n"
}

dec_msg() {
printf "${CYAN}> DECRYPT MESSAGE (begin) ------------------------${CLEAR}\n"

printf "${CYAN}Set local variables${CLEAR}\n"
PUB_KEY="${DATA_DIR}/${PUBLIC_KEY_NAME}"
PRV_KEY="${DATA_DIR}/${PRIVATE_KEY_NAME}"
ENCRYPTED_MSG="${DATA_DIR}/${ENCRYPTED_MSG_NAME}"
DECRYPTED_MSG="${DATA_DIR}/${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 public key${CLEAR}\n"
gpg2 --import-options import-restore --import ${PUB_KEY}

printf "${CYAN}Restore private key${CLEAR}\n"
gpg2 --import-options import-restore --import ${PRV_KEY}

printf "${CYAN}Decrypt message${CLEAR}\n"
gpg2 --decrypt ${ENCRYPTED_MSG} > ${DECRYPTED_MSG}

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

printf "${CYAN}> DECRYPT 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 keys
make down
ACTION=gen-key docker-compose run --rm gpg

.PHONY: enc-msg
enc-msg: ## Encrypt message
make down
ACTION=enc-msg docker-compose run --rm gpg

.PHONY: dec-msg
dec-msg: ## Decrypt message
make down
ACTION=dec-msg 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 keys


It creates public and private keys then backs them up into the /data/ folder. Files created are private.key and public.key.


$ make gen-key

> GENERATE 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: "FFB8195B9A6E634C [?]"
gpg: writing key binding signature
gpg: RSA/SHA512 signature from: "FFB8195B9A6E634C [?]"
gpg: RSA/SHA512 signature from: "EAB3A38FBCEDC2CB [?]"
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/4AF187A975C16AA53E8DE0FAFFB8195B9A6E634C.rev
gpg: RSA/SHA512 signature from: "FFB8195B9A6E634C Continuous Integration and Delivery (CI/CD use only) <cicd@example.com>"
gpg: revocation certificate stored as /root/.gnupg/openpgp-revocs.d/4AF187A975C16AA53E8DE0FAFFB8195B9A6E634C.rev
Backup public and private keys
done
Cleanup
done
> GENERATE KEYS (end) ----------------------------

Encrypt key


It encrypts /data/msg-to-encrypt.txt file content and creates /data/encrypted-msg.txt file. If you wanted, this is the only command that gets run by CI. In such case, you should replace --output ${ENCRYPTED_MSG} with --output - in enc_msg() function to output encrypted string.


$ make enc-msg

> ENCRYPT 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 public key
gpg: key FFB8195B9A6E634C: public key "Continuous Integration and Delivery (CI/CD use only) <cicd@example.com>" imported
gpg: Total number processed: 1
gpg: imported: 1
Restore private key
gpg: key FFB8195B9A6E634C: "Continuous Integration and Delivery (CI/CD use only) <cicd@example.com>" not changed
gpg: key FFB8195B9A6E634C: secret key imported
gpg: Total number processed: 1
gpg: unchanged: 1
gpg: secret keys read: 1
gpg: secret keys imported: 1
Auto trust and verify keys
gpg (GnuPG) 2.2.35; Copyright (C) 2022 g10 Code GmbH
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Secret key is available.

sec rsa2048/FFB8195B9A6E634C
created: 2022-06-04 expires: never usage: SCEA
trust: unknown validity: unknown
ssb rsa2048/EAB3A38FBCEDC2CB
created: 2022-06-04 expires: never usage: SEA
[ unknown] (1). Continuous Integration and Delivery (CI/CD use only) <cicd@example.com>

sec rsa2048/FFB8195B9A6E634C
created: 2022-06-04 expires: never usage: SCEA
trust: unknown validity: unknown
ssb rsa2048/EAB3A38FBCEDC2CB
created: 2022-06-04 expires: never usage: SEA
[ unknown] (1). Continuous Integration and Delivery (CI/CD use only) <cicd@example.com>

Please decide how far you trust this user to correctly verify other users keys
(by looking at passports, checking fingerprints from different sources, etc.)

1 = I dont know or wont say
2 = I do NOT trust
3 = I trust marginally
4 = I trust fully
5 = I trust ultimately
m = back to the main menu


sec rsa2048/FFB8195B9A6E634C
created: 2022-06-04 expires: never usage: SCEA
trust: ultimate validity: unknown
ssb rsa2048/EAB3A38FBCEDC2CB
created: 2022-06-04 expires: never usage: SEA
[ unknown] (1). Continuous Integration and Delivery (CI/CD use only) <cicd@example.com>
Please note that the shown key validity is not necessarily correct
unless you restart the program.


sec rsa2048/FFB8195B9A6E634C
created: 2022-06-04 expires: never usage: SCEA
trust: ultimate validity: unknown
ssb rsa2048/EAB3A38FBCEDC2CB
created: 2022-06-04 expires: never usage: SEA
[ unknown] (1). Continuous Integration and Delivery (CI/CD use only) <cicd@example.com>

Encrypt message
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
done
Cleanup
done
> ENCRYPT MESSAGE (end) --------------------------

Decrypt key


It decrypts /data/encrypted-msg.txt file content and creates /data/decrypted-msg.txt file.


$ make dec-msg

> DECRYPT 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 public key
gpg: key FFB8195B9A6E634C: public key "Continuous Integration and Delivery (CI/CD use only) <cicd@example.com>" imported
gpg: Total number processed: 1
gpg: imported: 1
Restore private key
gpg: key FFB8195B9A6E634C: "Continuous Integration and Delivery (CI/CD use only) <cicd@example.com>" not changed
gpg: key FFB8195B9A6E634C: secret key imported
gpg: Total number processed: 1
gpg: unchanged: 1
gpg: secret keys read: 1
gpg: secret keys imported: 1
Decrypt message
gpg: encrypted with 2048-bit RSA key, ID EAB3A38FBCEDC2CB, created 2022-06-04
"Continuous Integration and Delivery (CI/CD use only) <cicd@example.com>"
gpg: Signature made Sat Jun 4 11:04:54 2022 UTC
gpg: using RSA key 3535B47B7109B7CE4283DC47EAB3A38FBCEDC2CB
gpg: Good signature from "Continuous Integration and Delivery (CI/CD use only) <cicd@example.com>" [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg: There is no indication that the signature belongs to the owner.
Primary key fingerprint: 4AF1 87A9 75C1 6AA5 3E8D E0FA FFB8 195B 9A6E 634C
Subkey fingerprint: 3535 B47B 7109 B7CE 4283 DC47 EAB3 A38F BCED C2CB
Cleanup
done
> DECRYPT MESSAGE (end) --------------------------