经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Go语言 » 查看文章
golang开发及数字证书研究分享
来源:jb51  时间:2021/11/16 13:13:26  对本文有异议

在go语言提供的系统包中包含了大量和数字证书有关的方法。在这些方法中就有私钥生成的方法、私钥解析的方法、证书请求生成的方法、证书生成的方法等等。通过这些方法应该能够实现和openssl命令类似的功能。

仿照openssl生成证书的流程(从私钥的生成—>证书请求的生成—>证书的生成)用go语言进行模拟。

私钥的生成

在go的x509包下有go定义的证书的结构,该结构如下:

  1. Raw []byte // Complete ASN.1 DER content (certificate, signature algorithm and signature).
  2. RawTBSCertificate []byte // Certificate part of raw ASN.1 DER content.
  3. RawSubjectPublicKeyInfo []byte // DER encoded SubjectPublicKeyInfo.
  4. RawSubject []byte // DER encoded Subject
  5. RawIssuer []byte // DER encoded Issuer
  6. Signature []byte
  7. SignatureAlgorithm SignatureAlgorithm
  8. PublicKeyAlgorithm PublicKeyAlgorithm
  9. PublicKey interface{}
  10. Version int
  11. SerialNumber *big.Int
  12. Issuer pkix.Name
  13. Subject pkix.Name
  14. NotBefore, NotAfter time.Time // Validity bounds.
  15. KeyUsage KeyUsage
  16. Extensions []pkix.Extension
  17. ExtraExtensions []pkix.Extension
  18. UnhandledCriticalExtensions []asn1.ObjectIdentifier
  19. ExtKeyUsage []ExtKeyUsage // Sequence of extended key usages.
  20. UnknownExtKeyUsage []asn1.ObjectIdentifier // Encountered extended key usages unknown to this package.
  21. BasicConstraintsValid bool // if true then the next two fields are valid.
  22. IsCA bool
  23. MaxPathLen int
  24. MaxPathLenZero bool
  25. SubjectKeyId []byte
  26. AuthorityKeyId []byte
  27. OCSPServer []string
  28. IssuingCertificateURL []string
  29. // Subject Alternate Name values
  30. DNSNames []string
  31. EmailAddresses []string
  32. IPAddresses []net.IP
  33. PermittedDNSDomainsCritical bool // if true then the name constraints are marked critical.
  34. PermittedDNSDomains []string
  35. CRLDistributionPoints []string
  36. PolicyIdentifiers []asn1.ObjectIdentifier

在该结构中有PublicKeyAlgorithm字段,该字段用来表示生成公钥的算法。该字段的变量中可使用的字段如下:

  1. const (
  2. UnknownPublicKeyAlgorithm PublicKeyAlgorithm = iota
  3. RSA
  4. DSA
  5. ECDSA
  6. )

一共定义了4中情况。除去Unknown的情况。剩下的三种的实现分别在crypto/rsacrypto/dsacrypto/ecdsa这三个包中定义了实现。

RSA

使用RSA方法生成公私钥的方式非常简单。在crypto/rsa包中直接提供了生成方法。

  1. func GenerateKey(random io.Reader, bits int) (*PrivateKey, error)

该方法生成一个rsa的私钥。查找整个包所提供的方法并没有什么方法能够生成公钥。但在包中有公钥的结构说明。查看私钥的结构:

  1. type PrivateKey struct {
  2. PublicKey // public part.
  3. D *big.Int // private exponent
  4. Primes []*big.Int // prime factors of N, has >= 2 elements.
  5. Precomputed PrecomputedValues
  6. }

赫然发现,公钥包含在私钥的结构中。换句话说,只要生成的私钥,公钥就同时拥有了(ECDSA和DSA的公钥也是如此)。

ECDSA

使用ECDSA生成公私钥的方式和RSA的方式非常类似:

  1. func GenerateKey(c elliptic.Curve, rand io.Reader) (*PrivateKey, error)

crypto/elliptic为参数c提供了4中实现方式。分别为:

  1. func P224() Curve
  2. func P256() Curve
  3. func P384() Curve
  4. func P521() Curve

DSA

使用DSA生成公私钥的方式和上面两种有些不同:

  1. func GenerateKey(priv *PrivateKey, rand io.Reader) error

私钥并不是作为结果返回,而是作为参数传入。那很简单,我直接初始化一个DSA的私钥,然后把该私钥作为参数传入不就可以了嘛。事实是,仅仅是实例化了一个DSA的私钥是无法完成公私钥的生成的。生成的结果如下:

  1. priv:&{PublicKey:{Parameters:{P:<nil> Q:<nil> G:<nil>} Y:<nil>} X:<nil>}

可以发现公钥中的所有内容都是为nil(空),由此可以说明无法只通过GenerateKey()方法生成DSA的私钥。

crypto/dsa包中还提供了:

  1. func GenerateParameters(params *Parameters, rand io.Reader, sizes ParameterSizes) error

通过该方法的描述,可以了解到该方法是为DSA设置参数。那又如何和公私钥有关呢?,在DSA的私钥结构中包含公钥,在公钥的结构中就包含该方法所需要传入的参数Parameters。由此,我便想到可以先使用该方法对一些参数进行初始化,然后再生成私钥。

  1. priv := &dsa.PrivateKey{}
  2. dsa.GenerateParameters(&priv.Parameters, rand.Reader, dsa.L1024N160)
  3. dsa.GenerateKey(priv, rand.Reader)

生成的私钥内容如下:

  1. priv:&{PublicKey:{Parameters:{P:+91268520972047344779510472614939006285152176630742165979533208518526258287540244526987668731096217967904150874969731516661412604963023247030101570715552650277776208098462838867711078025572452557692674802977527475661989210578136725258241385474445330497234586673407237238372329018550727884900161895964574509801 Q:+767580094855879488293276223470508701563202760721 G:+42393651221310072390273970570719382707264443685255379637082820177806079494092036767507554061381644533127114802103872901363724639317297276457243780033980909021336576570837756106975221868617534717069925676009421223798208864916837561389117514471387385853288499961716794226875046226553216578582138687489881455573} Y:+68767508229940365112562020548287141674708444377336699267991474890690034611201698420418573204906537903040876819582645033160073997940957577512216430788561800033703926395782022182868300960590402743043934344374390498368316144177816214923367214895567903510165216432049170686626889267028482641530556275670781873053} X:+628682865942164859869306394087148223993136336500}

注意:Golang 对DSA证书没有完整的支持。

给私钥上锁(加访问密码)

在使用openssl进行私钥生成的时候,openssl需要我提供私钥的访问密码。那使用go进行私钥时,应该也有该功能。那应该在什么时候添加这个密码呢?是在生成私钥的时候,还是在生成pem文件的时候。我首先想到的是在生成秘密的时候,但是在crypto/rsacrypto/dsacrypto/ecdsa这三个包中查找时并没有发现任何和密码有关的词眼。那就应该在生成pem文件的时候加上密码。生成pem文件的方法在encoding/pem这个包中。但该包中只有两个编码,一个解码的方法,和密码有没有任何关系,唯一的存在的关系就是Block结构中的Header字段。

使用openssl生成的私钥文件中会存在这样的字段:

  1. Proc-Type: 4,ENCRYPTED
  2. DEK-Info: DES-EDE3-CBC,02a0ba59e8cfd431

使用该字段来说明使用加密方式和提供用于解密的初始值向量。

在生成私钥和生成文件都无法把密码添加进去。那我就在想是否是在得到私钥的时候对私钥的byte数组进行加密。但这样就需要自己实现了。讲道理的话,go应该会为这种普遍性的东西提供已经封装好的方法。来回重新看api文档。发现自己漏看一个非常重要的包crypto/x509。在该包提供的方法中。很轻松的就找到了如下两个方法:

  1. func DecryptPEMBlock(b *pem.Block, password []byte) ([]byte, error)
  2. func EncryptPEMBlock(rand io.Reader, blockType string, data, password []byte, alg PEMCipher) (*pem.Block, error)

在这两个方法中又要pem,password,恩应该就是这两个方法了,正好一个生成一个解析。

同在x509包下提供了:

  1. func MarshalPKCS1PrivateKey(key *rsa.PrivateKey) []byte
  2. func MarshalECPrivateKey(key *ecdsa.PrivateKey) ([]byte, error)

把RSA和ECDSA私钥转换成byte数组的方法,但是没有找到把DSA私钥转换成byte数组的方法。

生成证书请求

证书请求生成很简单在crypto/x509中直接提供了现成的方法。

  1. func CreateCertificateRequest(rand io.Reader, template *CertificateRequest, priv interface{}) (csr []byte, err error)

但使用用该方法有一个限制条件:

  1. 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是如下定义的:

  1. Raw []byte // Complete ASN.1 DER content (CSR, signature algorithm and signature).
  2. RawTBSCertificateRequest []byte // Certificate request info part of raw ASN.1 DER content.
  3. RawSubjectPublicKeyInfo []byte // DER encoded SubjectPublicKeyInfo.
  4. RawSubject []byte // DER encoded Subject.
  5. Version int
  6. Signature []byte
  7. SignatureAlgorithm SignatureAlgorithm
  8. PublicKeyAlgorithm PublicKeyAlgorithm
  9. PublicKey interface{}
  10. Subject pkix.Name
  11. Attributes []pkix.AttributeTypeAndValueSET
  12. Extensions []pkix.Extension
  13. ExtraExtensions []pkix.Extension
  14. DNSNames []string
  15. EmailAddresses []string
  16. IPAddresses []net.IP

有一些内容可以不用填写。如果填写了,在后面生成证书时将作为内容直接填入,我就根据openssl生成证书请求时在控制台所展现的内容进行填写。即添加Subject中的内容。Subject是这样定义的:

  1. type Name struct {
  2. Country, Organization, OrganizationalUnit []string
  3. Locality, Province []string
  4. StreetAddress, PostalCode []string
  5. SerialNumber, CommonName string
  6. Names []AttributeTypeAndValue
  7. ExtraNames []AttributeTypeAndValue
  8. }

生成证书

在go提供的crypto/x509包下并没有生成CA的方法,生成证书的方法也只有一个方法:

  1. 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中为证书的签名算法提供了常见的类型:

  1. UnknownSignatureAlgorithm SignatureAlgorithm = iota
  2. MD2WithRSA
  3. MD5WithRSA
  4. SHA1WithRSA
  5. SHA256WithRSA
  6. SHA384WithRSA
  7. SHA512WithRSA
  8. DSAWithSHA1
  9. DSAWithSHA256
  10. ECDSAWithSHA1
  11. ECDSAWithSHA256
  12. ECDSAWithSHA384
  13. ECDSAWithSHA512

在生成证书的时候我直接选择的SHA1WITHRSA,应为我的私钥是通过RSA算法生成的,没有任何问题,但是在看go的源码中有一段生成自签名证书的测试方法。在该方法中使用了其他的签名算法。因此我想,这些签名算法的应该如何选择。当我把签名算法改成ECDSAWITHSHA1的时候,在进行签名的时候,出现了签名错误。

因此我猜猜签名算法的选择需要和签署者的公私钥的生成方式有关。

代码时间

一切用代码说话。

和生成私钥有关:

  1. func GenRSAPriv(fileName, passwd string, len int) error {
  2. priv, err := rsa.GenerateKey(rand.Reader, len)
  3. if err != nil {
  4. return err
  5. }
  6. data := x509.MarshalPKCS1PrivateKey(priv)
  7. err = encodePrivPemFile(fileName, passwd, data)
  8. return err
  9. }
  10. //GenECDSAPriv 生成ECDSA私钥文件
  11. func GenECDSAPriv(fileName, passwd string) error {
  12. priv, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
  13. if err != nil {
  14. return err
  15. }
  16. data, err := x509.MarshalECPrivateKey(priv)
  17. if err != nil {
  18. return err
  19. }
  20. err = encodePrivPemFile(fileName, passwd, data)
  21. return err
  22. }
  23. //GenDSAPriv 生成DSA私钥(用于演示)
  24. func GenDSAPriv() {
  25. priv := &dsa.PrivateKey{}
  26. dsa.GenerateParameters(&priv.Parameters, rand.Reader, dsa.L1024N160)
  27. dsa.GenerateKey(priv, rand.Reader)
  28. fmt.Printf("priv:%+v\n", priv)
  29. }
  30. //DecodePriv 解析私钥文件生成私钥,(RSA,和ECDSA两种私钥格式)
  31. func DecodePriv(fileName, passwd string) (pubkey, priv interface{}, err error) {
  32. data, err := ioutil.ReadFile(fileName)
  33. if err != nil {
  34. return nil, nil, errors.New("读取私钥文件错误")
  35. }
  36. block, _ := pem.Decode(data)
  37. data, err = x509.DecryptPEMBlock(block, []byte(passwd))
  38. if err != nil {
  39. return nil, nil, err
  40. }
  41. privKey, err := x509.ParsePKCS1PrivateKey(data) //解析成RSA私钥
  42. if err != nil {
  43. priv, err = x509.ParseECPrivateKey(data) //解析成ECDSA私钥
  44. if err != nil {
  45. return nil, nil, errors.New("支持持RSA和ECDSA格式的私钥")
  46. }
  47. }
  48. priv = privKey
  49. pubkey = &privKey.PublicKey
  50. return
  51. }
  52. //生成私钥的pem文件
  53. func encodePrivPemFile(fileName, passwd string, data []byte) error {
  54. block, err := x509.EncryptPEMBlock(rand.Reader, "RSA PRIVATE KEY", data, []byte(passwd), x509.PEMCipher3DES)
  55. if err != nil {
  56. return err
  57. }
  58. file, err := os.Create(fileName)
  59. if err != nil {
  60. return err
  61. }
  62. err = pem.Encode(file, block)
  63. if err != nil {
  64. return err
  65. }
  66. return nil
  67. }

在这个代码用有一些问题:使用ECDSA生成私钥后加密的Type不知道填什么,暂时使用了”RSA PRIVATE KEY”。

和CertificateRequest有关的代码:

  1. // EncodeCsr 生成证书请求
  2. func EncodeCsr(country, organization, organizationlUnit, locality, province, streetAddress, postallCode []string, commonName, fileName string, priv interface{}) error {
  3. req := &x509.CertificateRequest{
  4. Subject: pkix.Name{
  5. Country: country,
  6. Organization: organization,
  7. OrganizationalUnit: organizationlUnit,
  8. Locality: locality,
  9. Province: province,
  10. StreetAddress: streetAddress,
  11. PostalCode: postallCode,
  12. CommonName: commonName,
  13. },
  14. }
  15. data, err := x509.CreateCertificateRequest(rand.Reader, req, priv)
  16. if err != nil {
  17. return err
  18. }
  19. err = util.EncodePemFile(fileName, "CERTIFICATE REQUEST", data)
  20. return err
  21. }
  22. //DecodeCsr 解析CSRpem文件
  23. func DecodeCsr(fileName string) (*x509.CertificateRequest, error) {
  24. data, err := util.DecodePemFile(fileName)
  25. if err != nil {
  26. return nil, err
  27. }
  28. req, err := x509.ParseCertificateRequest(data)
  29. return req, err
  30. }

和生成Certificate有关的代码:

  1. //GenSignselfCertificate 生成自签名证书
  2. func GenSignselfCertificate(req *x509.CertificateRequest, publickey, privKey interface{}, fileName string, maxPath int, days time.Duration) error {
  3. template := &x509.Certificate{
  4. SerialNumber: big.NewInt(random.Int63n(time.Now().Unix())),
  5. Subject: req.Subject,
  6. NotBefore: time.Now(),
  7. NotAfter: time.Now().Add(days * 24 * time.Hour),
  8. BasicConstraintsValid: true,
  9. IsCA: true,
  10. SignatureAlgorithm: x509.SHA1WithRSA, // 签名算法选择SHA1WithRSA
  11. KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageDataEncipherment,
  12. SubjectKeyId: []byte{1, 2, 3},
  13. }
  14. if maxPath > 0 { //如果长度超过0则设置了 最大的路径长度
  15. template.MaxPathLen = maxPath
  16. }
  17. cert, err := x509.CreateCertificate(rand.Reader, template, template, publickey, privKey)
  18. if err != nil {
  19. return errors.New("签发自签名证书失败")
  20. }
  21. err = util.EncodePemFile(fileName, "CERTIFICATE", cert)
  22. if err != nil {
  23. return err
  24. }
  25. return nil
  26. }
  27. //GenCertificate 生成非自签名证书
  28. func GenCertificate(req *x509.CertificateRequest, parentCert *x509.Certificate, pubKey, parentPrivKey interface{}, fileName string, isCA bool, days time.Duration) error {
  29. template := &x509.Certificate{
  30. SerialNumber: big.NewInt(random.Int63n(time.Now().Unix())),
  31. Subject: req.Subject,
  32. NotBefore: time.Now(),
  33. NotAfter: time.Now().Add(days * 24 * time.Hour),
  34. // ExtKeyUsage: []x509.ExtKeyUsage{ //额外的使用
  35. // x509.ExtKeyUsageClientAuth,
  36. // x509.ExtKeyUsageServerAuth,
  37. // },
  38. //
  39. SignatureAlgorithm: x509.SHA1WithRSA,
  40. }
  41. if isCA {
  42. template.BasicConstraintsValid = true
  43. template.IsCA = true
  44. }
  45. cert, err := x509.CreateCertificate(rand.Reader, template, parentCert, pubKey, parentPrivKey)
  46. if err != nil {
  47. return errors.New("签署证书失败")
  48. }
  49. err = util.EncodePemFile(fileName, "CERTIFICATE", cert)
  50. if err != nil {
  51. return err
  52. }
  53. return nil
  54. }

在生成证书这方法,由于可设置的内容过多,不应该使用参数来对证书内容进行控制。应该和openssl一样使用配置文件的方式来对证书中的内容进行配置。

以上就是golang开发及数字证书研究分享的详细内容,更多关于golang的资料请关注w3xue其它相关文章!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号