如何从PKCS#12容器中提取私钥并将其保存为PKCS#8格式?

时间:2016-02-23 23:49:10

标签: go pkcs#12 pkcs#8

我希望能够使用 AWS SNS 和aws golang SDK发送iOS APNS 推送通知。我已按照此说明创建了一个p12文件:https://support-aws.s3.amazonaws.com/Exporting-APNS-Cert-Keychain-Mac.pdf 现在,为了获取私钥和​​证书,我需要实现以下openssl等效命令:

openssl pkcs12 -in MyCertificates.p12 -out MyCer.pem -clcerts -nokeys
openssl pkcs12 -in MyCertificates.p12 -out MyKey.pem -nocerts -nodes
openssl pkcs8 -topk8 -inform pem -in MyKey.pem -outform pem -nocrypt -out MyKeyCorrectFormat.pem

我无法在 golang 中找到解决方法,我们将不胜感激。似乎问题是将私钥转换为pkcs8格式。

修改

这就是我一直在尝试做的事情(为了编译你需要将github.com/youmark/pkcs8中的第一个导入更改为golang.org/x/crypto/pbkdf2):

import (
"golang.org/x/crypto/pkcs12"
"io/ioutil"
"fmt"
"encoding/pem"
"github.com/aws/aws-sdk-go/service/sns"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/aws"
"crypto/x509"
"crypto/rsa"
"errors"
"github.com/youmark/pkcs8"
)
func main()  {
b, err:= ioutil.ReadFile("myP12File.p12")
if err!=nil {
    fmt.Println(err)
    return
}
password := "123456"
_, pKey , err := Decode(b,password)
pKeyPkcs8, err := pkcs8.ConvertPrivateKeyToPKCS8(pKey,passwordBytes)
if err!=nil {
    fmt.Println(err)
}
fmt.Println(string(pKeyPkcs8))
}
// Decode and verify an in memory .p12 certificate (DER binary format).
func Decode(p12 []byte, password string) (*x509.Certificate,       *rsa.PrivateKey, error) {
// decode an x509.Certificate to verify
privateKey, cert, err := pkcs12.Decode(p12, password)
if err != nil {
    return nil, nil, err
}
if err := verify(cert); err != nil {
    return nil, nil, err
}

// assert that private key is RSA
priv, ok := privateKey.(*rsa.PrivateKey)
if !ok {
    return nil, nil, errors.New("expected RSA private key type")
}
return cert, priv, nil
}

// verify checks if a certificate has expired
func verify(cert *x509.Certificate) error {
_, err := cert.Verify(x509.VerifyOptions{})
if err == nil {
    return nil
}

switch e := err.(type) {
case x509.CertificateInvalidError:
    switch e.Reason {
    case x509.Expired:
        return ErrExpired
    default:
        return err
    }
case x509.UnknownAuthorityError:
    // Apple cert isn't in the cert pool
    // ignoring this error
    return nil
default:
    return err
}
}

// Certificate errors
var (
 ErrExpired = errors.New("certificate has expired or is not yet valid")
)

打印转换后的密钥时得到的是乱码,所以我猜我的解码过程出了问题。

1 个答案:

答案 0 :(得分:4)

我想你在那里。您已将密钥转换为PKCS#8格式,但它显示为乱码,因为它以二进制DER形式打印。密钥只需要以pem格式编码。

测试此方法的一种方法是创建自己的包含self signed certificate & key的pkcs#12文件。好处是您可以更改到期时间以执行证书到期错误处理:

go run generate_cert.go -ca -duration 30m -host gooble.com

它生成key.pem和cert.pem。结合关键&证书:

cat key.pem cert.pem > both.pem

捆绑到pkcs#12:

openssl pkcs12 -export -in both.pem -out bundle.12 -nodes -password pass:123456

这里的bundle.12是一个二进制DER形式的pkcs#12文件,其中包含一个受密码保护的证书和私钥。

运行go程序(参见下面的源代码)以提取证书和密钥:

go run pk.go -in bundle.12 -outkey key8.pem -outcert outcert.pem -password 123456

提取的证书与原始证书相同。提取的私钥类似于原始私钥,但现在采用pkcs#8格式。

您可以从pkcs8文件中提取原始rsa密钥。原始key.pem与key.final.pem相同:

openssl rsa -in key8.pem -out key.final.pem

您还可以验证提取的pkcs#8私钥与原始证书具有相同的模数:

openssl x509 -in cert.pem -noout -modulus
Modulus=AEB5770C4DA8D...05E12398BE1

openssl rsa -in key8.pem -noout -modulus
Modulus=AEB5770C4DA8D...05E12398BE1

请注意,提取的pkcs#8私钥未加密;这可能不是你想要的,这取决于密钥的使用方式。

以下是go程序(pk.go)的略微修改版本:

package main

import (
    "crypto/x509"
    "encoding/pem"
    "errors"
    "flag"
    "github.com/youmark/pkcs8"
    "golang.org/x/crypto/pkcs12"
    "io/ioutil"
    "log"
    "os"
)

var (
    in       = flag.String("in", "", "pkcs#12 input file (private key and certificate only)")
    password = flag.String("password", "", "to unlock the pkcs#12 bundle")
    outkey   = flag.String("outkey", "", "output filename of private key in pkcs#8 PEM format")
    outcert  = flag.String("outcert", "", "output filename of certificate in PEM format")
)

func main() {
    flag.Parse()

    if *in == "" || *password == "" || *outkey == "" || *outcert == "" {
        flag.Usage()
        os.Exit(1)
    }

    data, err := ioutil.ReadFile(*in)
    if err != nil {
        log.Fatal(err)
    }

    privateKey, certificate, err := pkcs12.Decode(data, *password)
    if err != nil {
        log.Fatal(err)
    }

    if err := verify(certificate); err != nil {
        log.Fatal(err)
    }

    keyBytes, err := pkcs8.ConvertPrivateKeyToPKCS8(privateKey)
    if err != nil {
        log.Fatal(err)
    }

    //could write private key as binary DER encoded (instead of pem below)
    //_, err = ioutil.WriteFile(*outkey,keyBytes,0644)

    //write private key as pem
    keyFile, err := os.Create(*outkey)
    if err != nil {
        log.Fatal(err)
    }
    defer keyFile.Close()
    err = pem.Encode(keyFile, &pem.Block{Type: "PRIVATE KEY", Bytes: keyBytes})
    if err != nil {
        log.Fatal(err)
    }

    certFile, err := os.Create(*outcert)
    if err != nil {
        log.Fatal(err)
    }
    defer certFile.Close()
    err = pem.Encode(certFile, &pem.Block{Type: "CERTIFICATE", Bytes: certificate.Raw})
    if err != nil {
        log.Fatal(err)
    }
}

// verify checks if a certificate has expired
func verify(cert *x509.Certificate) error {
    _, err := cert.Verify(x509.VerifyOptions{})
    if err == nil {
        return nil
    }

    switch e := err.(type) {
    case x509.CertificateInvalidError:
        switch e.Reason {
        case x509.Expired:
            return ErrExpired
        default:
            return err
        }
    case x509.UnknownAuthorityError:
        // Apple cert isn't in the cert pool
        // ignoring this error
        return nil
    default:
        return err
    }
}

// Certificate errors
var (
    ErrExpired = errors.New("certificate has expired or is not yet valid")
)

希望有所帮助。