golang开发及数字证书研究分享
本篇文章向大家介绍《golang开发及数字证书研究分享》,主要包括数字证书,具有一定的参考价值,需要的朋友可以参考一下。
在go语言提供的系统包中包含了大量和数字证书有关的方法。在这些方法中就有私钥生成的方法、私钥解析的方法、证书请求生成的方法、证书生成的方法等等。通过这些方法应该能够实现和openssl命令类似的功能。
仿照openssl生成证书的流程(从私钥的生成—>证书请求的生成—>证书的生成)用go语言进行模拟。
私钥的生成
在go的x509包下有go定义的证书的结构,该结构如下:
Raw []byte // Complete ASN.1 DER content (certificate, signature algorithm and signature).
RawTBSCertificate []byte // Certificate part of raw ASN.1 DER content.
RawSubjectPublicKeyInfo []byte // DER encoded SubjectPublicKeyInfo.
RawSubject []byte // DER encoded Subject
RawIssuer []byte // DER encoded Issuer
Signature []byte
SignatureAlgorithm SignatureAlgorithm
PublicKeyAlgorithm PublicKeyAlgorithm
PublicKey interface{}
Version int
SerialNumber *big.Int
Issuer pkix.Name
Subject pkix.Name
NotBefore, NotAfter time.Time // Validity bounds.
KeyUsage KeyUsage
Extensions []pkix.Extension
ExtraExtensions []pkix.Extension
UnhandledCriticalExtensions []asn1.ObjectIdentifier
ExtKeyUsage []ExtKeyUsage // Sequence of extended key usages.
UnknownExtKeyUsage []asn1.ObjectIdentifier // Encountered extended key usages unknown to this package.
BasicConstraintsValid bool // if true then the next two fields are valid.
IsCA bool
MaxPathLen int
MaxPathLenZero bool
SubjectKeyId []byte
AuthorityKeyId []byte
OCSPServer []string
IssuingCertificateURL []string
// Subject Alternate Name values
DNSNames []string
EmailAddresses []string
IPAddresses []net.IP
PermittedDNSDomainsCritical bool // if true then the name constraints are marked critical.
PermittedDNSDomains []string
CRLDistributionPoints []string
PolicyIdentifiers []asn1.ObjectIdentifier
在该结构中有PublicKeyAlgorithm字段,该字段用来表示生成公钥的算法。该字段的变量中可使用的字段如下:
const (
UnknownPublicKeyAlgorithm PublicKeyAlgorithm = iota
RSA
DSA
ECDSA
)
一共定义了4中情况。除去Unknown的情况。剩下的三种的实现分别在crypto/rsa、crypto/dsa、crypto/ecdsa这三个包中定义了实现。
RSA
使用RSA方法生成公私钥的方式非常简单。在crypto/rsa包中直接提供了生成方法。
func GenerateKey(random io.Reader, bits int) (*PrivateKey, error)
该方法生成一个rsa的私钥。查找整个包所提供的方法并没有什么方法能够生成公钥。但在包中有公钥的结构说明。查看私钥的结构:
type PrivateKey struct {
PublicKey // public part.
D *big.Int // private exponent
Primes []*big.Int // prime factors of N, has >= 2 elements.
Precomputed PrecomputedValues
}
赫然发现,公钥包含在私钥的结构中。换句话说,只要生成的私钥,公钥就同时拥有了(ECDSA和DSA的公钥也是如此)。
ECDSA
使用ECDSA生成公私钥的方式和RSA的方式非常类似:
func GenerateKey(c elliptic.Curve, rand io.Reader) (*PrivateKey, error)
在crypto/elliptic为参数c提供了4中实现方式。分别为:
func P224() Curve func P256() Curve func P384() Curve func P521() Curve
DSA
使用DSA生成公私钥的方式和上面两种有些不同:
func GenerateKey(priv *PrivateKey, rand io.Reader) error
私钥并不是作为结果返回,而是作为参数传入。那很简单,我直接初始化一个DSA的私钥,然后把该私钥作为参数传入不就可以了嘛。事实是,仅仅是实例化了一个DSA的私钥是无法完成公私钥的生成的。生成的结果如下:
priv:&{PublicKey:{Parameters:{P:<nil> Q:<nil> G:<nil>} Y:<nil>} X:<nil>}</nil></nil></nil></nil></nil>
可以发现公钥中的所有内容都是为nil(空),由此可以说明无法只通过GenerateKey()方法生成DSA的私钥。
在crypto/dsa包中还提供了:
func GenerateParameters(params *Parameters, rand io.Reader, sizes ParameterSizes) error
通过该方法的描述,可以了解到该方法是为DSA设置参数。那又如何和公私钥有关呢?,在DSA的私钥结构中包含公钥,在公钥的结构中就包含该方法所需要传入的参数Parameters。由此,我便想到可以先使用该方法对一些参数进行初始化,然后再生成私钥。
priv := &dsa.PrivateKey{}
dsa.GenerateParameters(&priv.Parameters, rand.Reader, dsa.L1024N160)
dsa.GenerateKey(priv, rand.Reader)
生成的私钥内容如下:
priv:&{PublicKey:{Parameters:{P:+91268520972047344779510472614939006285152176630742165979533208518526258287540244526987668731096217967904150874969731516661412604963023247030101570715552650277776208098462838867711078025572452557692674802977527475661989210578136725258241385474445330497234586673407237238372329018550727884900161895964574509801 Q:+767580094855879488293276223470508701563202760721 G:+42393651221310072390273970570719382707264443685255379637082820177806079494092036767507554061381644533127114802103872901363724639317297276457243780033980909021336576570837756106975221868617534717069925676009421223798208864916837561389117514471387385853288499961716794226875046226553216578582138687489881455573} Y:+68767508229940365112562020548287141674708444377336699267991474890690034611201698420418573204906537903040876819582645033160073997940957577512216430788561800033703926395782022182868300960590402743043934344374390498368316144177816214923367214895567903510165216432049170686626889267028482641530556275670781873053} X:+628682865942164859869306394087148223993136336500}
注意:Golang 对DSA证书没有完整的支持。
给私钥上锁(加访问密码)
在使用openssl进行私钥生成的时候,openssl需要我提供私钥的访问密码。那使用go进行私钥时,应该也有该功能。那应该在什么时候添加这个密码呢?是在生成私钥的时候,还是在生成pem文件的时候。我首先想到的是在生成秘密的时候,但是在crypto/rsa、crypto/dsa、crypto/ecdsa这三个包中查找时并没有发现任何和密码有关的词眼。那就应该在生成pem文件的时候加上密码。生成pem文件的方法在encoding/pem这个包中。但该包中只有两个编码,一个解码的方法,和密码有没有任何关系,唯一的存在的关系就是Block结构中的Header字段。
使用openssl生成的私钥文件中会存在这样的字段:
Proc-Type: 4,ENCRYPTED DEK-Info: DES-EDE3-CBC,02a0ba59e8cfd431
使用该字段来说明使用加密方式和提供用于解密的初始值向量。
在生成私钥和生成文件都无法把密码添加进去。那我就在想是否是在得到私钥的时候对私钥的byte数组进行加密。但这样就需要自己实现了。讲道理的话,go应该会为这种普遍性的东西提供已经封装好的方法。来回重新看api文档。发现自己漏看一个非常重要的包crypto/x509。在该包提供的方法中。很轻松的就找到了如下两个方法:
func DecryptPEMBlock(b *pem.Block, password []byte) ([]byte, error) func EncryptPEMBlock(rand io.Reader, blockType string, data, password []byte, alg PEMCipher) (*pem.Block, error)
在这两个方法中又要pem,password,恩应该就是这两个方法了,正好一个生成一个解析。
同在x509包下提供了:
func MarshalPKCS1PrivateKey(key *rsa.PrivateKey) []byte func MarshalECPrivateKey(key *ecdsa.PrivateKey) ([]byte, error)
把RSA和ECDSA私钥转换成byte数组的方法,但是没有找到把DSA私钥转换成byte数组的方法。
生成证书请求
证书请求生成很简单在crypto/x509中直接提供了现成的方法。
func CreateCertificateRequest(rand io.Reader, template *CertificateRequest, priv interface{}) (csr []byte, err error)
但使用用该方法有一个限制条件:
All keys types that are implemented via crypto.Signer are supported (This includes *rsa.PublicKey and *ecdsa.PublicKey.)
无法使用*dsa.PublicKey类型的公钥。而传入的参数是一个私钥,因此无法使用dsa类型的私钥。
go对dsa类型的证书
该方法需要通过一个证书请求的模板,在go中CertificateRequest是如下定义的:
Raw []byte // Complete ASN.1 DER content (CSR, signature algorithm and signature).
RawTBSCertificateRequest []byte // Certificate request info part of raw ASN.1 DER content.
RawSubjectPublicKeyInfo []byte // DER encoded SubjectPublicKeyInfo.
RawSubject []byte // DER encoded Subject.
Version int
Signature []byte
SignatureAlgorithm SignatureAlgorithm
PublicKeyAlgorithm PublicKeyAlgorithm
PublicKey interface{}
Subject pkix.Name
Attributes []pkix.AttributeTypeAndValueSET
Extensions []pkix.Extension
ExtraExtensions []pkix.Extension
DNSNames []string
EmailAddresses []string
IPAddresses []net.IP
有一些内容可以不用填写。如果填写了,在后面生成证书时将作为内容直接填入,我就根据openssl生成证书请求时在控制台所展现的内容进行填写。即添加Subject中的内容。Subject是这样定义的:
type Name struct {
Country, Organization, OrganizationalUnit []string
Locality, Province []string
StreetAddress, PostalCode []string
SerialNumber, CommonName string
Names []AttributeTypeAndValue
ExtraNames []AttributeTypeAndValue
}
生成证书
在go提供的crypto/x509包下并没有生成CA的方法,生成证书的方法也只有一个方法:
func CreateCertificate(rand io.Reader, template, parent *Certificate, pub, priv interface{}) (cert []byte, err error)
它的参数中使用的是两个证书,和我们之前生成的CertificateRequest没有关系,而且在整个crypto/x509中的方法中都没有找到把CertificateRequest转换成Certificate的方法,而且CertificateRequest和Certificate中的部分数据结构是一样的,因此猜想是通过把CertificateRequest中的部分内容复制到Certificate中。然后再通过CreateCertificate进行签发。
如果传入的两个证书参数是一样的,那么生成的证书是一张自签发的根证书。如果传入的两张证书不同,生成的就是普通的证书了。使用的公钥和私钥是签发者的公私钥即参数parent的公私钥。和生成CertificateRequest一样,在这个方法中使用的公私钥不能是DSA类型的。
坑
设置CA
在Certificate这个结构体中有IsCA这个字段。用来标识该证书是CA证书,但是在设置该字段为true后生成的证书在扩展中并没有显示这个证书是CA证书的。原因是在如果要使IsCA生效,需要设置BasicConstraintsValid也为true。同样的也适用于MaxPathLen这个字段。
签名算法的选择
在go中为证书的签名算法提供了常见的类型:
UnknownSignatureAlgorithm SignatureAlgorithm = iota MD2WithRSA MD5WithRSA SHA1WithRSA SHA256WithRSA SHA384WithRSA SHA512WithRSA DSAWithSHA1 DSAWithSHA256 ECDSAWithSHA1 ECDSAWithSHA256 ECDSAWithSHA384 ECDSAWithSHA512
在生成证书的时候我直接选择的SHA1WITHRSA,应为我的私钥是通过RSA算法生成的,没有任何问题,但是在看go的源码中有一段生成自签名证书的测试方法。在该方法中使用了其他的签名算法。因此我想,这些签名算法的应该如何选择。当我把签名算法改成ECDSAWITHSHA1的时候,在进行签名的时候,出现了签名错误。
因此我猜猜签名算法的选择需要和签署者的公私钥的生成方式有关。
代码时间
一切用代码说话。
和生成私钥有关:
func GenRSAPriv(fileName, passwd string, len int) error {
priv, err := rsa.GenerateKey(rand.Reader, len)
if err != nil {
return err
}
data := x509.MarshalPKCS1PrivateKey(priv)
err = encodePrivPemFile(fileName, passwd, data)
return err
}
//GenECDSAPriv 生成ECDSA私钥文件
func GenECDSAPriv(fileName, passwd string) error {
priv, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
if err != nil {
return err
}
data, err := x509.MarshalECPrivateKey(priv)
if err != nil {
return err
}
err = encodePrivPemFile(fileName, passwd, data)
return err
}
//GenDSAPriv 生成DSA私钥(用于演示)
func GenDSAPriv() {
priv := &dsa.PrivateKey{}
dsa.GenerateParameters(&priv.Parameters, rand.Reader, dsa.L1024N160)
dsa.GenerateKey(priv, rand.Reader)
fmt.Printf("priv:%+v\n", priv)
}
//DecodePriv 解析私钥文件生成私钥,(RSA,和ECDSA两种私钥格式)
func DecodePriv(fileName, passwd string) (pubkey, priv interface{}, err error) {
data, err := ioutil.ReadFile(fileName)
if err != nil {
return nil, nil, errors.New("读取私钥文件错误")
}
block, _ := pem.Decode(data)
data, err = x509.DecryptPEMBlock(block, []byte(passwd))
if err != nil {
return nil, nil, err
}
privKey, err := x509.ParsePKCS1PrivateKey(data) //解析成RSA私钥
if err != nil {
priv, err = x509.ParseECPrivateKey(data) //解析成ECDSA私钥
if err != nil {
return nil, nil, errors.New("支持持RSA和ECDSA格式的私钥")
}
}
priv = privKey
pubkey = &privKey.PublicKey
return
}
//生成私钥的pem文件
func encodePrivPemFile(fileName, passwd string, data []byte) error {
block, err := x509.EncryptPEMBlock(rand.Reader, "RSA PRIVATE KEY", data, []byte(passwd), x509.PEMCipher3DES)
if err != nil {
return err
}
file, err := os.Create(fileName)
if err != nil {
return err
}
err = pem.Encode(file, block)
if err != nil {
return err
}
return nil
}
在这个代码用有一些问题:使用ECDSA生成私钥后加密的Type不知道填什么,暂时使用了”RSA PRIVATE KEY”。
和CertificateRequest有关的代码:
// EncodeCsr 生成证书请求
func EncodeCsr(country, organization, organizationlUnit, locality, province, streetAddress, postallCode []string, commonName, fileName string, priv interface{}) error {
req := &x509.CertificateRequest{
Subject: pkix.Name{
Country: country,
Organization: organization,
OrganizationalUnit: organizationlUnit,
Locality: locality,
Province: province,
StreetAddress: streetAddress,
PostalCode: postallCode,
CommonName: commonName,
},
}
data, err := x509.CreateCertificateRequest(rand.Reader, req, priv)
if err != nil {
return err
}
err = util.EncodePemFile(fileName, "CERTIFICATE REQUEST", data)
return err
}
//DecodeCsr 解析CSRpem文件
func DecodeCsr(fileName string) (*x509.CertificateRequest, error) {
data, err := util.DecodePemFile(fileName)
if err != nil {
return nil, err
}
req, err := x509.ParseCertificateRequest(data)
return req, err
}
和生成Certificate有关的代码:
//GenSignselfCertificate 生成自签名证书
func GenSignselfCertificate(req *x509.CertificateRequest, publickey, privKey interface{}, fileName string, maxPath int, days time.Duration) error {
template := &x509.Certificate{
SerialNumber: big.NewInt(random.Int63n(time.Now().Unix())),
Subject: req.Subject,
NotBefore: time.Now(),
NotAfter: time.Now().Add(days * 24 * time.Hour),
BasicConstraintsValid: true,
IsCA: true,
SignatureAlgorithm: x509.SHA1WithRSA, // 签名算法选择SHA1WithRSA
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageDataEncipherment,
SubjectKeyId: []byte{1, 2, 3},
}
if maxPath > 0 { //如果长度超过0则设置了 最大的路径长度
template.MaxPathLen = maxPath
}
cert, err := x509.CreateCertificate(rand.Reader, template, template, publickey, privKey)
if err != nil {
return errors.New("签发自签名证书失败")
}
err = util.EncodePemFile(fileName, "CERTIFICATE", cert)
if err != nil {
return err
}
return nil
}
//GenCertificate 生成非自签名证书
func GenCertificate(req *x509.CertificateRequest, parentCert *x509.Certificate, pubKey, parentPrivKey interface{}, fileName string, isCA bool, days time.Duration) error {
template := &x509.Certificate{
SerialNumber: big.NewInt(random.Int63n(time.Now().Unix())),
Subject: req.Subject,
NotBefore: time.Now(),
NotAfter: time.Now().Add(days * 24 * time.Hour),
// ExtKeyUsage: []x509.ExtKeyUsage{ //额外的使用
// x509.ExtKeyUsageClientAuth,
// x509.ExtKeyUsageServerAuth,
// },
//
SignatureAlgorithm: x509.SHA1WithRSA,
}
if isCA {
template.BasicConstraintsValid = true
template.IsCA = true
}
cert, err := x509.CreateCertificate(rand.Reader, template, parentCert, pubKey, parentPrivKey)
if err != nil {
return errors.New("签署证书失败")
}
err = util.EncodePemFile(fileName, "CERTIFICATE", cert)
if err != nil {
return err
}
return nil
}
在生成证书这方法,由于可设置的内容过多,不应该使用参数来对证书内容进行控制。应该和openssl一样使用配置文件的方式来对证书中的内容进行配置。
今天带大家了解了数字证书的相关知识,希望对你有所帮助;关于Golang的技术知识我们会一点点深入介绍,欢迎大家关注golang学习网公众号,一起学习编程~
golang开发安装go-torch火焰图操作步骤
- 上一篇
- golang开发安装go-torch火焰图操作步骤
- 下一篇
- golang开发go包依赖管理godep使用教程
-
- Golang · Go教程 | 8分钟前 |
- GolangCI/CD测试流程实现详解
- 347浏览 收藏
-
- Golang · Go教程 | 13分钟前 |
- Golang模块冲突解决全攻略
- 200浏览 收藏
-
- Golang · Go教程 | 24分钟前 |
- Go语言处理JSON浮点数编码技巧
- 391浏览 收藏
-
- Golang · Go教程 | 43分钟前 |
- Golangselect多路复用实战教程详解
- 307浏览 收藏
-
- Golang · Go教程 | 54分钟前 |
- MGO存储嵌套结构体方法全解析
- 119浏览 收藏
-
- Golang · Go教程 | 9小时前 | 格式化输出 printf fmt库 格式化动词 Stringer接口
- Golangfmt库用法与格式化技巧解析
- 140浏览 收藏
-
- Golang · Go教程 | 9小时前 |
- Golang配置Protobuf安装教程
- 147浏览 收藏
-
- Golang · Go教程 | 9小时前 |
- Golang中介者模式实现与通信解耦技巧
- 378浏览 收藏
-
- Golang · Go教程 | 9小时前 |
- Golang多协程通信技巧分享
- 255浏览 收藏
-
- Golang · Go教程 | 9小时前 |
- Golang如何判断变量类型?
- 393浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ChatExcel酷表
- ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
- 3167次使用
-
- Any绘本
- 探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
- 3380次使用
-
- 可赞AI
- 可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
- 3409次使用
-
- 星月写作
- 星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
- 4513次使用
-
- MagicLight
- MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
- 3789次使用
-
- Golangmap实践及实现原理解析
- 2022-12-28 505浏览
-
- go和golang的区别解析:帮你选择合适的编程语言
- 2023-12-29 503浏览
-
- 试了下Golang实现try catch的方法
- 2022-12-27 502浏览
-
- 如何在go语言中实现高并发的服务器架构
- 2023-08-27 502浏览
-
- 提升工作效率的Go语言项目开发经验分享
- 2023-11-03 502浏览

