Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

组成部分

  1. BEGIN 标记-----BEGIN <TYPE>-----
  2. Base64 数据:每行 64 个字符(可选)
  3. END 标记-----END <TYPE>-----

常见的 PEM 类型

类型说明用途
CERTIFICATEX.509 证书SSL/TLS 证书
CERTIFICATE REQUESTCSR证书签名请求
PRIVATE KEYPKCS#8 私钥通用私钥格式
RSA PRIVATE KEYRSA 私钥RSA 算法私钥
RSA PUBLIC KEYRSA 公钥RSA 算法公钥
EC PRIVATE KEYEC 私钥椭圆曲线私钥
PUBLIC KEY公钥通用公钥格式
ENCRYPTED PRIVATE KEY加密私钥密码保护的私钥
OPENSSH PRIVATE KEYOpenSSH 私钥SSH 密钥
PGPPGP 密钥PGP 加密密钥

PEM vs Base64

特性PEMBase64
标记有 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 字节

最佳实践

✅ 推荐做法

  1. 总是检查解码结果

    // ✅ 推荐
    block, _ := pem.Decode(data)
    if block == nil {
        return fmt.Errorf("无效的 PEM 数据")
    }
    
    // ❌ 不推荐
    block, _ := pem.Decode(data)
    // 直接使用 block
    
  2. 使用正确的类型标记

    // ✅ 推荐
    &pem.Block{Type: "CERTIFICATE", Bytes: certBytes}
    &pem.Block{Type: "PRIVATE KEY", Bytes: keyBytes}
    
    // ❌ 不推荐
    &pem.Block{Type: "KEY", Bytes: keyBytes}  // 不标准
    
  3. 处理多个 Block

    // ✅ 推荐:处理证书链
    for len(data) > 0 {
        block, rest := pem.Decode(data)
        if block == nil {
            break
        }
        // 处理 block
        data = rest
    }
    
  4. 使用 PKCS#8 格式

    // ✅ 推荐:通用格式
    &pem.Block{Type: "PRIVATE KEY", Bytes: pkcs8Bytes}
    
    // ❌ 不推荐:特定算法格式
    &pem.Block{Type: "RSA PRIVATE KEY", Bytes: pkcs1Bytes}
    
  5. 文件操作使用 pem.Encode

    // ✅ 推荐
    file, _ := os.Create("cert.pem")
    defer file.Close()
    pem.Encode(file, block)
    
    // ❌ 不推荐
    ioutil.WriteFile("cert.pem", pem.EncodeToMemory(block), 0644)
    

❌ 不安全做法

  1. 不要忽略错误

    // ❌ 错误
    block, _ := pem.Decode(data)
    
    // ✅ 正确
    block, _ := pem.Decode(data)
    if block == nil {
        return error
    }
    
  2. 不要信任 PEM 类型

    // ❌ 错误
    block, _ := pem.Decode(data)
    // 假设是证书
    x509.ParseCertificate(block.Bytes)
    
    // ✅ 正确
    block, _ := pem.Decode(data)
    if block.Type != "CERTIFICATE" {
        return error
    }
    
  3. 不要混用格式

    // ❌ 错误
    // 混用 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()

总结

核心类型

类型用途说明
BlockPEM 数据块包含 Type、Headers、Bytes

核心函数

函数用途返回值
Encode编码到 Writererror
EncodeToMemory编码到内存[]byte
Decode解码 PEM*Block, []byte

常见 PEM 类型

类型用途
CERTIFICATEX.509 证书
PRIVATE KEYPKCS#8 私钥
RSA PRIVATE KEYRSA 私钥
EC PRIVATE KEYEC 私钥
PUBLIC KEY公钥

使用场景

场景方法说明
证书存储Encode/DecodeX.509 证书
密钥存储EncodeToMemory私钥/公钥
证书链循环 Decode多个 Block
文件操作pem.Encode直接写入文件

参考资料


最后更新:2026-04-03
Go 版本:Go 1.23+