Golang与OpenSSL集成使用指南

Golang与OpenSSL集成使用指南 大家好,很高兴看到你们这里也有一个 Discourse 论坛 🙂

我对 Golang 非常非常陌生,目前更多时间是在阅读文档,而不是编程,但我想这对于刚开始来说是正常的。

我有两个主题,想向社区寻求帮助。

OpenSSL。

我开始阅读文档,但不知怎的,我找不到描述 Golang 中哪些方法等同于 shell 命令的部分。我真的认为这能帮助人们过渡。

所以,我要做的是将证书(cer 或 pem)和密钥(key 或 pem)转换为 PKCS12 (.pfx) 文件。

我的 shell 命令是:

如果证书是 PEM 格式:openssl x509 -in inout_cert.pem -out output_cert.pem 如果证书是 DER 格式:openssl x509 -inform der -in input_cert.cer -out output_cert.pem

如果密钥是 PEM 格式:openssl pkcs8 -in input_key.pem -passin pass:PASS -out output_key.pem 如果密钥是 DER 格式:openssl pkcs8 -inform der -in input_key.key -passin pass:PASS -out output_key.pem

最后:

openssl pkcs12 -export -in output_cert.pem -inkey output_key.pem -out server.pfx -password pass:PASS

这将生成所需的 server.pfx 文件,其密码与密钥相同(如果密钥有密码的话)。

目前我在 Golang 中通过包装器来实现,实际上是作为 shell 命令执行并监听 stdout 和 stderr 来捕获所有输出。这并不慢(30毫秒)… 但由于多种原因(下面会提到),我不想这样做,而是希望直接用 Golang 的 openssl 库来完成。 我目前还在服务器上临时存储文件,如果可能的话,我也不想这样做。

所以我的问题是:

  1. 如何用 Golang 的 openssl 分析证书?是否可以简单地传入某个东西,然后它告诉我“这是一个 PEM 格式的‘证书’”或“这是一个 DER 格式的‘密钥’”?
  2. 我能否(从我的表单上传中)获取文件,不将它们存储为文件,而是将它们保存在内存中或作为 []byte 变量,然后像这样转换它们,这样我就不必存储它们了?
  3. 如果我使用 Golang 的 openssl,我的系统是否需要安装 openssl,还是不再需要了?

第二部分如下:

file

我使用 shell 程序 file 来识别文件,而不依赖于它们的文件扩展名等。如果 golang-openssl 能够识别所有证书和密钥,我就不需要它了,但目前我不知道它是否能做到。 如果 golang-openssl 不能做到这一点,是否有 Golang 库可以做到?对于我的需求,它必须能够区分:

  • 证书 (der)
  • 证书 (pem)
  • 密钥 (der)
  • 密钥 (pem)
  • 密钥 (pem - 加密)

这对我来说就足够了。

目前我有这两个外部库,我想摆脱它们,这样我的应用程序就是 100% 基于 Go 的,而不是一些依赖于子 shell 的“弗兰肯斯坦”项目。

我的长期目标

… 对于 Golang 来说,是能够 100% 在 Golang 中运行我的代码,而不需要其他外部依赖。 我想这样做有多个原因:

  • 性能
  • 安全性
  • 完整性
  • 能够在 docker-scratch 中运行

我能够将我的整个应用程序作为 <10MB 的 docker 镜像运行,但我不得不禁用所有需要“openssl”和“file”的部分。这意味着对我来说,应用程序并不是真正可用的,但我对使用 Golang 并构建应用程序时,docker 镜像可以变得如此之小感到非常印象深刻。我真的很喜欢这一点,并且想更多地了解 Golang 以及基于 Golang 的应用程序的 Docker 化 🙂

目前就这些。我希望这里的一些友好的朋友能给我一些建议,或者更好的例子,以及他们会怎么做。

顺便说一下,以下是我使用的:

  • golang v1.19.1
  • gin v1.8.1

所以对我来说,这是一个基于 gin 的 Web 服务器。

此致。


更多关于Golang与OpenSSL集成使用指南的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang与OpenSSL集成使用指南的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go中处理OpenSSL操作,标准库crypto/x509crypto/pkcs12可以完全替代你的shell命令。以下是具体实现:

1. 证书和密钥解析

package main

import (
    "crypto/x509"
    "encoding/pem"
    "errors"
    "io"
    "os"
)

// 自动检测并解析证书(支持PEM和DER格式)
func ParseCertificate(data []byte) (*x509.Certificate, error) {
    // 尝试解析为PEM格式
    block, _ := pem.Decode(data)
    if block != nil && block.Type == "CERTIFICATE" {
        return x509.ParseCertificate(block.Bytes)
    }
    
    // 如果不是PEM,尝试解析为DER格式
    return x509.ParseCertificate(data)
}

// 自动检测并解析私钥(支持PEM和DER格式)
func ParsePrivateKey(data []byte, password []byte) (interface{}, error) {
    // 尝试解析为PEM格式
    block, _ := pem.Decode(data)
    if block != nil {
        switch block.Type {
        case "RSA PRIVATE KEY":
            return x509.ParsePKCS1PrivateKey(block.Bytes)
        case "EC PRIVATE KEY":
            return x509.ParseECPrivateKey(block.Bytes)
        case "PRIVATE KEY":
            return x509.ParsePKCS8PrivateKey(block.Bytes)
        case "ENCRYPTED PRIVATE KEY":
            return x509.DecryptPEMBlock(block, password)
        }
    }
    
    // 如果不是PEM,尝试解析为DER格式
    if key, err := x509.ParsePKCS8PrivateKey(data); err == nil {
        return key, nil
    }
    if key, err := x509.ParsePKCS1PrivateKey(data); err == nil {
        return key, nil
    }
    if key, err := x509.ParseECPrivateKey(data); err == nil {
        return key, nil
    }
    
    return nil, errors.New("无法解析私钥")
}

2. 内存中转换证书格式

import (
    "crypto/rsa"
    "crypto/x509"
    "encoding/pem"
)

// 证书DER转PEM(内存中转换)
func ConvertCertDERtoPEM(derData []byte) []byte {
    block := &pem.Block{
        Type:  "CERTIFICATE",
        Bytes: derData,
    }
    return pem.EncodeToMemory(block)
}

// 证书PEM转DER(内存中转换)
func ConvertCertPEMtoDER(pemData []byte) ([]byte, error) {
    block, _ := pem.Decode(pemData)
    if block == nil || block.Type != "CERTIFICATE" {
        return nil, errors.New("无效的PEM证书")
    }
    return block.Bytes, nil
}

3. 生成PKCS12文件(完全在内存中)

import (
    "crypto/x509"
    "encoding/pem"
    "software.sslmate.com/src/go-pkcs12"
)

func CreatePKCS12InMemory(certPEM, keyPEM []byte, password string) ([]byte, error) {
    // 解析证书
    certBlock, _ := pem.Decode(certPEM)
    if certBlock == nil {
        return nil, errors.New("无法解析证书PEM")
    }
    cert, err := x509.ParseCertificate(certBlock.Bytes)
    if err != nil {
        return nil, err
    }
    
    // 解析私钥
    keyBlock, _ := pem.Decode(keyPEM)
    if keyBlock == nil {
        return nil, errors.New("无法解析私钥PEM")
    }
    
    var privateKey interface{}
    switch keyBlock.Type {
    case "RSA PRIVATE KEY":
        privateKey, err = x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
    case "EC PRIVATE KEY":
        privateKey, err = x509.ParseECPrivateKey(keyBlock.Bytes)
    case "PRIVATE KEY":
        privateKey, err = x509.ParsePKCS8PrivateKey(keyBlock.Bytes)
    default:
        return nil, errors.New("不支持的私钥格式")
    }
    
    if err != nil {
        return nil, err
    }
    
    // 创建PKCS12数据
    pfxData, err := pkcs12.Encode(privateKey, cert, nil, password)
    if err != nil {
        return nil, err
    }
    
    return pfxData, nil
}

4. 完整示例:处理上传文件并生成PFX

import (
    "bytes"
    "crypto/x509"
    "encoding/pem"
    "github.com/gin-gonic/gin"
    "net/http"
    "software.sslmate.com/src/go-pkcs12"
)

func ConvertToPKCS12Handler(c *gin.Context) {
    // 从表单获取文件
    certFile, certHeader, err := c.Request.FormFile("certificate")
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "证书文件缺失"})
        return
    }
    defer certFile.Close()
    
    keyFile, keyHeader, err := c.Request.FormFile("private_key")
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "私钥文件缺失"})
        return
    }
    defer keyFile.Close()
    
    password := c.PostForm("password")
    
    // 读取文件到内存
    certBuf := new(bytes.Buffer)
    keyBuf := new(bytes.Buffer)
    
    if _, err := certBuf.ReadFrom(certFile); err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "读取证书失败"})
        return
    }
    
    if _, err := keyBuf.ReadFrom(keyFile); err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "读取私钥失败"})
        return
    }
    
    // 自动检测并解析
    certData := certBuf.Bytes()
    keyData := keyBuf.Bytes()
    
    // 解析证书
    cert, err := ParseCertificate(certData)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "证书解析失败: " + err.Error()})
        return
    }
    
    // 解析私钥
    privateKey, err := ParsePrivateKey(keyData, []byte(password))
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "私钥解析失败: " + err.Error()})
        return
    }
    
    // 生成PKCS12
    pfxData, err := pkcs12.Encode(privateKey, cert, nil, password)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "PKCS12生成失败: " + err.Error()})
        return
    }
    
    // 返回PFX文件
    c.Header("Content-Disposition", "attachment; filename=server.pfx")
    c.Data(http.StatusOK, "application/x-pkcs12", pfxData)
}

5. 文件类型检测

import (
    "bytes"
    "crypto/x509"
    "encoding/pem"
    "strings"
)

type FileType string

const (
    CertPEM     FileType = "CERTIFICATE_PEM"
    CertDER     FileType = "CERTIFICATE_DER"
    KeyPEM      FileType = "PRIVATE_KEY_PEM"
    KeyDER      FileType = "PRIVATE_KEY_DER"
    KeyEncPEM   FileType = "ENCRYPTED_PRIVATE_KEY_PEM"
    Unknown     FileType = "UNKNOWN"
)

func DetectFileType(data []byte) (FileType, error) {
    // 尝试解析为PEM
    block, _ := pem.Decode(data)
    if block != nil {
        switch block.Type {
        case "CERTIFICATE":
            return CertPEM, nil
        case "RSA PRIVATE KEY", "EC PRIVATE KEY", "PRIVATE KEY":
            return KeyPEM, nil
        case "ENCRYPTED PRIVATE KEY":
            return KeyEncPEM, nil
        }
    }
    
    // 尝试解析为DER证书
    if _, err := x509.ParseCertificate(data); err == nil {
        return CertDER, nil
    }
    
    // 尝试解析为DER私钥
    if _, err := x509.ParsePKCS8PrivateKey(data); err == nil {
        return KeyDER, nil
    }
    if _, err := x509.ParsePKCS1PrivateKey(data); err == nil {
        return KeyDER, nil
    }
    if _, err := x509.ParseECPrivateKey(data); err == nil {
        return KeyDER, nil
    }
    
    return Unknown, errors.New("无法识别文件类型")
}

依赖管理

go.mod中添加:

require software.sslmate.com/src/go-pkcs12 v0.2.0

这个方案完全消除了对系统OpenSSL的依赖,所有操作都在内存中进行,生成的Docker镜像可以保持<10MB。go-pkcs12库是纯Go实现,支持PKCS#12格式的编码和解码。

回到顶部