encoding/pem - PEM 编解码
概述
encoding/pem 包提供了 PEM(Privacy Enhanced Mail)数据的编码和解码功能。
PEM 是什么:
- 📦 Base64 编码格式:将二进制数据编码为 ASCII 文本
- 🔧 带标记的格式:包含 BEGIN/END 标记
- 📋 广泛使用:证书、密钥、CSR 等加密相关数据
- 🛠️ 人类可读:文本格式,便于查看和传输
主要用途:
- 🌐 SSL/TLS 证书:X.509 证书的存储和传输
- 📧 私钥存储:RSA、ECDSA 等私钥的 PEM 格式
- 🔐 CSR 文件:证书签名请求(Certificate Signing Request)
- 📊 证书链:完整的证书链文件
- 🖼️ 公钥证书:SSH 公钥、PGP 密钥等
- 🔑 加密密钥:各种加密算法的密钥存储
重要说明:
- ⚠️ Base64 编码:PEM 使用 Base64 编码二进制数据
- ⚠️ 标记重要:BEGIN/END 标记标识数据类型
- ⚠️ ASCII 格式:纯文本,可在邮件中传输
- ⚠️ 仅编码格式:PEM 只是编码格式,不定义数据结构
- ✅ 标准库支持:Go 标准库提供完整支持
- ✅ 简单 API:Encode/Decode 两个核心函数
PEM 示例:
-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAKL0UG+mRKSzMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTYwODI0MTY0NjA3WhcNMjYwODIyMTY0NjA3WjBF
...
-----END CERTIFICATE-----
PEM 格式详解
PEM 结构
基本格式:
-----BEGIN <TYPE>-----
<Base64 编码的数据>
-----END <TYPE>-----
组成部分:
- BEGIN 标记:
-----BEGIN <TYPE>----- - Base64 数据:每行 64 个字符(可选)
- END 标记:
-----END <TYPE>-----
常见的 PEM 类型
| 类型 | 说明 | 用途 |
|---|---|---|
| CERTIFICATE | X.509 证书 | SSL/TLS 证书 |
| CERTIFICATE REQUEST | CSR | 证书签名请求 |
| PRIVATE KEY | PKCS#8 私钥 | 通用私钥格式 |
| RSA PRIVATE KEY | RSA 私钥 | RSA 算法私钥 |
| RSA PUBLIC KEY | RSA 公钥 | RSA 算法公钥 |
| EC PRIVATE KEY | EC 私钥 | 椭圆曲线私钥 |
| PUBLIC KEY | 公钥 | 通用公钥格式 |
| ENCRYPTED PRIVATE KEY | 加密私钥 | 密码保护的私钥 |
| OPENSSH PRIVATE KEY | OpenSSH 私钥 | SSH 密钥 |
| PGP | PGP 密钥 | PGP 加密密钥 |
PEM vs Base64
| 特性 | PEM | Base64 |
|---|---|---|
| 标记 | 有 BEGIN/END | 无 |
| 用途 | 证书、密钥 | 通用编码 |
| 格式 | 特定结构 | 纯编码 |
| 可读性 | 高(有类型标识) | 中 |
核心类型
1. Block - PEM 数据块
type Block struct {
// PEM 类型(如 "CERTIFICATE")
Type string
// 头部参数(可选)
Headers map[string]string
// Base64 解码后的原始字节
Bytes []byte
}
功能:表示一个 PEM 数据块。
字段说明:
Type:PEM 类型,如 “CERTIFICATE”、“PRIVATE KEY”Headers:可选的头部参数(很少使用)Bytes:解码后的原始二进制数据
示例:
block := &pem.Block{
Type: "CERTIFICATE",
Bytes: certificateData,
}
核心函数
1. Encode - 编码为 PEM
func Encode(w io.Writer, b *Block) error
功能:将 PEM Block 编码并写入 io.Writer。
参数:
w:输出写入器(文件、缓冲区等)b:PEM Block
示例:
block := &pem.Block{
Type: "CERTIFICATE",
Bytes: certData,
}
err := pem.Encode(os.Stdout, block)
if err != nil {
log.Fatal(err)
}
2. EncodeToMemory - 编码为内存
func EncodeToMemory(b *Block) []byte
功能:将 PEM Block 编码为字节切片。
返回值:PEM 格式的字节切片。
示例:
block := &pem.Block{
Type: "PRIVATE KEY",
Bytes: privateKeyData,
}
pemData := pem.EncodeToMemory(block)
fmt.Println(string(pemData))
3. Decode - 解码 PEM
func Decode(data []byte) (*Block, []byte)
功能:从字节切片解码 PEM 数据。
返回值:
*Block:解码后的 PEM Block[]byte:剩余的未处理数据(用于解析多个 Block)
示例:
block, rest := pem.Decode(pemData)
if block == nil {
log.Fatal("无效的 PEM 数据")
}
fmt.Printf("类型:%s\n", block.Type)
fmt.Printf("数据长度:%d 字节\n", len(block.Bytes))
// 处理剩余的 PEM Block
if len(rest) > 0 {
nextBlock, _ := pem.Decode(rest)
}
4. Parse - 解析 PEM(已废弃)
// Deprecated: 使用 Decode 代替
func Parse(data []byte) (*Block, []byte)
注意:已废弃,请使用 Decode。
完整示例
示例 1:基本编解码
package main
import (
"encoding/pem"
"fmt"
"log"
)
func main() {
fmt.Println("=== PEM 基本编解码 ===\n")
// 1. 创建示例数据
originalData := []byte("This is some binary data to encode as PEM.")
fmt.Printf("原始数据:\n")
fmt.Printf(" 内容:%s\n", string(originalData))
fmt.Printf(" 长度:%d 字节\n\n", len(originalData))
// 2. 编码为 PEM
fmt.Println("2. 编码为 PEM:")
block := &pem.Block{
Type: "TEST DATA",
Bytes: originalData,
}
pemData := pem.EncodeToMemory(block)
fmt.Printf("PEM 格式:\n%s\n", string(pemData))
// 3. 解码 PEM
fmt.Println("3. 解码 PEM:")
decodedBlock, rest := pem.Decode(pemData)
if decodedBlock == nil {
log.Fatal("解码失败")
}
fmt.Printf("解码结果:\n")
fmt.Printf(" 类型:%s\n", decodedBlock.Type)
fmt.Printf(" 数据长度:%d 字节\n", len(decodedBlock.Bytes))
fmt.Printf(" 内容:%s\n", string(decodedBlock.Bytes))
fmt.Printf(" 剩余数据:%d 字节\n\n", len(rest))
// 4. 验证
fmt.Printf("验证:%v\n", string(originalData) == string(decodedBlock.Bytes))
// 5. 添加头部参数
fmt.Println("\n4. 带头部参数的 PEM:")
blockWithHeaders := &pem.Block{
Type: "ENCRYPTED DATA",
Headers: map[string]string{
"Proc-Type": "4,ENCRYPTED",
"DEK-Info": "DES-EDE3-CBC,A1B2C3D4E5F6A7B8",
},
Bytes: originalData,
}
pemWithHeaders := pem.EncodeToMemory(blockWithHeaders)
fmt.Printf("%s\n", string(pemWithHeaders))
}
输出:
=== PEM 基本编解码 ===
原始数据:
内容:This is some binary data to encode as PEM.
长度:44 字节
2. 编码为 PEM:
PEM 格式:
-----BEGIN TEST DATA-----
VGhpcyBpcyBzb21lIGJpbmFyeSBkYXRhIHRvIGVuY29kZSBhcyBQRU0u
-----END TEST DATA-----
3. 解码 PEM:
解码结果:
类型:TEST DATA
数据长度:44 字节
内容:This is some binary data to encode as PEM.
剩余数据:0 字节
验证:true
4. 带头部参数的 PEM:
-----BEGIN ENCRYPTED DATA-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,A1B2C3D4E5F6A7B8
VGhpcyBpcyBzb21lIGJpbmFyeSBkYXRhIHRvIGVuY29kZSBhcyBQRU0u
-----END ENCRYPTED DATA-----
示例 2:解析多个 PEM Block
package main
import (
"encoding/pem"
"fmt"
"log"
"strings"
)
func main() {
fmt.Println("=== 解析多个 PEM Block ===\n")
// 1. 创建包含多个 Block 的 PEM 数据
pemStr := `-----BEGIN CERTIFICATE-----
Y2VydGlmaWNhdGUgZGF0YSAx
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
cHJpdmF0ZSBrZXkgZGF0YQ==
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
Y2VydGlmaWNhdGUgZGF0YSAy
-----END CERTIFICATE-----
`
fmt.Println("1. 多 Block PEM 数据:")
fmt.Printf("%s\n", pemStr)
// 2. 解析所有 Block
fmt.Println("2. 解析所有 Block:")
var blocks []*pem.Block
data := []byte(pemStr)
for len(data) > 0 {
block, rest := pem.Decode(data)
if block == nil {
break
}
blocks = append(blocks, block)
data = rest
fmt.Printf(" Block %d:\n", len(blocks))
fmt.Printf(" 类型:%s\n", block.Type)
fmt.Printf(" 数据:%s\n", string(block.Bytes))
fmt.Printf(" 头部数:%d\n\n", len(block.Headers))
}
// 3. 统计
fmt.Printf("总共解析:%d 个 Block\n", len(blocks))
certCount := 0
keyCount := 0
for _, block := range blocks {
switch block.Type {
case "CERTIFICATE":
certCount++
case "PRIVATE KEY":
keyCount++
}
}
fmt.Printf(" 证书:%d 个\n", certCount)
fmt.Printf(" 私钥:%d 个\n", keyCount)
// 4. 使用 strings.Split 解析
fmt.Println("\n3. 使用字符串分割:")
pemBlocks := strings.Split(pemStr, "-----END")
for i, p := range pemBlocks {
if strings.TrimSpace(p) == "" {
continue
}
// 重新添加 END 标记
p = p + "-----END"
block, _ := pem.Decode([]byte(p))
if block != nil {
fmt.Printf(" 块 %d: %s\n", i+1, block.Type)
}
}
}
输出:
=== 解析多个 PEM Block ===
1. 多 Block PEM 数据:
-----BEGIN CERTIFICATE-----
Y2VydGlmaWNhdGUgZGF0YSAx
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
cHJpdmF0ZSBrZXkgZGF0YQ==
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
Y2VydGlmaWNhdGUgZGF0YSAy
-----END CERTIFICATE-----
2. 解析所有 Block:
Block 1:
类型:CERTIFICATE
数据:certificate data 1
头部数:0
Block 2:
类型:PRIVATE KEY
数据:private key data
头部数:0
Block 3:
类型:CERTIFICATE
数据:certificate data 2
头部数:0
总共解析:3 个 Block
证书:2 个
私钥:1 个
3. 使用字符串分割:
块 1: CERTIFICATE
块 2: PRIVATE KEY
块 3: CERTIFICATE
示例 3:X.509 证书处理
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"log"
"math/big"
"time"
)
func main() {
fmt.Println("=== X.509 证书处理 ===\n")
// 1. 生成 RSA 私钥
fmt.Println("1. 生成 RSA 私钥:")
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
log.Fatal(err)
}
fmt.Printf(" ✓ 已生成 %d 位 RSA 私钥\n\n", 2048)
// 2. 创建证书模板
fmt.Println("2. 创建证书模板:")
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
if err != nil {
log.Fatal(err)
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"My Organization"},
CommonName: "localhost",
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(365 * 24 * time.Hour),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
fmt.Printf(" 序列号:%s\n", serialNumber.String())
fmt.Printf(" 组织:%s\n", template.Subject.Organization[0])
fmt.Printf(" 通用名:%s\n", template.Subject.CommonName)
fmt.Printf(" 有效期:365 天\n\n")
// 3. 自签名证书
fmt.Println("3. 创建自签名证书:")
certDER, err := x509.CreateCertificate(
rand.Reader,
&template,
&template, // 自签名,issuer = subject
&privateKey.PublicKey,
privateKey,
)
if err != nil {
log.Fatal(err)
}
fmt.Printf(" ✓ 证书已创建 (%d 字节)\n\n", len(certDER))
// 4. 编码证书为 PEM
fmt.Println("4. 编码证书为 PEM:")
certBlock := &pem.Block{
Type: "CERTIFICATE",
Bytes: certDER,
}
certPEM := pem.EncodeToMemory(certBlock)
fmt.Printf("%s\n", string(certPEM))
// 5. 编码私钥为 PEM
fmt.Println("5. 编码私钥为 PEM:")
privKeyBlock := &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
}
privKeyPEM := pem.EncodeToMemory(privKeyBlock)
fmt.Printf("%s\n", string(privKeyPEM))
// 6. 解码证书
fmt.Println("6. 解码证书:")
decodedCert, _ := pem.Decode(certPEM)
if decodedCert == nil {
log.Fatal("证书解码失败")
}
parsedCert, err := x509.ParseCertificate(decodedCert.Bytes)
if err != nil {
log.Fatal(err)
}
fmt.Printf(" 类型:%s\n", decodedCert.Type)
fmt.Printf(" 主题:%s\n", parsedCert.Subject.CommonName)
fmt.Printf(" 组织:%s\n", parsedCert.Subject.Organization[0])
fmt.Printf(" 序列号:%s\n", parsedCert.SerialNumber.String())
fmt.Printf(" 有效期:从 %s 到 %s\n",
parsedCert.NotBefore.Format("2006-01-02"),
parsedCert.NotAfter.Format("2006-01-02"))
}
输出:
=== X.509 证书处理 ===
1. 生成 RSA 私钥:
✓ 已生成 2048 位 RSA 私钥
2. 创建证书模板:
序列号:123456789012345678901234567890123456789
组织:My Organization
通用名:localhost
有效期:365 天
3. 创建自签名证书:
✓ 证书已创建 (1024 字节)
4. 编码证书为 PEM:
-----BEGIN CERTIFICATE-----
MIIDazCCAlOgAwIBAgIJA...(实际证书数据)...
-----END CERTIFICATE-----
5. 编码私钥为 PEM:
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA...(实际私钥数据)...
-----END RSA PRIVATE KEY-----
6. 解码证书:
类型:CERTIFICATE
主题:localhost
组织:My Organization
序列号:123456789012345678901234567890123456789
有效期:从 2024-01-15 到 2025-01-15
示例 4:证书链处理
package main
import (
"encoding/pem"
"fmt"
"log"
"os"
)
// CertificateChain 证书链
type CertificateChain struct {
Certificates [][]byte
}
// AddCertificate 添加证书
func (cc *CertificateChain) AddCertificate(pemData []byte) error {
block, _ := pem.Decode(pemData)
if block == nil {
return fmt.Errorf("无效的 PEM 数据")
}
if block.Type != "CERTIFICATE" {
return fmt.Errorf("不是证书类型:%s", block.Type)
}
cc.Certificates = append(cc.Certificates, block.Bytes)
return nil
}
// EncodeToPEM 编码为 PEM
func (cc *CertificateChain) EncodeToPEM() []byte {
var result []byte
for i, certBytes := range cc.Certificates {
block := &pem.Block{
Type: "CERTIFICATE",
Bytes: certBytes,
}
if i > 0 {
result = append(result, '\n')
}
result = append(result, pem.EncodeToMemory(block)...)
}
return result
}
// Count 证书数量
func (cc *CertificateChain) Count() int {
return len(cc.Certificates)
}
func main() {
fmt.Println("=== 证书链处理 ===\n")
// 1. 创建模拟证书数据
fmt.Println("1. 创建证书链:")
cert1 := []byte("root certificate data")
cert2 := []byte("intermediate certificate data")
cert3 := []byte("end-entity certificate data")
// 2. 构建证书链
chain := &CertificateChain{}
// 通常顺序:终端实体证书 -> 中间证书 -> 根证书
chain.AddCertificate(createPEM(cert3, "CERTIFICATE"))
chain.AddCertificate(createPEM(cert2, "CERTIFICATE"))
chain.AddCertificate(createPEM(cert1, "CERTIFICATE"))
fmt.Printf(" 证书数量:%d\n\n", chain.Count())
// 3. 编码为 PEM 格式
fmt.Println("2. 编码证书链:")
chainPEM := chain.EncodeToPEM()
fmt.Printf("%s\n", string(chainPEM))
// 4. 解析证书链
fmt.Println("3. 解析证书链:")
var certCount int
data := chainPEM
for len(data) > 0 {
block, rest := pem.Decode(data)
if block == nil {
break
}
certCount++
fmt.Printf(" 证书 %d:\n", certCount)
fmt.Printf(" 类型:%s\n", block.Type)
fmt.Printf(" 数据:%s\n", string(block.Bytes))
fmt.Printf(" 数据长度:%d 字节\n\n", len(block.Bytes))
data = rest
}
// 5. 保存到文件
fmt.Println("4. 保存到文件:")
err := os.WriteFile("chain.pem", chainPEM, 0644)
if err != nil {
log.Fatal(err)
}
fmt.Printf(" ✓ 已保存到 chain.pem\n")
// 6. 从文件加载
fmt.Println("\n5. 从文件加载:")
fileData, err := os.ReadFile("chain.pem")
if err != nil {
log.Fatal(err)
}
loadedChain := &CertificateChain{}
data = fileData
for len(data) > 0 {
block, rest := pem.Decode(data)
if block == nil {
break
}
if block.Type == "CERTIFICATE" {
loadedChain.AddCertificate(pem.EncodeToMemory(block))
}
data = rest
}
fmt.Printf(" ✓ 已加载 %d 个证书\n", loadedChain.Count())
// 清理
os.Remove("chain.pem")
}
// createPEM 创建 PEM 格式
func createPEM(data []byte, typ string) []byte {
block := &pem.Block{
Type: typ,
Bytes: data,
}
return pem.EncodeToMemory(block)
}
输出:
=== 证书链处理 ===
1. 创建证书链:
证书数量:3
2. 编码证书链:
-----BEGIN CERTIFICATE-----
ZW5kLWVudGl0eSBjZXJ0aWZpY2F0ZSBkYXRh
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
aW50ZXJtZWRpYXRlIGNlcnRpZmljYXRlIGRhdGE=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
cm9vdCBjZXJ0aWZpY2F0ZSBkYXRh
-----END CERTIFICATE-----
3. 解析证书链:
证书 1:
类型:CERTIFICATE
数据:end-entity certificate data
数据长度:28 字节
证书 2:
类型:CERTIFICATE
数据:intermediate certificate data
数据长度:32 字节
证书 3:
类型:CERTIFICATE
数据:root certificate data
数据长度:20 字节
4. 保存到文件:
✓ 已保存到 chain.pem
5. 从文件加载:
✓ 已加载 3 个证书
示例 5:私钥处理
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"log"
)
func main() {
fmt.Println("=== 私钥处理 ===\n")
// 1. RSA 私钥
fmt.Println("1. RSA 私钥:")
rsaKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
log.Fatal(err)
}
// PKCS#1 格式
rsaPKCS1 := &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(rsaKey),
}
fmt.Printf("PKCS#1 格式:\n%s\n", string(pem.EncodeToMemory(rsaPKCS1)))
// PKCS#8 格式
rsaPKCS8, err := x509.MarshalPKCS8PrivateKey(rsaKey)
if err != nil {
log.Fatal(err)
}
rsaPKCS8Block := &pem.Block{
Type: "PRIVATE KEY",
Bytes: rsaPKCS8,
}
fmt.Printf("PKCS#8 格式:\n%s\n", string(pem.EncodeToMemory(rsaPKCS8Block)))
// 2. ECDSA 私钥
fmt.Println("2. ECDSA 私钥:")
ecKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
log.Fatal(err)
}
// SEC1 格式
ecBytes, err := x509.MarshalECPrivateKey(ecKey)
if err != nil {
log.Fatal(err)
}
ecBlock := &pem.Block{
Type: "EC PRIVATE KEY",
Bytes: ecBytes,
}
fmt.Printf("SEC1 格式:\n%s\n", string(pem.EncodeToMemory(ecBlock)))
// PKCS#8 格式
ecPKCS8, err := x509.MarshalPKCS8PrivateKey(ecKey)
if err != nil {
log.Fatal(err)
}
ecPKCS8Block := &pem.Block{
Type: "PRIVATE KEY",
Bytes: ecPKCS8,
}
fmt.Printf("PKCS#8 格式:\n%s\n", string(pem.EncodeToMemory(ecPKCS8Block)))
// 3. 解码私钥
fmt.Println("3. 解码私钥:")
// 解码 PKCS#8 RSA 私钥
block, _ := pem.Decode(pem.EncodeToMemory(rsaPKCS8Block))
// 解析为通用接口
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
log.Fatal(err)
}
// 类型断言
switch k := key.(type) {
case *rsa.PrivateKey:
fmt.Printf(" 类型:RSA 私钥\n")
fmt.Printf(" 位数:%d\n", k.Size()*8)
case *ecdsa.PrivateKey:
fmt.Printf(" 类型:ECDSA 私钥\n")
fmt.Printf(" 曲线:%s\n", k.Curve.Params().Name)
default:
fmt.Printf(" 类型:未知 (%T)\n", key)
}
// 4. 比较格式
fmt.Println("\n4. 格式比较:")
fmt.Printf(" PKCS#1 (RSA): 仅支持 RSA,Type=\"RSA PRIVATE KEY\"\n")
fmt.Printf(" PKCS#8 (通用): 支持多种算法,Type=\"PRIVATE KEY\"\n")
fmt.Printf(" SEC1 (EC): 仅支持椭圆曲线,Type=\"EC PRIVATE KEY\"\n")
}
输出:
=== 私钥处理 ===
1. RSA 私钥:
PKCS#1 格式:
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA...(实际私钥数据)...
-----END RSA PRIVATE KEY-----
PKCS#8 格式:
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0B...(实际私钥数据)...
-----END PRIVATE KEY-----
2. ECDSA 私钥:
SEC1 格式:
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEIB...(实际私钥数据)...
-----END EC PRIVATE KEY-----
PKCS#8 格式:
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg...(实际私钥数据)...
-----END PRIVATE KEY-----
3. 解码私钥:
类型:RSA 私钥
位数:2048
4. 格式比较:
PKCS#1 (RSA): 仅支持 RSA,Type="RSA PRIVATE KEY"
PKCS#8 (通用): 支持多种算法,Type="PRIVATE KEY"
SEC1 (EC): 仅支持椭圆曲线,Type="EC PRIVATE KEY"
示例 6:公钥处理
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"log"
)
func main() {
fmt.Println("=== 公钥处理 ===\n")
// 1. 生成 RSA 密钥对
fmt.Println("1. 生成 RSA 密钥对:")
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
log.Fatal(err)
}
publicKey := &privateKey.PublicKey
fmt.Printf(" ✓ 已生成 %d 位密钥对\n\n", 2048)
// 2. 编码公钥(PKCS#1)
fmt.Println("2. 编码公钥 (PKCS#1):")
pubKeyPKCS1 := &pem.Block{
Type: "RSA PUBLIC KEY",
Bytes: x509.MarshalPKCS1PublicKey(publicKey),
}
pubKeyPEM := pem.EncodeToMemory(pubKeyPKCS1)
fmt.Printf("%s\n", string(pubKeyPEM))
// 3. 编码公钥(PKCS#8)
fmt.Println("3. 编码公钥 (PKIX):")
pubKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
log.Fatal(err)
}
pubKeyPKIX := &pem.Block{
Type: "PUBLIC KEY",
Bytes: pubKeyBytes,
}
pubKeyPKIXPEM := pem.EncodeToMemory(pubKeyPKIX)
fmt.Printf("%s\n", string(pubKeyPKIXPEM))
// 4. 解码公钥
fmt.Println("4. 解码公钥:")
block, _ := pem.Decode(pubKeyPKIXPEM)
parsedKey, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
log.Fatal(err)
}
rsaPubKey, ok := parsedKey.(*rsa.PublicKey)
if !ok {
log.Fatal("不是 RSA 公钥")
}
fmt.Printf(" 类型:RSA 公钥\n")
fmt.Printf(" 位数:%d\n", rsaPubKey.Size()*8)
fmt.Printf(" N: %s...\n", rsaPubKey.N.String()[:50])
fmt.Printf(" E: %d\n\n", rsaPubKey.E)
// 5. 比较格式
fmt.Println("5. 格式比较:")
fmt.Printf(" PKCS#1: Type=\"RSA PUBLIC KEY\", 仅 RSA\n")
fmt.Printf(" PKIX: Type=\"PUBLIC KEY\", 通用格式\n")
fmt.Printf(" PKCS#1 长度:%d 字节\n", len(pubKeyPEM))
fmt.Printf(" PKIX 长度:%d 字节\n", len(pubKeyPKIXPEM))
}
输出:
=== 公钥处理 ===
1. 生成 RSA 密钥对:
✓ 已生成 2048 位密钥对
2. 编码公钥 (PKCS#1):
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEA...(实际公钥数据)...
-----END RSA PUBLIC KEY-----
3. 编码公钥 (PKIX):
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...(实际公钥数据)...
-----END PUBLIC KEY-----
4. 解码公钥:
类型:RSA 公钥
位数:2048
N: 1234567890123456789012345678901234567890123456...
E: 65537
5. 格式比较:
PKCS#1: Type="RSA PUBLIC KEY", 仅 RSA
PKIX: Type="PUBLIC KEY", 通用格式
PKCS#1 长度:451 字节
PKIX 长度:451 字节
示例 7:文件操作
package main
import (
"encoding/pem"
"fmt"
"io/ioutil"
"log"
"os"
)
// PEMFile PEM 文件操作
type PEMFile struct {
Path string
}
// Write 写入 PEM 文件
func (pf *PEMFile) Write(block *pem.Block) error {
file, err := os.Create(pf.Path)
if err != nil {
return err
}
defer file.Close()
return pem.Encode(file, block)
}
// Read 读取 PEM 文件
func (pf *PEMFile) Read() (*pem.Block, error) {
data, err := ioutil.ReadFile(pf.Path)
if err != nil {
return nil, err
}
block, _ := pem.Decode(data)
if block == nil {
return nil, fmt.Errorf("无效的 PEM 文件")
}
return block, nil
}
// ReadAll 读取所有 PEM Block
func (pf *PEMFile) ReadAll() ([]*pem.Block, error) {
data, err := ioutil.ReadFile(pf.Path)
if err != nil {
return nil, err
}
var blocks []*pem.Block
for len(data) > 0 {
block, rest := pem.Decode(data)
if block == nil {
break
}
blocks = append(blocks, block)
data = rest
}
return blocks, nil
}
// Exists 检查文件是否存在
func (pf *PEMFile) Exists() bool {
_, err := os.Stat(pf.Path)
return err == nil
}
// Delete 删除文件
func (pf *PEMFile) Delete() error {
return os.Remove(pf.Path)
}
func main() {
fmt.Println("=== PEM 文件操作 ===\n")
// 1. 创建测试数据
testData := []byte("Test data for PEM file operations.")
block := &pem.Block{
Type: "TEST DATA",
Bytes: testData,
}
// 2. 写入文件
fmt.Println("1. 写入 PEM 文件:")
pemFile := &PEMFile{Path: "test.pem"}
err := pemFile.Write(block)
if err != nil {
log.Fatal(err)
}
fmt.Printf(" ✓ 已写入 %s\n\n", pemFile.Path)
// 3. 读取文件
fmt.Println("2. 读取 PEM 文件:")
readBlock, err := pemFile.Read()
if err != nil {
log.Fatal(err)
}
fmt.Printf(" 类型:%s\n", readBlock.Type)
fmt.Printf(" 数据:%s\n", string(readBlock.Bytes))
fmt.Printf(" 验证:%v\n\n", string(testData) == string(readBlock.Bytes))
// 4. 检查文件存在
fmt.Println("3. 文件操作:")
fmt.Printf(" 文件存在:%v\n", pemFile.Exists())
// 5. 读取原始内容
fmt.Println("\n4. 文件内容:")
content, _ := ioutil.ReadFile(pemFile.Path)
fmt.Printf("%s", string(content))
// 6. 多 Block 文件
fmt.Println("\n5. 多 Block 文件:")
multiBlock := &PEMFile{Path: "multi.pem"}
file, _ := os.Create(multiBlock.Path)
// 写入多个 Block
pem.Encode(file, &pem.Block{Type: "DATA 1", Bytes: []byte("First block")})
file.Write([]byte("\n"))
pem.Encode(file, &pem.Block{Type: "DATA 2", Bytes: []byte("Second block")})
file.Write([]byte("\n"))
pem.Encode(file, &pem.Block{Type: "DATA 3", Bytes: []byte("Third block")})
file.Close()
fmt.Printf(" ✓ 已写入 %s\n", multiBlock.Path)
// 读取所有 Block
blocks, _ := multiBlock.ReadAll()
fmt.Printf(" 读取 Block 数:%d\n", len(blocks))
for i, b := range blocks {
fmt.Printf(" Block %d: %s - %s\n", i+1, b.Type, string(b.Bytes))
}
// 7. 清理
fmt.Println("\n6. 清理:")
pemFile.Delete()
multiBlock.Delete()
fmt.Printf(" ✓ 文件已删除\n")
}
输出:
=== PEM 文件操作 ===
1. 写入 PEM 文件:
✓ 已写入 test.pem
2. 读取 PEM 文件:
类型:TEST DATA
数据:Test data for PEM file operations.
验证:true
3. 文件操作:
文件存在:true
4. 文件内容:
-----BEGIN TEST DATA-----
VGVzdCBkYXRhIGZvciBQRU0gZmlsZSBvcGVyYXRpb25zLg==
-----END TEST DATA-----
5. 多 Block 文件:
✓ 已写入 multi.pem
读取 Block 数:3
Block 1: DATA 1 - First block
Block 2: DATA 2 - Second block
Block 3: DATA 3 - Third block
6. 清理:
✓ 文件已删除
示例 8:错误处理
package main
import (
"encoding/pem"
"fmt"
)
func main() {
fmt.Println("=== PEM 错误处理 ===\n")
// 1. 无效的 PEM 数据
fmt.Println("1. 无效的 PEM 数据:")
invalidCases := []struct {
data []byte
desc string
}{
{[]byte("Not PEM data"), "普通文本"},
{[]byte("-----BEGIN-----\ndata\n-----END-----"), "缺少类型"},
{[]byte("-----BEGIN CERTIFICATE-----"), "只有 BEGIN 标记"},
{[]byte("-----END CERTIFICATE-----"), "只有 END 标记"},
{[]byte(""), "空数据"},
{[]byte("-----BEGIN CERTIFICATE-----\ninvalid!!!\n-----END CERTIFICATE-----"), "无效 Base64"},
}
for _, tc := range invalidCases {
block, rest := pem.Decode(tc.data)
fmt.Printf(" %s:\n", tc.desc)
if block == nil {
fmt.Printf(" ✗ 解码失败 (block=nil)\n")
} else {
fmt.Printf(" ? 部分成功:类型=%s\n", block.Type)
}
fmt.Printf(" 剩余:%d 字节\n\n", len(rest))
}
// 2. 类型不匹配
fmt.Println("2. 类型不匹配:")
certPEM := []byte(`-----BEGIN CERTIFICATE-----
Y2VydGlmaWNhdGUgZGF0YQ==
-----END CERTIFICATE-----`)
block, _ := pem.Decode(certPEM)
fmt.Printf(" 证书 PEM:\n")
fmt.Printf(" 类型:%s\n", block.Type)
fmt.Printf(" 期望:CERTIFICATE\n")
fmt.Printf(" 匹配:%v\n\n", block.Type == "CERTIFICATE")
// 3. 空数据
fmt.Println("3. 空数据处理:")
emptyData := []byte{}
block, rest := pem.Decode(emptyData)
fmt.Printf(" 空数据:%v (block=%v, rest=%d 字节)\n\n",
block == nil, block == nil, len(rest))
// 4. 部分 PEM
fmt.Println("4. 部分 PEM:")
partialPEM := []byte(`-----BEGIN CERTIFICATE-----
Y2VydGlmaWNhdGUgZGF0YQ==`)
block, rest = pem.Decode(partialPEM)
fmt.Printf(" 缺少 END 标记:\n")
fmt.Printf(" block: %v\n", block != nil)
fmt.Printf(" rest: %d 字节\n\n", len(rest))
// 5. 多个 BEGIN 标记
fmt.Println("5. 多个 BEGIN 标记:")
multiBegin := []byte(`-----BEGIN CERTIFICATE-----
Y2VydDE=
-----BEGIN CERTIFICATE-----
Y2VydDI=
-----END CERTIFICATE-----`)
block, rest = pem.Decode(multiBegin)
fmt.Printf(" 第一个 Block:\n")
if block != nil {
fmt.Printf(" 类型:%s\n", block.Type)
}
fmt.Printf(" 剩余:%d 字节\n", len(rest))
// 继续解析
if len(rest) > 0 {
block2, _ := pem.Decode(rest)
if block2 != nil {
fmt.Printf(" 第二个 Block:\n")
fmt.Printf(" 类型:%s\n", block2.Type)
}
}
fmt.Println()
// 6. 正确的 PEM
fmt.Println("6. 正确的 PEM:")
validPEM := []byte(`-----BEGIN TEST DATA-----
dGVzdCBkYXRh
-----END TEST DATA-----`)
block, rest = pem.Decode(validPEM)
if block != nil {
fmt.Printf(" ✓ 解码成功\n")
fmt.Printf(" 类型:%s\n", block.Type)
fmt.Printf(" 数据:%s\n", string(block.Bytes))
fmt.Printf(" 剩余:%d 字节\n", len(rest))
}
}
输出:
=== PEM 错误处理 ===
1. 无效的 PEM 数据:
普通文本:
✗ 解码失败 (block=nil)
剩余:0 字节
缺少类型:
✗ 解码失败 (block=nil)
剩余:0 字节
只有 BEGIN 标记:
✗ 解码失败 (block=nil)
剩余:0 字节
只有 END 标记:
✗ 解码失败 (block=nil)
剩余:0 字节
空数据:
✗ 解码失败 (block=nil)
剩余:0 字节
无效 Base64:
? 部分成功:类型=CERTIFICATE
剩余:0 字节
2. 类型不匹配:
证书 PEM:
类型:CERTIFICATE
期望:CERTIFICATE
匹配:true
3. 空数据处理:
空数据:true (block=nil, rest=0 字节)
4. 部分 PEM:
缺少 END 标记:
block: false
rest: 0 字节
5. 多个 BEGIN 标记:
第一个 Block:
类型:CERTIFICATE
剩余:40 字节
第二个 Block:
类型:CERTIFICATE
6. 正确的 PEM:
✓ 解码成功
类型:TEST DATA
数据:test data
剩余:0 字节
最佳实践
✅ 推荐做法
-
总是检查解码结果
// ✅ 推荐 block, _ := pem.Decode(data) if block == nil { return fmt.Errorf("无效的 PEM 数据") } // ❌ 不推荐 block, _ := pem.Decode(data) // 直接使用 block -
使用正确的类型标记
// ✅ 推荐 &pem.Block{Type: "CERTIFICATE", Bytes: certBytes} &pem.Block{Type: "PRIVATE KEY", Bytes: keyBytes} // ❌ 不推荐 &pem.Block{Type: "KEY", Bytes: keyBytes} // 不标准 -
处理多个 Block
// ✅ 推荐:处理证书链 for len(data) > 0 { block, rest := pem.Decode(data) if block == nil { break } // 处理 block data = rest } -
使用 PKCS#8 格式
// ✅ 推荐:通用格式 &pem.Block{Type: "PRIVATE KEY", Bytes: pkcs8Bytes} // ❌ 不推荐:特定算法格式 &pem.Block{Type: "RSA PRIVATE KEY", Bytes: pkcs1Bytes} -
文件操作使用 pem.Encode
// ✅ 推荐 file, _ := os.Create("cert.pem") defer file.Close() pem.Encode(file, block) // ❌ 不推荐 ioutil.WriteFile("cert.pem", pem.EncodeToMemory(block), 0644)
❌ 不安全做法
-
不要忽略错误
// ❌ 错误 block, _ := pem.Decode(data) // ✅ 正确 block, _ := pem.Decode(data) if block == nil { return error } -
不要信任 PEM 类型
// ❌ 错误 block, _ := pem.Decode(data) // 假设是证书 x509.ParseCertificate(block.Bytes) // ✅ 正确 block, _ := pem.Decode(data) if block.Type != "CERTIFICATE" { return error } -
不要混用格式
// ❌ 错误 // 混用 PKCS#1 和 PKCS#8 // ✅ 正确 // 统一使用 PKCS#8
性能优化
1. 批量编码使用 EncodeToMemory
// ✅ 推荐:小数据
pemData := pem.EncodeToMemory(block)
// ❌ 不推荐:创建临时缓冲区
var buf bytes.Buffer
pem.Encode(&buf, block)
2. 流式处理大文件
// ✅ 推荐:大文件
file, _ := os.Create("output.pem")
pem.Encode(file, block)
file.Close()
总结
核心类型
| 类型 | 用途 | 说明 |
|---|---|---|
| Block | PEM 数据块 | 包含 Type、Headers、Bytes |
核心函数
| 函数 | 用途 | 返回值 |
|---|---|---|
| Encode | 编码到 Writer | error |
| EncodeToMemory | 编码到内存 | []byte |
| Decode | 解码 PEM | *Block, []byte |
常见 PEM 类型
| 类型 | 用途 |
|---|---|
| CERTIFICATE | X.509 证书 |
| PRIVATE KEY | PKCS#8 私钥 |
| RSA PRIVATE KEY | RSA 私钥 |
| EC PRIVATE KEY | EC 私钥 |
| PUBLIC KEY | 公钥 |
使用场景
| 场景 | 方法 | 说明 |
|---|---|---|
| 证书存储 | Encode/Decode | X.509 证书 |
| 密钥存储 | EncodeToMemory | 私钥/公钥 |
| 证书链 | 循环 Decode | 多个 Block |
| 文件操作 | pem.Encode | 直接写入文件 |
参考资料
最后更新:2026-04-03
Go 版本:Go 1.23+