~/snippets/go-pkcs7-encrypt-decrypt
Published on

Encrypt and Decrypt with PKCS7

1403 words8 min read
// This is an example showing how to
// => Generate a SSH RSA Private/Public Key Pair
// => Encrypt it with PKCS7, using the Public Key
// => Decrypt the encryted result, using the Private Key
//
// References
// ==========
//
// => github.com/fullsailor/pkcs7
// => https://systemweakness.com/generating-rsa-pem-key-pair-using-go-7fd9f1471b58
// => https://ericchiang.github.io/post/go-tls/
package main

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/base64"
	"encoding/pem"
	"flag"
	"log/slog"
	"math/big"
	"os"
	"runtime/debug"
	"time"

	"github.com/fullsailor/pkcs7"
)

func init() {
	build, _ := debug.ReadBuildInfo()

	enableDebug := flag.Bool("debug", false, "whether to enable debug mode")
	flag.Parse()

	opts := &slog.HandlerOptions{}
	if *enableDebug {
		opts.Level = slog.LevelDebug
	}

	slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, opts)).With(
		slog.Group("program_info",
			slog.Int("pid", os.Getpid()),
			slog.String("go_version", build.GoVersion),
		),
	))
}

func main() {
	str := "Hello World"
	bits := 4096

	slog.Debug("Generating new RSA Private Key", slog.Int("bits", bits))
	privKey, err := rsa.GenerateKey(rand.Reader, bits)
	if err != nil {
		slog.Error("Error generating RSA Private Key", "error", err)
		os.Exit(1)
	}

	slog.Debug("Encoding Private Key to PEM format")
	privKeyPEM := &pem.Block{
		Type:  "RSA PRIVATE KEY",
		Bytes: x509.MarshalPKCS1PrivateKey(privKey),
	}

	slog.Debug("Creating Private Key file")
	privKeyFile, err := os.Create("private_key.pem")
	if err != nil {
		slog.Error("Error creating private_key file", "error", err)
		os.Exit(1)
	}

	slog.Debug("Encoding Private Key to file")
	if err := pem.Encode(privKeyFile, privKeyPEM); err != nil {
		slog.Error("Error encoding Private Key to file", "error", err)
		os.Exit(1)
	}
	if err := privKeyFile.Close(); err != nil {
		slog.Error("Error closing file")
		os.Exit(1)
	}

	slog.Debug("Extracting Public Key from Private Key")
	pubKey := &privKey.PublicKey

	slog.Debug("Encoding Public Key to PEM format")
	pubKeyPEM := &pem.Block{
		Type:  "RSA PUBLIC KEY",
		Bytes: x509.MarshalPKCS1PublicKey(pubKey),
	}

	slog.Debug("Creating Public Key file")
	pubKeyFile, err := os.Create("public_key.pem")
	if err != nil {
		slog.Error("Error creating public_key file", "error", err)
		os.Exit(1)
	}

	slog.Debug("Encoding Public Key to file")
	if err := pem.Encode(pubKeyFile, pubKeyPEM); err != nil {
		slog.Error("Error encoding Public Key to file", "error", err)
		os.Exit(1)
	}
	if err := pubKeyFile.Close(); err != nil {
		slog.Error("Error closing file")
		os.Exit(1)
	}

	slog.Info("RSA key pair generated successfully!")

	slog.Debug("Starting PKCS7 encryption process")

	slog.Debug("Generating x509 Certificate template")
	tmpl, err := x509CertificateTemplate()
	if err != nil {
		slog.Error("Error generating x509 Certificate template", "error", err)
		os.Exit(1)
	}

	slog.Debug("Creating Certificate DER")
	certDER, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, pubKey, privKey)
	if err != nil {
		slog.Error("Error generating x509 Certificate template", "error", err)
		os.Exit(1)
	}

	x509PubCert, err := x509.ParseCertificate(certDER)
	if err != nil {
		slog.Error("Error converting Public Key to x509", "error", err)
		os.Exit(1)
	}

	slog.Debug("Encrypting input", slog.String("str", str))
	encOut, err := pkcs7.Encrypt([]byte(str), []*x509.Certificate{x509PubCert})
	if err != nil {
		slog.Error("Error encrypting input", "error", err)
		os.Exit(1)
	}

	slog.Info("Input encrypted successfully", "base64", base64.StdEncoding.EncodeToString(encOut))

	slog.Debug("Starting PKCS7 decryption process")
	p7, err := pkcs7.Parse(encOut)
	if err != nil {
		slog.Error("Error parsing encrypted output", "error", err)
		os.Exit(1)
	}

	slog.Debug("Decrypting output")
	decOut, err := p7.Decrypt(x509PubCert, privKey)
	if err != nil {
		slog.Error("Error decrypting output", "error", err)
		os.Exit(1)
	}

	slog.Info("Output decrypted successfully", "str", str, "decrypted", string(decOut))
}

func x509CertificateTemplate() (*x509.Certificate, error) {
	slog.Debug("Creating Certificate Template")

	limit := new(big.Int).Lsh(big.NewInt(1), 128)

	sn, err := rand.Int(rand.Reader, limit)
	if err != nil {
		return nil, err
	}

	return &x509.Certificate{
		SerialNumber:          sn,
		Subject:               pkix.Name{Organization: []string{"Wibble Wobble, Inc."}},
		SignatureAlgorithm:    x509.SHA256WithRSA,
		NotBefore:             time.Now(),
		NotAfter:              time.Now().AddDate(1, 0, 0), // valid for a year
		BasicConstraintsValid: true,
	}, nil
}