goopenpgp

OpenPGP - attempted encryption fails


I am attempting to get a repo (https://github.com/jchavannes/go-pgp) to encrypt. When running the code I get

EOF @ File: main.go  Function: main.main Line: 21
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x52cdc2]

The consolidated code looks like:

package main

import (
    "bytes"
    "compress/gzip"
    "crypto"
    "errors"
    "fmt"
    "io"

    "github.com/jimlawless/whereami"
    "golang.org/x/crypto/openpgp"
    "golang.org/x/crypto/openpgp/armor"
    "golang.org/x/crypto/openpgp/packet"
)

func main() {
    // Create public key entity
    publicKeyPacket, err := getPublicKeyPacket([]byte(TestPublicKey))
    if err != nil {
        fmt.Println(err.Error() + " @ " + whereami.WhereAmI())
    }
    pubEntity, err := createEntityFromKeys(publicKeyPacket, nil)
    if err != nil {
        fmt.Println(err.Error() + " @ " + whereami.WhereAmI())
    }

    // Encrypt message
    encrypted, _ := encrypt(pubEntity, []byte(TestMessage))
    fmt.Println(encrypted)
}

func getPublicKeyPacket(publicKey []byte) (*packet.PublicKey, error) {
    publicKeyReader := bytes.NewReader(publicKey)
    block, err := armor.Decode(publicKeyReader)
    if err != nil {
        return nil, err
    }

    if block.Type != openpgp.PublicKeyType {
        return nil, errors.New("Invalid public key data")
    }

    packetReader := packet.NewReader(block.Body)
    pkt, err := packetReader.Next()
    if err != nil {
        return nil, err
    }

    key, ok := pkt.(*packet.PublicKey)
    if !ok {
        return nil, err
    }
    return key, nil
}

func createEntityFromKeys(pubKey *packet.PublicKey, privKey *packet.PrivateKey) (*openpgp.Entity, error) {
    config := packet.Config{
        DefaultHash:            crypto.SHA256,
        DefaultCipher:          packet.CipherAES256,
        DefaultCompressionAlgo: packet.CompressionZLIB,
        CompressionConfig: &packet.CompressionConfig{
            Level: 9,
        },
        RSABits: 4096,
    }
    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, nil
}

func encrypt(entity *openpgp.Entity, message []byte) ([]byte, error) {
    // Create buffer to write output to
    buf := new(bytes.Buffer)

    // Create encoder
    encoderWriter, err := armor.Encode(buf, "Message", make(map[string]string))
    if err != nil {
        return []byte{}, fmt.Errorf("Error creating OpenPGP armor: %v", err)
    }

    // Create encryptor with encoder
    encryptorWriter, err := openpgp.Encrypt(encoderWriter, []*openpgp.Entity{entity}, nil, nil, nil)
    if err != nil {
        return []byte{}, fmt.Errorf("Error creating entity for encryption: %v", err)
    }

    // Create compressor with encryptor
    compressorWriter, err := gzip.NewWriterLevel(encryptorWriter, gzip.BestCompression)
    if err != nil {
        return []byte{}, fmt.Errorf("Invalid compression level: %v", err)
    }

    // Write message to compressor
    messageReader := bytes.NewReader(message)
    _, err = io.Copy(compressorWriter, messageReader)
    if err != nil {
        return []byte{}, fmt.Errorf("Error writing data to compressor: %v", err)
    }

    compressorWriter.Close()
    encryptorWriter.Close()
    encoderWriter.Close()

    // Return buffer output - an encoded, encrypted, and compressed message
    return buf.Bytes(), nil
}

const TestMessage = "hello world"

const TestPrivateKey = `-----BEGIN PGP PRIVATE KEY BLOCK-----
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
=5NT7
-----END PGP PRIVATE KEY BLOCK-----`

const TestPublicKey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
xsFNBFkNK+ABEADUpjJ/kz3j+iz9qnzUb6ONw+WHSLp8umnd1z06SBVkWFjYReqf
oPCOq67XDseK71ZSevrIt7EdTLAzl0xN8kB+8iedAGM5OCakDe3R8L83OGy1Em26
PbrrYs3TYKGDXW65TsGYCoETROGgU2zPvuBDU1RvVvd9vAlWHQis43BOWaaakCEc
00V3sdNcfV+lz7fNUXEgtmTCCr9NWX4gO3YeenIIxep4WD27VwscW5Q2B1cnxcFL
+TZzE2oVjtXljGSO94XsekuNU47zwJZNGyU6SSlSZ+KVXuSdkRRfNYHlgDWg5b8C
xVmdVUfsx3bmNlOlXoyETj83xvRlLxn3PYIgOz6OlYGba5oDogK2QLXGTXK1o9OE
kgoghmCNQqxocvb1hQXT8cEynIbAdc6/JknYaoic6ka1iTTz3uN8FEPw5gRlidcQ
3wkbmqIS0LJs3JmVbD7/BxMY1dwqMyvulfnLiTsWSPvk41o7dHf077t23V9w78Jg
h4Xq4HRvt37PtuO6eWW3c5aUIWmvvDqMbMEqp2y23noYoVNqEpVoHolDdoCSurv/
XxbNBnj46XwaIs6OlrO2htV0al2/WVTNnSLxCyoHXoJEDXyaOyNKn1jM/FczgYQJ
069uC804ohOfjLmbtUEYE7Hjeo5utPm2ryjnakgV5AStKgL0SyFZUwN/DwARAQAB
=gO1a
-----END PGP PUBLIC KEY BLOCK-----`

Any ideas how to fix this?


Solution

  • tl;dr

    1. always check for errors.
    2. The x/crypto/openpgp package is deprecated and you shouldn't be using it.

    So here's your error:

    EOF @ File: main.go  Function: main.main Line: 21
    

    That's coming back from getPublicKeyPacket([]byte(TestPublicKey)), where we see:

        publicKeyReader := bytes.NewReader(publicKey)
        block, err := armor.Decode(publicKeyReader)
        if err != nil {
            return nil, err
        }
    

    Looking at the documentation for armor.Decode, we see (emphasis mine):

    Decode reads a PGP armored block from the given Reader. It will ignore leading garbage. If it doesn't find a block, it will return nil, io.EOF.

    So, it looks like you have an invalid public key block. Let's test that theory!

    If we take the data from TestPublicKey and write it to a file...

    -----BEGIN PGP PUBLIC KEY BLOCK-----
    xsFNBFkNK+ABEADUpjJ/kz3j+iz9qnzUb6ONw+WHSLp8umnd1z06SBVkWFjYReqf
    oPCOq67XDseK71ZSevrIt7EdTLAzl0xN8kB+8iedAGM5OCakDe3R8L83OGy1Em26
    PbrrYs3TYKGDXW65TsGYCoETROGgU2zPvuBDU1RvVvd9vAlWHQis43BOWaaakCEc
    00V3sdNcfV+lz7fNUXEgtmTCCr9NWX4gO3YeenIIxep4WD27VwscW5Q2B1cnxcFL
    +TZzE2oVjtXljGSO94XsekuNU47zwJZNGyU6SSlSZ+KVXuSdkRRfNYHlgDWg5b8C
    xVmdVUfsx3bmNlOlXoyETj83xvRlLxn3PYIgOz6OlYGba5oDogK2QLXGTXK1o9OE
    kgoghmCNQqxocvb1hQXT8cEynIbAdc6/JknYaoic6ka1iTTz3uN8FEPw5gRlidcQ
    3wkbmqIS0LJs3JmVbD7/BxMY1dwqMyvulfnLiTsWSPvk41o7dHf077t23V9w78Jg
    h4Xq4HRvt37PtuO6eWW3c5aUIWmvvDqMbMEqp2y23noYoVNqEpVoHolDdoCSurv/
    XxbNBnj46XwaIs6OlrO2htV0al2/WVTNnSLxCyoHXoJEDXyaOyNKn1jM/FczgYQJ
    069uC804ohOfjLmbtUEYE7Hjeo5utPm2ryjnakgV5AStKgL0SyFZUwN/DwARAQAB
    =gO1a
    -----END PGP PUBLIC KEY BLOCK-----
    

    ...and then try to import that with gpg, we see:

    $ gpg --import testpublickey.asc
    gpg: invalid armor header: xsFNBFkNK+ABEADUpjJ/kz3j+iz9qnzUb6ONw+WHSLp8umnd1z06SBVkWFjYReqf\n
    gpg: key 39078898F94F4667: new key but contains no user ID - skipped
    

    It looks as if gpg agrees. Let's generate a proper public key and see if the problem goes away:

    $ gpg --full-generate-key
    [...]
    pub   rsa3072 2022-06-04 [SC]
          92F9D24D5428E3D2B5FE5774A7F5A28D78317D17
    uid                      Test Key <test@example.com>
    sub   rsa3072 2022-06-04 [E]
    $ gpg --export -a test@example.com > testpublickey.asc
    $ gpg --export-secret-key -a test@example.com > testsecretkey.asc
    

    If we embed the content of testpublickey.asc into your code and compile, it now runs without errors...

    $ go build
    $ ./gpgtest
    []
    

    ...although it doesn't appear to be generating useful output. But that's because you're ignoring the error from encrypt. If we handle an error response:

        encrypted, err := encrypt(pubEntity, []byte(TestMessage))
        if err != nil {
            panic(err)
        }
        fmt.Println(encrypted)
    

    Then we see:

    panic: Error creating entity for encryption: openpgp: invalid argument: cannot encrypt because no candidate hash functions are compiled in. (Wanted RIPEMD160 in this case.)
    
    goroutine 1 [running]:
    main.main()
            /home/lars/tmp/go/main.go:31 +0xe9
    

    And that error seems to be addressed here, which tells us the solution is to import the appropriate hash by adding:

    import (
        ...
        _ "golang.org/x/crypto/ripemd160"
    )
    

    With this change and a recompile, we now see as output:

    [45 45 45 45 45 66 69 71 73 78 32 77 101 115 115 97 103 101 45 45 45 45 45 10 10 119 99 68 77 65 54 102 49 111 111 49 52 77 88 48 88 65 81 119 65 87 70 43 115 72 119 89 121 117 106 83 70 76 57 100 73 101 113 115 107 81 103 117 106 99 118 97 69 57 52 47 57 51 105 57 118 118 98 67 82 111 113 57 98 10 99 54 66 85 108 103 116 50 111 72 97 122 86 110 105 50 49 121 68 122 112 119 82 115 81 109 105 48 43 100 90 103 81 99 52 54 112 65 89 77 106 116 48 81 117 66 116 50 65 77 100 86 71 118 56 79 121 66 47 70 115 87 89 116 10 49 100 83 80 90 115 121 73 116 113 118 50 65 114 122 102 100 114 68 84 54 75 116 99 110 76 70 85 114 72 67 55 102 66 112 74 115 88 51 86 75 53 111 106 75 82 117 115 54 70 72 56 52 83 101 54 55 98 66 70 113 81 90 107 10 113 80 66 68 57 108 49 66 84 88 80 83 57 73 100 56 111 74 114 115 119 82 87 88 119 117 90 80 75 121 49 101 107 71 53 53 69 73 73 71 78 49 84 69 70 90 100 103 54 110 72 83 119 83 100 53 84 112 120 107 71 68 80 48 10 104 67 110 122 121 109 111 97 53 55 78 84 71 97 82 110 65 47 119 88 71 86 104 104 110 87 68 55 85 120 107 116 53 90 100 52 84 88 43 107 109 119 102 48 50 108 80 115 66 122 114 122 118 50 87 50 56 51 97 85 79 109 70 118 10 71 113 55 71 69 80 119 83 116 74 100 47 49 90 81 113 110 109 48 65 80 86 72 69 69 108 114 98 120 67 122 83 66 82 80 103 51 78 73 79 112 106 100 121 83 48 112 116 57 82 107 114 111 82 87 43 57 99 50 76 98 100 54 83 10 112 119 88 110 76 56 78 100 114 49 70 43 105 122 101 84 109 112 76 99 82 101 119 89 88 57 107 113 113 69 106 78 117 103 90 54 65 117 85 83 77 73 121 104 102 101 99 121 108 69 78 99 47 103 87 98 103 103 51 54 65 81 101 116 10 86 52 65 48 122 88 122 82 121 102 122 121 76 53 56 67 101 54 112 65 71 85 54 118 115 66 114 73 83 122 86 107 108 105 118 108 69 115 114 99 83 75 119 98 67 79 111 78 112 112 118 117 105 53 108 89 77 57 77 65 110 110 111 79 10 98 56 109 78 50 72 110 110 117 85 102 48 43 43 76 67 90 48 98 105 48 117 89 66 102 43 120 68 87 70 108 122 115 87 69 73 81 66 111 114 78 70 82 100 117 97 85 77 76 73 104 47 89 108 116 65 104 108 90 81 111 47 122 120 10 90 86 55 98 102 108 53 103 88 74 117 89 83 83 86 43 113 100 72 75 43 106 109 103 83 121 89 90 75 100 89 110 80 78 112 49 53 71 48 55 50 74 85 73 52 52 82 119 56 66 113 97 81 80 75 54 52 105 101 76 67 65 106 104 10 43 43 110 103 80 103 65 61 10 61 121 72 81 117 10 45 45 45 45 45 69 78 68 32 77 101 115 115 97 103 101 45 45 45 45 45]
    

    Printing out a byte buffer isn't all that helpful, so let's wrap that with string:

        fmt.Println(string(encrypted))
    

    And that gives us:

    -----BEGIN Message-----
    
    wcDMA6f1oo14MX0XAQwABlLztwGC16in9i0kHm4M7FETC+1xgJDllBEGKqZWvPIb
    FruPomy+jBPMROuVpZl9qxBTJwkTvhXxMZsSZz+mWOzrsAanhffZtCRbZ5BC+f8U
    1iAkt35Sk73tN7w62+G7Unza0KlE8l3eqEb947wZSYAufdVnDESRd6ScTFuj/pxp
    0pnfpaOAjjZPp9DOpExpU+yIij94t0UrAwndsSE3D3Bi1DfJ1Q9GPgYQbplwio3u
    wfzFZOonIH2TG0AUwiY9lLVTu+u9rj5PovGdXuaIGTw26IAn05Xo3DRPdgee+W+2
    qMHEDE6PoZFcL3eTJ8ed0IbvRAHZ3oovJpo/nJ2Pf7RfJcsujYYfsAv7Y2jmFWDd
    cwo4NStRBCEZBhTfkZTJUI4kSpOtiXK2kqyQYuzKhQB3UnoMi4vbge9aOoT6x27f
    u/UuvdRAuZ4CjD0ptyauFZKPwQxkYZ3R3LeUrPXbOQ1KPLWtnECseeqSqGVWpmVs
    1DgjgF+cVXovuUFcWHhl0uYB/Q0NJCQV59t7QMtHalBIOv/KSOQUlOjGpRaZB5GL
    NXRSSTLYZOUXqUpFMC8em+uoT3KFLxeYVG/5wB5YXyK/47CZvEvK32g54rS5dlHh
    JergyAA=
    =iYGx
    -----END Message-----
    

    But that same answer also tells us that the crypto/openpgp package is deprecated and you shouldn't be using it. The linked issue has some suggestions for alternatives.