golang開(kāi)發(fā)及數(shù)字證書(shū)研究分享
在go語(yǔ)言提供的系統(tǒng)包中包含了大量和數(shù)字證書(shū)有關(guān)的方法。在這些方法中就有私鑰生成的方法、私鑰解析的方法、證書(shū)請(qǐng)求生成的方法、證書(shū)生成的方法等等。通過(guò)這些方法應(yīng)該能夠?qū)崿F(xiàn)和openssl命令類(lèi)似的功能。
仿照openssl生成證書(shū)的流程(從私鑰的生成—>證書(shū)請(qǐng)求的生成—>證書(shū)的生成)用go語(yǔ)言進(jìn)行模擬。
私鑰的生成
在go的x509包下有g(shù)o定義的證書(shū)的結(jié)構(gòu),該結(jié)構(gòu)如下:
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
在該結(jié)構(gòu)中有PublicKeyAlgorithm
字段,該字段用來(lái)表示生成公鑰的算法。該字段的變量中可使用的字段如下:
const ( UnknownPublicKeyAlgorithm PublicKeyAlgorithm = iota RSA DSA ECDSA )
一共定義了4中情況。除去Unknown的情況。剩下的三種的實(shí)現(xiàn)分別在crypto/rsa
、crypto/dsa
、crypto/ecdsa
這三個(gè)包中定義了實(shí)現(xiàn)。
RSA
使用RSA方法生成公私鑰的方式非常簡(jiǎn)單。在crypto/rsa
包中直接提供了生成方法。
func GenerateKey(random io.Reader, bits int) (*PrivateKey, error)
該方法生成一個(gè)rsa的私鑰。查找整個(gè)包所提供的方法并沒(méi)有什么方法能夠生成公鑰。但在包中有公鑰的結(jié)構(gòu)說(shuō)明。查看私鑰的結(jié)構(gòu):
type PrivateKey struct { PublicKey // public part. D *big.Int // private exponent Primes []*big.Int // prime factors of N, has >= 2 elements. Precomputed PrecomputedValues }
赫然發(fā)現(xiàn),公鑰包含在私鑰的結(jié)構(gòu)中。換句話說(shuō),只要生成的私鑰,公鑰就同時(shí)擁有了(ECDSA和DSA的公鑰也是如此)。
ECDSA
使用ECDSA生成公私鑰的方式和RSA的方式非常類(lèi)似:
func GenerateKey(c elliptic.Curve, rand io.Reader) (*PrivateKey, error)
在crypto/elliptic
為參數(shù)c提供了4中實(shí)現(xiàn)方式。分別為:
func P224() Curve func P256() Curve func P384() Curve func P521() Curve
DSA
使用DSA生成公私鑰的方式和上面兩種有些不同:
func GenerateKey(priv *PrivateKey, rand io.Reader) error
私鑰并不是作為結(jié)果返回,而是作為參數(shù)傳入。那很簡(jiǎn)單,我直接初始化一個(gè)DSA的私鑰,然后把該私鑰作為參數(shù)傳入不就可以了嘛。事實(shí)是,僅僅是實(shí)例化了一個(gè)DSA的私鑰是無(wú)法完成公私鑰的生成的。生成的結(jié)果如下:
priv:&{PublicKey:{Parameters:{P:<nil> Q:<nil> G:<nil>} Y:<nil>} X:<nil>}
可以發(fā)現(xiàn)公鑰中的所有內(nèi)容都是為nil(空),由此可以說(shuō)明無(wú)法只通過(guò)GenerateKey()
方法生成DSA的私鑰。
在crypto/dsa
包中還提供了:
func GenerateParameters(params *Parameters, rand io.Reader, sizes ParameterSizes) error
通過(guò)該方法的描述,可以了解到該方法是為DSA設(shè)置參數(shù)。那又如何和公私鑰有關(guān)呢?,在DSA的私鑰結(jié)構(gòu)中包含公鑰,在公鑰的結(jié)構(gòu)中就包含該方法所需要傳入的參數(shù)Parameters
。由此,我便想到可以先使用該方法對(duì)一些參數(shù)進(jìn)行初始化,然后再生成私鑰。
priv := &dsa.PrivateKey{} dsa.GenerateParameters(&priv.Parameters, rand.Reader, dsa.L1024N160) dsa.GenerateKey(priv, rand.Reader)
生成的私鑰內(nèi)容如下:
priv:&{PublicKey:{Parameters:{P:+91268520972047344779510472614939006285152176630742165979533208518526258287540244526987668731096217967904150874969731516661412604963023247030101570715552650277776208098462838867711078025572452557692674802977527475661989210578136725258241385474445330497234586673407237238372329018550727884900161895964574509801 Q:+767580094855879488293276223470508701563202760721 G:+42393651221310072390273970570719382707264443685255379637082820177806079494092036767507554061381644533127114802103872901363724639317297276457243780033980909021336576570837756106975221868617534717069925676009421223798208864916837561389117514471387385853288499961716794226875046226553216578582138687489881455573} Y:+68767508229940365112562020548287141674708444377336699267991474890690034611201698420418573204906537903040876819582645033160073997940957577512216430788561800033703926395782022182868300960590402743043934344374390498368316144177816214923367214895567903510165216432049170686626889267028482641530556275670781873053} X:+628682865942164859869306394087148223993136336500}
注意:Golang 對(duì)DSA證書(shū)沒(méi)有完整的支持。
給私鑰上鎖(加訪問(wèn)密碼)
在使用openssl進(jìn)行私鑰生成的時(shí)候,openssl需要我提供私鑰的訪問(wèn)密碼。那使用go進(jìn)行私鑰時(shí),應(yīng)該也有該功能。那應(yīng)該在什么時(shí)候添加這個(gè)密碼呢?是在生成私鑰的時(shí)候,還是在生成pem文件的時(shí)候。我首先想到的是在生成秘密的時(shí)候,但是在crypto/rsa
、crypto/dsa
、crypto/ecdsa
這三個(gè)包中查找時(shí)并沒(méi)有發(fā)現(xiàn)任何和密碼有關(guān)的詞眼。那就應(yīng)該在生成pem文件的時(shí)候加上密碼。生成pem文件的方法在encoding/pem
這個(gè)包中。但該包中只有兩個(gè)編碼,一個(gè)解碼的方法,和密碼有沒(méi)有任何關(guān)系,唯一的存在的關(guān)系就是Block
結(jié)構(gòu)中的Header
字段。
使用openssl生成的私鑰文件中會(huì)存在這樣的字段:
Proc-Type: 4,ENCRYPTED DEK-Info: DES-EDE3-CBC,02a0ba59e8cfd431
使用該字段來(lái)說(shuō)明使用加密方式和提供用于解密的初始值向量。
在生成私鑰和生成文件都無(wú)法把密碼添加進(jìn)去。那我就在想是否是在得到私鑰的時(shí)候?qū)λ借€的byte數(shù)組進(jìn)行加密。但這樣就需要自己實(shí)現(xiàn)了。講道理的話,go應(yīng)該會(huì)為這種普遍性的東西提供已經(jīng)封裝好的方法。來(lái)回重新看api文檔。發(fā)現(xiàn)自己漏看一個(gè)非常重要的包crypto/x509
。在該包提供的方法中。很輕松的就找到了如下兩個(gè)方法:
func DecryptPEMBlock(b *pem.Block, password []byte) ([]byte, error) func EncryptPEMBlock(rand io.Reader, blockType string, data, password []byte, alg PEMCipher) (*pem.Block, error)
在這兩個(gè)方法中又要pem,password,恩應(yīng)該就是這兩個(gè)方法了,正好一個(gè)生成一個(gè)解析。
同在x509
包下提供了:
func MarshalPKCS1PrivateKey(key *rsa.PrivateKey) []byte func MarshalECPrivateKey(key *ecdsa.PrivateKey) ([]byte, error)
把RSA和ECDSA私鑰轉(zhuǎn)換成byte數(shù)組的方法,但是沒(méi)有找到把DSA私鑰轉(zhuǎn)換成byte數(shù)組的方法。
生成證書(shū)請(qǐng)求
證書(shū)請(qǐng)求生成很簡(jiǎn)單在crypto/x509
中直接提供了現(xiàn)成的方法。
func CreateCertificateRequest(rand io.Reader, template *CertificateRequest, priv interface{}) (csr []byte, err error)
但使用用該方法有一個(gè)限制條件:
All keys types that are implemented via crypto.Signer are supported (This includes *rsa.PublicKey and *ecdsa.PublicKey.)
無(wú)法使用*dsa.PublicKey類(lèi)型的公鑰。而傳入的參數(shù)是一個(gè)私鑰,因此無(wú)法使用dsa類(lèi)型的私鑰。
go對(duì)dsa類(lèi)型的證書(shū)
該方法需要通過(guò)一個(gè)證書(shū)請(qǐng)求的模板,在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
有一些內(nèi)容可以不用填寫(xiě)。如果填寫(xiě)了,在后面生成證書(shū)時(shí)將作為內(nèi)容直接填入,我就根據(jù)openssl生成證書(shū)請(qǐng)求時(shí)在控制臺(tái)所展現(xiàn)的內(nèi)容進(jìn)行填寫(xiě)。即添加Subject中的內(nèi)容。Subject是這樣定義的:
type Name struct { Country, Organization, OrganizationalUnit []string Locality, Province []string StreetAddress, PostalCode []string SerialNumber, CommonName string Names []AttributeTypeAndValue ExtraNames []AttributeTypeAndValue }
生成證書(shū)
在go提供的crypto/x509
包下并沒(méi)有生成CA的方法,生成證書(shū)的方法也只有一個(gè)方法:
func CreateCertificate(rand io.Reader, template, parent *Certificate, pub, priv interface{}) (cert []byte, err error)
它的參數(shù)中使用的是兩個(gè)證書(shū),和我們之前生成的CertificateRequest沒(méi)有關(guān)系,而且在整個(gè)crypto/x509
中的方法中都沒(méi)有找到把CertificateRequest轉(zhuǎn)換成Certificate的方法,而且CertificateRequest和Certificate中的部分?jǐn)?shù)據(jù)結(jié)構(gòu)是一樣的,因此猜想是通過(guò)把CertificateRequest中的部分內(nèi)容復(fù)制到Certificate中。然后再通過(guò)CreateCertificate進(jìn)行簽發(fā)。
如果傳入的兩個(gè)證書(shū)參數(shù)是一樣的,那么生成的證書(shū)是一張自簽發(fā)的根證書(shū)。如果傳入的兩張證書(shū)不同,生成的就是普通的證書(shū)了。使用的公鑰和私鑰是簽發(fā)者的公私鑰即參數(shù)parent的公私鑰。和生成CertificateRequest一樣,在這個(gè)方法中使用的公私鑰不能是DSA類(lèi)型的。
坑
設(shè)置CA
在Certificate這個(gè)結(jié)構(gòu)體中有IsCA
這個(gè)字段。用來(lái)標(biāo)識(shí)該證書(shū)是CA證書(shū),但是在設(shè)置該字段為true后生成的證書(shū)在擴(kuò)展中并沒(méi)有顯示這個(gè)證書(shū)是CA證書(shū)的。原因是在如果要使IsCA
生效,需要設(shè)置BasicConstraintsValid
也為true。同樣的也適用于MaxPathLen
這個(gè)字段。
簽名算法的選擇
在go中為證書(shū)的簽名算法提供了常見(jiàn)的類(lèi)型:
UnknownSignatureAlgorithm SignatureAlgorithm = iota MD2WithRSA MD5WithRSA SHA1WithRSA SHA256WithRSA SHA384WithRSA SHA512WithRSA DSAWithSHA1 DSAWithSHA256 ECDSAWithSHA1 ECDSAWithSHA256 ECDSAWithSHA384 ECDSAWithSHA512
在生成證書(shū)的時(shí)候我直接選擇的SHA1WITHRSA
,應(yīng)為我的私鑰是通過(guò)RSA算法生成的,沒(méi)有任何問(wèn)題,但是在看go的源碼中有一段生成自簽名證書(shū)的測(cè)試方法。在該方法中使用了其他的簽名算法。因此我想,這些簽名算法的應(yīng)該如何選擇。當(dāng)我把簽名算法改成ECDSAWITHSHA1
的時(shí)候,在進(jìn)行簽名的時(shí)候,出現(xiàn)了簽名錯(cuò)誤。
因此我猜猜簽名算法的選擇需要和簽署者的公私鑰的生成方式有關(guān)。
代碼時(shí)間
一切用代碼說(shuō)話。
和生成私鑰有關(guān):
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("讀取私鑰文件錯(cuò)誤") } 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 }
在這個(gè)代碼用有一些問(wèn)題:使用ECDSA生成私鑰后加密的Type不知道填什么,暫時(shí)使用了”RSA PRIVATE KEY”。
和CertificateRequest有關(guān)的代碼:
// EncodeCsr 生成證書(shū)請(qǐng)求 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有關(guān)的代碼:
//GenSignselfCertificate 生成自簽名證書(shū) 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 { //如果長(zhǎng)度超過(guò)0則設(shè)置了 最大的路徑長(zhǎng)度 template.MaxPathLen = maxPath } cert, err := x509.CreateCertificate(rand.Reader, template, template, publickey, privKey) if err != nil { return errors.New("簽發(fā)自簽名證書(shū)失敗") } err = util.EncodePemFile(fileName, "CERTIFICATE", cert) if err != nil { return err } return nil } //GenCertificate 生成非自簽名證書(shū) 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("簽署證書(shū)失敗") } err = util.EncodePemFile(fileName, "CERTIFICATE", cert) if err != nil { return err } return nil }
在生成證書(shū)這方法,由于可設(shè)置的內(nèi)容過(guò)多,不應(yīng)該使用參數(shù)來(lái)對(duì)證書(shū)內(nèi)容進(jìn)行控制。應(yīng)該和openssl一樣使用配置文件的方式來(lái)對(duì)證書(shū)中的內(nèi)容進(jìn)行配置。
以上就是golang開(kāi)發(fā)及數(shù)字證書(shū)研究分享的詳細(xì)內(nèi)容,更多關(guān)于golang的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
基于HLS創(chuàng)建Golang視頻流服務(wù)器的優(yōu)缺點(diǎn)
HLS 是 HTTP Live Streaming 的縮寫(xiě),是蘋(píng)果開(kāi)發(fā)的一種基于 HTTP 的自適應(yīng)比特率流媒體傳輸協(xié)議。這篇文章主要介紹了基于 HLS 創(chuàng)建 Golang 視頻流服務(wù)器,需要的朋友可以參考下2021-08-08go時(shí)間/時(shí)間戳操作大全(小結(jié))
這篇文章主要介紹了go時(shí)間/時(shí)間戳操作大全,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07Golang結(jié)合ip2region實(shí)現(xiàn)ip歸屬地查詢(xún)
ip2region - 是一個(gè)離線IP地址定位庫(kù)和IP定位數(shù)據(jù)管理框架,提供了眾多主流編程語(yǔ)言的 xdb 數(shù)據(jù)生成和查詢(xún)客戶(hù)端實(shí)現(xiàn),下面我們就來(lái)看看Golang如何結(jié)合ip2region實(shí)現(xiàn)ip歸屬地查詢(xún)吧2024-03-03詳解Go語(yǔ)言中init的使用與常見(jiàn)應(yīng)用場(chǎng)景
Go?中有一個(gè)特別的?init()?函數(shù),它主要用于包的初始化,這篇文章將以此為主題介紹?Go?中?init()?函數(shù)的使用和常見(jiàn)使用場(chǎng)景,希望對(duì)大家有所幫助2024-02-02一文詳細(xì)介紹golang中.()類(lèi)型斷言的使用方法
Golang是一門(mén)非常流行的編程語(yǔ)言,在很多領(lǐng)域都有著廣泛的應(yīng)用,在開(kāi)發(fā)過(guò)程中,很多時(shí)候我們需要將函數(shù)作為參數(shù)傳遞給其他函數(shù),這時(shí)候就需要用到golang中的.()用法,本文將詳細(xì)介紹golang中.()的使用方法,需要的朋友可以參考下2023-08-08GO中的slice使用簡(jiǎn)介(源碼分析slice)
slice(切片)是go中常見(jiàn)和強(qiáng)大的類(lèi)型,這篇文章不是slice使用簡(jiǎn)介,從源碼角度來(lái)分析slice的實(shí)現(xiàn),slice的一些迷惑的使用方式,感興趣的朋友跟隨小編一起看看吧2023-06-06Go語(yǔ)言通過(guò)WaitGroup實(shí)現(xiàn)控制并發(fā)的示例詳解
Channel能夠很好的幫助我們控制并發(fā),但是在開(kāi)發(fā)習(xí)慣上與顯示的表達(dá)不太相同,所以在Go語(yǔ)言中可以利用sync包中的WaitGroup實(shí)現(xiàn)并發(fā)控制,本文就來(lái)和大家詳細(xì)聊聊WaitGroup如何實(shí)現(xiàn)控制并發(fā)2023-01-01Golang極簡(jiǎn)入門(mén)教程(二):方法和接口
這篇文章主要介紹了Golang極簡(jiǎn)入門(mén)教程(二):方法和接口,本文同時(shí)講解了錯(cuò)誤、匿名域等內(nèi)容,需要的朋友可以參考下2014-10-10