使用go.crypto/openpgp验证签名

6

我有一个二进制文件:

foo.bin

使用gpg密钥签名了此文件,以创建:

foo.bin.sig

我有一个包含用于签名二进制文件的公钥的文件。
我想做的是使用Go验证此签名。
我阅读了go.crypto/openpgp文档,但对于这种用例并不特别有帮助。
验证将在远程机器上完成。理想情况下,我想避免在运行此代码的机器上使用密钥环。公钥可以轻松地存储在可执行文件本身中...如果我能找出如何完成此验证。
我认为需要执行的步骤如下:
创建一个仅表示公钥的实体
打开二进制文件和签名,并将其传递给某个验证函数
主要问题是:如何仅使用公钥编写此验证函数?

我不太清楚您的要求。GPG密钥验证是否可以用其他方法替代,比如SHA256/MD5/RSA等? - Shane Hou
根据维基百科,OpenPGP签名是文件哈希的签名。签名使用RSA或DSA完成,哈希可以使用许多算法完成。我认为您需要了解.sig文件以验证签名。然后,crypto包应该具有您所需的所有方法。如果您找到关于.sig文件定义的文档(我没有找到),请在此处放置它。我也想看看。 - user983716
1个回答

6

openpgp API并不是最直观易用的,但我试着去使用它(这是个双关语),下面是我的成果:

package main

import (
    "bytes"
    "code.google.com/p/go.crypto/openpgp/packet"
    "encoding/hex"
    "errors"
    "fmt"
    "io/ioutil"
    "os"
)

// gpg --export YOURKEYID --export-options export-minimal,no-export-attributes | hexdump /dev/stdin -v -e '/1 "%02X"'
var publicKeyHex string = "99[VERY LONG HEX STRING]B6"

func main() {
    if len(os.Args) != 3 {
        fmt.Println("Usage: " + os.Args[0] + " <file> <signature file>")
        return
    }

    err := checkSig(os.Args[1], os.Args[2])

    if err != nil {
        fmt.Println("Invalid signature : ")
        fmt.Println(err)
    } else {
        fmt.Println("Valid signature")
    }
}

func checkSig(fileName string, sigFileName string) error {
    // First, get the content of the file we have signed
    fileContent, err := ioutil.ReadFile(fileName)
    if err != nil {
        return err
    }

    // Get a Reader for the signature file
    sigFile, err := os.Open(sigFileName)
    if err != nil {
        return err
    }

    defer func() {
        if err := sigFile.Close(); err != nil {
            panic(err)
        }
    }()

    // Read the signature file
    pack, err := packet.Read(sigFile)
    if err != nil {
        return err
    }

    // Was it really a signature file ? If yes, get the Signature
    signature, ok := pack.(*packet.Signature)
    if !ok {
        return errors.New(os.Args[2] + " is not a valid signature file.")
    }

    // For convenience, we have the key in hexadecimal, convert it to binary
    publicKeyBin, err := hex.DecodeString(publicKeyHex)
    if err != nil {
        return err
    }

    // Read the key
    pack, err = packet.Read(bytes.NewReader(publicKeyBin))
    if err != nil {
        return err
    }

    // Was it really a public key file ? If yes, get the PublicKey
    publicKey, ok := pack.(*packet.PublicKey)
    if !ok {
        return errors.New("Invalid public key.")
    }

    // Get the hash method used for the signature
    hash := signature.Hash.New()

    // Hash the content of the file (if the file is big, that's where you have to change the code to avoid getting the whole file in memory, by reading and writting in small chunks)
    _, err = hash.Write(fileContent)
    if err != nil {
        return err
    }

    // Check the signature
    err = publicKey.VerifySignature(hash, signature)
    if err != nil {
        return err
    }

    return nil
}

根据要求,我将公钥放入代码中。 您可以像这样进行测试:

$ go run testpgp.go foo.bin foo.bin.sig

如果您签署的文件非常大,您可能需要稍微更改代码以避免在内存中加载它。

在我的情况下,签名和公钥都是加密的,因此我需要使用 armor 包来解码相应的块:https://gist.github.com/FZambia/f91ddffb1a2b776d56e1988c6048e4d8 - Alex Emelin
很棒的答案!非常详细,代码也很规范。 - Gabriel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接