Search code examples
encryptiongopublic-key-encryptiongnupgopenpgp

Issues with openpgp golang gpg library


So I'm pretty new to golang and i'm struggling to get a working example going of encrypting some text with openpgp and decrypting it again.

Here is what I have so far: (https://gist.github.com/93750a142d3de4e8fdd2.git)

package main

import (
    "log"
    "bytes"
    "code.google.com/p/go.crypto/openpgp"
    "encoding/base64"
    "io/ioutil"
    "os"
)

// create gpg keys with
// $ gpg --gen-key
// ensure you correct paths and passphrase

const mysecretstring = "this is so very secret!"
const secretKeyring = "/Users/stuart-warren/.gnupg/secring.gpg"
const publicKeyring = "/Users/stuart-warren/.gnupg/pubring.gpg"
const passphrase = "1234"

func main() {
    log.Printf("Secret: ", mysecretstring)
    log.Printf("Secret Keyring: ", secretKeyring)
    log.Printf("Public Keyring: ", publicKeyring)
    log.Printf("Passphrase: ", passphrase)

    // Read in public key
    keyringFileBuffer, _ := os.Open(publicKeyring)
    defer keyringFileBuffer.Close()
    entitylist, _ := openpgp.ReadKeyRing(keyringFileBuffer)

    // encrypt string
    buf := new(bytes.Buffer)
    w, _ := openpgp.Encrypt(buf, entitylist, nil, nil, nil)
    w.Write([]byte(mysecretstring))

    // Encode to base64
    bytesp, _ := ioutil.ReadAll(buf)
    encstr := base64.StdEncoding.EncodeToString(bytesp)

    // Output encrypted/encoded string
    log.Printf("Encrypted Secret: ", encstr)

    // Here is where I would transfer the encrypted string to someone else 
    // but we'll just decrypt it in the same code

    // init some vars
    var entity2 *openpgp.Entity
    var entitylist2 openpgp.EntityList

    // Open the private key file
    keyringFileBuffer2, _ := os.Open(secretKeyring)
    defer keyringFileBuffer2.Close()
    entitylist2, _ = openpgp.ReadKeyRing(keyringFileBuffer2)
    entity2 = entitylist2[0]

    // Get the passphrase and read the private key.
    // Have not touched the encrypted string yet
    passphrasebyte := []byte(passphrase)
    log.Printf("Decrypting private key using passphrase")
    entity2.PrivateKey.Decrypt(passphrasebyte)
    for _, subkey := range entity2.Subkeys {
            subkey.PrivateKey.Decrypt(passphrasebyte)
    }
    log.Printf("Finished decrypting private key using passphrase")

    // Decode the base64 string
    dec, _ := base64.StdEncoding.DecodeString(encstr)

    // Decrypt it with the contents of the private key
    md, _ := openpgp.ReadMessage(bytes.NewBuffer(dec), entitylist2, nil, nil)
    bytess, _ := ioutil.ReadAll(md.UnverifiedBody)
    decstr := string(bytess)

    // should be done
    log.Printf("Decrypted Secret: ", decstr)

}

This is based off of https://github.com/jyap808/jaeger

When I run it, it seems to partially work, but only outputs some of the characters of the original string... Changing the original string causes some very weird issues.

2014/09/07 22:59:38 Secret: %!(EXTRA string=this is so very secret!)
2014/09/07 22:59:38 Secret Keyring: %!(EXTRA string=/Users/stuart-warren/.gnupg/secring.gpg)
2014/09/07 22:59:38 Public Keyring: %!(EXTRA string=/Users/stuart-warren/.gnupg/pubring.gpg)
2014/09/07 22:59:38 Passphrase: %!(EXTRA string=1234)
2014/09/07 22:59:38 Encrypted Secret: %!(EXTRA string=wcBMA5a76vUxixWPAQgAOkrt/LQ3u++VbJ/20egxCUzMqcMYtq+JXL7SqbB5S1KrgHhGd8RHUmxy2h45hOLcAt+kfvSz0EJ/EsCmwnbP6HRPEqiMLt6XaVS26Rr9HQHPpRBZkqnwAP0EmlYNnF5zjnU5xTcEOyyr7EYhEgDv0Ro1FQkaCL2xdBhDCXs4EdQsjVrcECWOt0KgbCWs+N/0cEdeyHwodkaDgJ7NMq/pPuviaRu4JHCIxMiyz8yhOCHOM+bI80KsJesjGrgbjnGDfJUZNYDBNc8PqzfC39lB2MBrn/w07thJxvjbep39R0u2C4eEcroTRLB+t9i4fJNiVpoSclYRSZXm5OsYYv/XwtLgAeRZ07lFEsGoHSbqGLUnHFFw4Svk4FPgCuGVpOCS4vYiisDg+ORYj8dpu/Z3gSlVJ6mhSr7H4J3i9vItRuBx4WUB4HHgmQ==)
2014/09/07 22:59:38 Decrypting private key using passphrase
2014/09/07 22:59:38 Finished decrypting private key using passphrase
2014/09/07 22:59:38 Decrypted Secret: %!(EXTRA string=this)

Clearly there is something I'm not understanding, so would appreciate any assistance given.


Solution

  • A reminder that security is unusually treacherous territory, and if there's a way to call on other well-tested code even more of your toplevel task than just what Go's OpenPGP package is handling for you, consider it. It's good that at least low-level details are outsourced to openpgp because they're nasty and so so easy to get wrong. But tiny mistakes at any level can make crypto features worse than useless; if there's a way to write less security-critical code, that's one of the best things anyone can do for security.

    On the specific question: you have to Close() the writer to get everything flushed out (a trait OpenPGP's writer shares with, say, compress/gzip's).

    Unrelated changes: the way you're printing things is a better fit log.Println, which just lets you pass a bunch of values you want printed with spaces in between (like, say, Python print), rather than needing format specifiers like "%s" or "%d". (The "EXTRA" in your initial output is what Go's Printf emits when you pass more things than you had format specifiers for.) It's also best practice to check errors (I dropped if err != nils where I saw a need, but inelegantly and without much thought, and I may not have gotten all the calls) and to run go fmt on your code.

    Again, I can't testify to the seaworthiness of this code or anything like that. But now it round-trips all the text. I wound up with:

    package main
    
    import (
        "bytes"
        "code.google.com/p/go.crypto/openpgp"
        "encoding/base64"
        "io/ioutil"
        "log"
        "os"
    )
    
    // create gpg keys with
    // $ gpg --gen-key
    // ensure you correct paths and passphrase
    
    const mysecretstring = "this is so very secret!"
    const prefix, passphrase = "/Users/stuart-warren/", "1234"
    const secretKeyring = prefix + ".gnupg/secring.gpg"
    const publicKeyring = prefix + ".gnupg/pubring.gpg"
    
    func encTest() error {
        log.Println("Secret:", mysecretstring)
        log.Println("Secret Keyring:", secretKeyring)
        log.Println("Public Keyring:", publicKeyring)
        log.Println("Passphrase:", passphrase)
    
        // Read in public key
        keyringFileBuffer, _ := os.Open(publicKeyring)
        defer keyringFileBuffer.Close()
        entitylist, err := openpgp.ReadKeyRing(keyringFileBuffer)
        if err != nil {
            return err
        }
    
        // encrypt string
        buf := new(bytes.Buffer)
        w, err := openpgp.Encrypt(buf, entitylist, nil, nil, nil)
        if err != nil {
            return err
        }
        _, err = w.Write([]byte(mysecretstring))
        if err != nil {
            return err
        }
        err = w.Close()
        if err != nil {
            return err
        }
    
        // Encode to base64
        bytesp, err := ioutil.ReadAll(buf)
        if err != nil {
            return err
        }
        encstr := base64.StdEncoding.EncodeToString(bytesp)
    
        // Output encrypted/encoded string
        log.Println("Encrypted Secret:", encstr)
    
        // Here is where I would transfer the encrypted string to someone else
        // but we'll just decrypt it in the same code
    
        // init some vars
        var entity2 *openpgp.Entity
        var entitylist2 openpgp.EntityList
    
        // Open the private key file
        keyringFileBuffer2, err := os.Open(secretKeyring)
        if err != nil {
            return err
        }
        defer keyringFileBuffer2.Close()
        entitylist2, err = openpgp.ReadKeyRing(keyringFileBuffer2)
        if err != nil {
            return err
        }
        entity2 = entitylist2[0]
    
        // Get the passphrase and read the private key.
        // Have not touched the encrypted string yet
        passphrasebyte := []byte(passphrase)
        log.Println("Decrypting private key using passphrase")
        entity2.PrivateKey.Decrypt(passphrasebyte)
        for _, subkey := range entity2.Subkeys {
            subkey.PrivateKey.Decrypt(passphrasebyte)
        }
        log.Println("Finished decrypting private key using passphrase")
    
        // Decode the base64 string
        dec, err := base64.StdEncoding.DecodeString(encstr)
        if err != nil {
            return err
        }
    
        // Decrypt it with the contents of the private key
        md, err := openpgp.ReadMessage(bytes.NewBuffer(dec), entitylist2, nil, nil)
        if err != nil {
            return err
        }
        bytess, err := ioutil.ReadAll(md.UnverifiedBody)
        if err != nil {
            return err
        }
        decstr := string(bytess)
    
        // should be done
        log.Println("Decrypted Secret:", decstr)
    
        return nil
    }
    
    func main() {
        err := encTest()
        if err != nil {
            log.Fatal(err)
        }
    }