Skip to content

Instantly share code, notes, and snippets.

@eliquious
Created January 4, 2016 05:01
Show Gist options
  • Save eliquious/9e96017f47d9bd43cdf9 to your computer and use it in GitHub Desktop.
Save eliquious/9e96017f47d9bd43cdf9 to your computer and use it in GitHub Desktop.

Revisions

  1. eliquious created this gist Jan 4, 2016.
    41 changes: 41 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,41 @@

    ## Building

    ```
    go build -o goencrypt main.go
    ```

    ## Generating Keys

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

    ## Encrypting

    ```sh
    # 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

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

    ## Decrypting

    ```sh
    # Decrypts STDIN and writes decrypted file to STDOUT
    cat file.txt.asc | ./goencrypt --public=keyname.pubkey --private=keyname.privkey decrypt > file.txt.bak
    ```
    310 changes: 310 additions & 0 deletions main.go
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,310 @@
    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)
    }