Skip to content

Instantly share code, notes, and snippets.

@eliquious
Created January 4, 2016 05:01
Show Gist options
  • Star 56 You must be signed in to star a gist
  • Fork 19 You must be signed in to fork a gist
  • Save eliquious/9e96017f47d9bd43cdf9 to your computer and use it in GitHub Desktop.
Save eliquious/9e96017f47d9bd43cdf9 to your computer and use it in GitHub Desktop.
Golang OpenPGP examples

Building

go build -o goencrypt main.go

Generating Keys

# Produces keyname.pubkey and keyname.privkey
./goencrypt keygen keyname

Encrypting

# Encrypts STDIN and outputs encrypted data to STDOUT (ascii armored)
cat file.txt | ./goencrypt --public=keyname.pubkey --private=keyname.privkey encrypt > file.txt.asc

Signing

# Signs STDIN and writes signature to STDOUT (ascii armored)
cat file.txt | ./goencrypt --public=keyname.pubkey --private=keyname.privkey sign > file.txt.sig

Verifying

# Verifies STDIN against signature and public key
cat file.txt | ./goencrypt --public=keyname.pubkey --sig=file.txt.sig verify

Decrypting

# Decrypts STDIN and writes decrypted file to STDOUT
cat file.txt.asc | ./goencrypt --public=keyname.pubkey --private=keyname.privkey decrypt > file.txt.bak
package main
import (
"errors"
"io"
"os"
"path/filepath"
"time"
"crypto"
"crypto/rand"
"crypto/rsa"
_ "crypto/sha256"
_ "golang.org/x/crypto/ripemd160"
"compress/gzip"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/armor"
"golang.org/x/crypto/openpgp/packet"
kingpin "gopkg.in/alecthomas/kingpin.v2"
)
var (
// Goencrypt app
app = kingpin.New("goencrypt", "A command line tool for encrypting files")
bits = app.Flag("bits", "Bits for keys").Default("4096").Int()
privateKey = app.Flag("private", "Private key").String()
publicKey = app.Flag("public", "Public key").String()
signatureFile = app.Flag("sig", "Signature File").String()
// Generates new public and private keys
keyGenCmd = app.Command("keygen", "Generates a new public/private key pair")
keyOutputPrefix = keyGenCmd.Arg("prefix", "Prefix of key files").Required().String()
keyOutputDir = keyGenCmd.Flag("d", "Output directory of key files").Default(".").String()
// Encrypts a file with a public key
encryptionCmd = app.Command("encrypt", "Encrypt from stdin")
// Signs a file with a private key
signCmd = app.Command("sign", "Sign stdin")
// Verifies a file was signed with the public key
verifyCmd = app.Command("verify", "Verify a signature of stdin")
// Decrypts a file with a private key
decryptionCmd = app.Command("decrypt", "Decrypt from stdin")
)
func main() {
switch kingpin.MustParse(app.Parse(os.Args[1:])) {
// generate keys
case keyGenCmd.FullCommand():
generateKeys()
// case createEntityCmd.FullCommand():
// newEntity()
case encryptionCmd.FullCommand():
encryptFile()
case signCmd.FullCommand():
signFile()
case verifyCmd.FullCommand():
verifyFile()
case decryptionCmd.FullCommand():
decryptFile()
default:
kingpin.FatalUsage("Unknown command")
}
}
func encodePrivateKey(out io.Writer, key *rsa.PrivateKey) {
w, err := armor.Encode(out, openpgp.PrivateKeyType, make(map[string]string))
kingpin.FatalIfError(err, "Error creating OpenPGP Armor: %s", err)
pgpKey := packet.NewRSAPrivateKey(time.Now(), key)
kingpin.FatalIfError(pgpKey.Serialize(w), "Error serializing private key: %s", err)
kingpin.FatalIfError(w.Close(), "Error serializing private key: %s", err)
}
func decodePrivateKey(filename string) *packet.PrivateKey {
// open ascii armored private key
in, err := os.Open(filename)
kingpin.FatalIfError(err, "Error opening private key: %s", err)
defer in.Close()
block, err := armor.Decode(in)
kingpin.FatalIfError(err, "Error decoding OpenPGP Armor: %s", err)
if block.Type != openpgp.PrivateKeyType {
kingpin.FatalIfError(errors.New("Invalid private key file"), "Error decoding private key")
}
reader := packet.NewReader(block.Body)
pkt, err := reader.Next()
kingpin.FatalIfError(err, "Error reading private key")
key, ok := pkt.(*packet.PrivateKey)
if !ok {
kingpin.FatalIfError(errors.New("Invalid private key"), "Error parsing private key")
}
return key
}
func encodePublicKey(out io.Writer, key *rsa.PrivateKey) {
w, err := armor.Encode(out, openpgp.PublicKeyType, make(map[string]string))
kingpin.FatalIfError(err, "Error creating OpenPGP Armor: %s", err)
pgpKey := packet.NewRSAPublicKey(time.Now(), &key.PublicKey)
kingpin.FatalIfError(pgpKey.Serialize(w), "Error serializing public key: %s", err)
kingpin.FatalIfError(w.Close(), "Error serializing public key: %s", err)
}
func decodePublicKey(filename string) *packet.PublicKey {
// open ascii armored public key
in, err := os.Open(filename)
kingpin.FatalIfError(err, "Error opening public key: %s", err)
defer in.Close()
block, err := armor.Decode(in)
kingpin.FatalIfError(err, "Error decoding OpenPGP Armor: %s", err)
if block.Type != openpgp.PublicKeyType {
kingpin.FatalIfError(errors.New("Invalid private key file"), "Error decoding private key")
}
reader := packet.NewReader(block.Body)
pkt, err := reader.Next()
kingpin.FatalIfError(err, "Error reading private key")
key, ok := pkt.(*packet.PublicKey)
if !ok {
kingpin.FatalIfError(errors.New("Invalid public key"), "Error parsing public key")
}
return key
}
func decodeSignature(filename string) *packet.Signature {
// open ascii armored public key
in, err := os.Open(filename)
kingpin.FatalIfError(err, "Error opening public key: %s", err)
defer in.Close()
block, err := armor.Decode(in)
kingpin.FatalIfError(err, "Error decoding OpenPGP Armor: %s", err)
if block.Type != openpgp.SignatureType {
kingpin.FatalIfError(errors.New("Invalid signature file"), "Error decoding signature")
}
reader := packet.NewReader(block.Body)
pkt, err := reader.Next()
kingpin.FatalIfError(err, "Error reading signature")
sig, ok := pkt.(*packet.Signature)
if !ok {
kingpin.FatalIfError(errors.New("Invalid signature"), "Error parsing signature")
}
return sig
}
func encryptFile() {
pubKey := decodePublicKey(*publicKey)
privKey := decodePrivateKey(*privateKey)
to := createEntityFromKeys(pubKey, privKey)
w, err := armor.Encode(os.Stdout, "Message", make(map[string]string))
kingpin.FatalIfError(err, "Error creating OpenPGP Armor: %s", err)
defer w.Close()
plain, err := openpgp.Encrypt(w, []*openpgp.Entity{to}, nil, nil, nil)
kingpin.FatalIfError(err, "Error creating entity for encryption")
defer plain.Close()
compressed, err := gzip.NewWriterLevel(plain, gzip.BestCompression)
kingpin.FatalIfError(err, "Invalid compression level")
n, err := io.Copy(compressed, os.Stdin)
kingpin.FatalIfError(err, "Error writing encrypted file")
kingpin.Errorf("Encrypted %d bytes", n)
compressed.Close()
}
func decryptFile() {
pubKey := decodePublicKey(*publicKey)
privKey := decodePrivateKey(*privateKey)
entity := createEntityFromKeys(pubKey, privKey)
block, err := armor.Decode(os.Stdin)
kingpin.FatalIfError(err, "Error reading OpenPGP Armor: %s", err)
if block.Type != "Message" {
kingpin.FatalIfError(err, "Invalid message type")
}
var entityList openpgp.EntityList
entityList = append(entityList, entity)
md, err := openpgp.ReadMessage(block.Body, entityList, nil, nil)
kingpin.FatalIfError(err, "Error reading message")
compressed, err := gzip.NewReader(md.UnverifiedBody)
kingpin.FatalIfError(err, "Invalid compression level")
defer compressed.Close()
n, err := io.Copy(os.Stdout, compressed)
kingpin.FatalIfError(err, "Error reading encrypted file")
kingpin.Errorf("Decrypted %d bytes", n)
}
func signFile() {
pubKey := decodePublicKey(*publicKey)
privKey := decodePrivateKey(*privateKey)
signer := createEntityFromKeys(pubKey, privKey)
err := openpgp.ArmoredDetachSign(os.Stdout, signer, os.Stdin, nil)
kingpin.FatalIfError(err, "Error signing input")
}
func verifyFile() {
pubKey := decodePublicKey(*publicKey)
sig := decodeSignature(*signatureFile)
hash := sig.Hash.New()
io.Copy(hash, os.Stdin)
err := pubKey.VerifySignature(hash, sig)
kingpin.FatalIfError(err, "Error signing input")
kingpin.Errorf("Verified signature")
}
func createEntityFromKeys(pubKey *packet.PublicKey, privKey *packet.PrivateKey) *openpgp.Entity {
config := packet.Config{
DefaultHash: crypto.SHA256,
DefaultCipher: packet.CipherAES256,
DefaultCompressionAlgo: packet.CompressionZLIB,
CompressionConfig: &packet.CompressionConfig{
Level: 9,
},
RSABits: *bits,
}
currentTime := config.Now()
uid := packet.NewUserId("", "", "")
e := openpgp.Entity{
PrimaryKey: pubKey,
PrivateKey: privKey,
Identities: make(map[string]*openpgp.Identity),
}
isPrimaryId := false
e.Identities[uid.Id] = &openpgp.Identity{
Name: uid.Name,
UserId: uid,
SelfSignature: &packet.Signature{
CreationTime: currentTime,
SigType: packet.SigTypePositiveCert,
PubKeyAlgo: packet.PubKeyAlgoRSA,
Hash: config.Hash(),
IsPrimaryId: &isPrimaryId,
FlagsValid: true,
FlagSign: true,
FlagCertify: true,
IssuerKeyId: &e.PrimaryKey.KeyId,
},
}
keyLifetimeSecs := uint32(86400 * 365)
e.Subkeys = make([]openpgp.Subkey, 1)
e.Subkeys[0] = openpgp.Subkey{
PublicKey: pubKey,
PrivateKey: privKey,
Sig: &packet.Signature{
CreationTime: currentTime,
SigType: packet.SigTypeSubkeyBinding,
PubKeyAlgo: packet.PubKeyAlgoRSA,
Hash: config.Hash(),
PreferredHash: []uint8{8}, // SHA-256
FlagsValid: true,
FlagEncryptStorage: true,
FlagEncryptCommunications: true,
IssuerKeyId: &e.PrimaryKey.KeyId,
KeyLifetimeSecs: &keyLifetimeSecs,
},
}
return &e
}
func generateKeys() {
key, err := rsa.GenerateKey(rand.Reader, *bits)
kingpin.FatalIfError(err, "Error generating RSA key: %s", err)
priv, err := os.Create(filepath.Join(*keyOutputDir, *keyOutputPrefix+".privkey"))
kingpin.FatalIfError(err, "Error writing private key to file: %s", err)
defer priv.Close()
pub, err := os.Create(filepath.Join(*keyOutputDir, *keyOutputPrefix+".pubkey"))
kingpin.FatalIfError(err, "Error writing public key to file: %s", err)
defer pub.Close()
encodePrivateKey(priv, key)
encodePublicKey(pub, key)
}
@dolmen
Copy link

dolmen commented Dec 15, 2016

Could you add a license to this gist?

@mh-cbon
Copy link

mh-cbon commented May 18, 2019

in decodePublicKeyfunction, take care that error messages are slightly wrong as they refer to the private key instead of the public key.

@mojocn
Copy link

mojocn commented May 16, 2020

Where is golang.org/x/crypto/ripemd160 used? Ripemd160 is deprecated
and how to replace it?

@mh-cbon
Copy link

mh-cbon commented Aug 3, 2020

Where is golang.org/x/crypto/ripemd160 used? Ripemd160 is deprecated
and how to replace it?

https://gist.github.com/eliquious/9e96017f47d9bd43cdf9#file-main-go-L14 ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment