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

huangapple go评论89阅读模式
英文:

How do I extract the private key from a PKCS#12 container and save it in PKCS#8 format?

问题

我想要使用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 (
	"crypto/rsa"
	"crypto/x509"
	"errors"
	"fmt"
	"io/ioutil"

	"github.com/youmark/pkcs8"
	"golang.org/x/crypto/pkcs12"
)

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")
)

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

英文:

I want to be able to send iOS APNS push notifications using AWS SNS with the aws golang SDK. I've created a p12 file following this instructions: https://support-aws.s3.amazonaws.com/Exporting-APNS-Cert-Keychain-Mac.pdf
now in order to get the private key and cert I need to implement the following openssl equivalent commands:

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

I can't find a way to do this in golang, any help will be appreciated. What seems to be the issue is converting the private key to pkcs8 format.

EDIT:

This is what I have been trying to do (in order to compile you need to change the first import in github.com/youmark/pkcs8 to golang.org/x/crypto/pbkdf2) :

import (
	"crypto/rsa"
	"crypto/x509"
	"errors"
	"fmt"
	"io/ioutil"

	"github.com/youmark/pkcs8"
	"golang.org/x/crypto/pkcs12"
)

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")
)

What I get when printing the converted key is gibberish, so I guess there is something wrong with my decoding process somewhere.

答案1

得分: 5

我认为你已经到达了目标。你已经将密钥转换为PKCS#8格式,但由于以二进制DER形式打印出来,所以显示的是无意义的字符。密钥只需要以PEM格式进行编码。

测试的一种方法是创建一个包含自签名证书和密钥的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")
)

希望对你有所帮助。

英文:

I think you're there. You've converted the key to PKCS#8 format, but it's displaying as gibberish because it's printed in binary DER form. The key just needs to be encoded in pem format.

One way to test this is by creating your own pkcs#12 file containing a self signed certificate & key. A benefit is you can vary the expiry duration to exercise your certificate expiry error handling:

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

It generates key.pem and cert.pem. Combine key & cert:

cat key.pem cert.pem > both.pem

Bundle into pkcs#12:

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

Here bundle.12 is a pkcs#12 file in binary DER form containing a certificate and private key, protected by a password.

Run the go program (see source below) to extract the certificate and key:

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

The extracted certificate is identical to the original certificate. The extracted private key is similar to the original private key, but now in pkcs#8 format.

You can extract the original rsa key from the pkcs8 file. The original key.pem is identical to key.final.pem:

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

You could also verify the extracted pkcs#8 private key has the same modulus as the original certificate:

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

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

Note that the extracted pkcs#8 private key is unencrypted; that may not be what you want depending on how the key's going to be used.

Here's a slightly modified version of the go program (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")
)

Hope that helps.

huangapple
  • 本文由 发表于 2016年2月24日 07:49:09
  • 转载请务必保留本文链接:https://go.coder-hub.com/35590600.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定