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 库来完成。 我目前还在服务器上临时存储文件,如果可能的话,我也不想这样做。
所以我的问题是:
- 如何用 Golang 的 openssl 分析证书?是否可以简单地传入某个东西,然后它告诉我“这是一个 PEM 格式的‘证书’”或“这是一个 DER 格式的‘密钥’”?
- 我能否(从我的表单上传中)获取文件,不将它们存储为文件,而是将它们保存在内存中或作为 []byte 变量,然后像这样转换它们,这样我就不必存储它们了?
- 如果我使用 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
更多关于Golang与OpenSSL集成使用指南的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
在Go中处理OpenSSL操作,标准库crypto/x509和crypto/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格式的编码和解码。

