Go语言开发(十五)、Go语言常用标准库五
一、md5
1、md5简介
md5在crypto/md5包中,md5包提供了New和Sum方法。
func New() hash.Hash
func Sum(data []byte) [Size]byte
hash.Hash继承了io.Writer,因此可以将其当成一个输入流进行内容的更新。
type Writer interface {
Write(p []byte) (n int, err error)
}
Write方法将p中的内容读入后存入到hash.Hash,最后在Sum方法通过内部函数checkSum计算出其校验和。
Sum函数是对hash.Hash对象内部存储的内容进行校验和计算然后将其追加到data的后面形成一个新的byte切片。通常将data置为nil。
Sum方法返回一个Size大小的byte数组,对于MD5返回一个128bit的16字节byte数组。
2、md5使用示例
package main
import (
"crypto/md5"
"fmt"
"io"
"os"
)
func MD5FromWriter() {
h := md5.New()
io.WriteString(h, "The fog is getting thicker!")
io.WriteString(h, "And Leon's getting laaarger!")
fmt.Printf("%x\n", h.Sum(nil))
// Output: e2c569be17396eca2a2e3c11578123ed
}
func MD5FromSum() {
data := []byte("These pretzels are making me thirsty.")
fmt.Printf("%x\n", md5.Sum(data))
// Output: b0804ec967f48520697662a204f5fe72
}
func MD5FromFile() {
f, err := os.Open("file.txt")
if err != nil {
fmt.Println("Open file failded")
}
defer f.Close()
h := md5.New()
if _, err := io.Copy(h, f); err != nil {
fmt.Println("Copy failed")
}
fmt.Printf("%x\n", h.Sum(nil))
}
func main() {
MD5FromWriter()
MD5FromSum()
MD5FromFile()
}
二、sha256
1、sha256简介
SHA-256算法输入报文的最大长度不超过2^64 bit,输入按512-bit分组进行处理,输出一个256-bit的报文摘要。
sha256支持根据输入生成SHA256和SHA224的报文摘要,主要方法如下:
func New() hash.Hash
func New224() hash.Hash
func Sum256(data []byte) [Size]byte
func Sum224(data []byte) (sum224 [Size224]byte)
2、sha256使用示例
package main
import (
"crypto/sha256"
"fmt"
"io"
"os"
)
func SHA256FromSum256() {
sum := sha256.Sum256([]byte("hello world\n"))
fmt.Printf("%x\n", sum)
// Output: a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447
}
func SHA256FromWriter() {
h := sha256.New()
h.Write([]byte("hello world\n"))
fmt.Printf("%x\n", h.Sum(nil))
// Output: a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447
}
func SHA256FromFile() {
f, err := os.Open("file.txt")
if err != nil {
fmt.Println("Open file failed.")
}
defer f.Close()
h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
fmt.Println("Copy file failed.")
}
fmt.Printf("%x\n", h.Sum(nil))
}
func main() {
SHA256FromSum256()
SHA256FromWriter()
SHA256FromFile()
}
三、tls
1、tls简介
TLS(Transport Layer Security,传输层安全协议)及其前身SSL(Secure Sockets Layer,安全套接层)是一种安全协议,目的是为互联网通信提供安全及数据完整性保障。
Go语言tls包实现了tls 1.2的功能,可以满足日常的应用。x509包提供证书管理的相关操作。
SSL包含记录层(Record Layer)和传输层,记录层协议确定了传输层数据的封装格式。传输层安全协议使用X.509认证,利用非对称加密演算来对通信方做身份认证,然后交换对称密钥作为会谈密钥(Session key)。会谈密钥用来将通信两方交换的数据做加密,保证两个应用间通信的保密性和可靠性,使客户与服务器应用之间的通信不被***者窃听。
生成服务端密钥:openssl genrsa -out key.pem 2048
生成服务端证书:openssl req -new -x509 -key key.pem -out cert.pem -days 3650
生成客户端密钥:openssl genrsa -out client.key 2048
生成客户端密钥:openssl req -new -x509 -key client.key -out client.pem -days 3650
每个节点(不管是客户端还是服务端)都有一个证书文件和key文件,用来互相加密解密;证书里包含public key,key文件里包含private key,证书与key文件构成一对密钥对,是互为加解密的。
根证书是所有节点公用的,不管是客户端还是服务端,都要先注册根证书(通常根证书注册是注册到操作系统信任的根证书数据库里),以示根证书是可信的, 然后当需要验证对方的证书时,因为待验证的证书是通过根证书签名的,由于信任根证书,所以也可以信任对方的证书。
如果需要实现双向认证,那么每一端都需要三个文件:{node}.cer: PEM certificate
己方证书文件,将会被发给对方,让对方认证。{node}.key: PEM RSA private key
己方private key文件,用来解密经己方证书(包含己方public key)加密的内容,加密过程一般是由对方实施的。ca.cer: PEM certificate
根证书文件,用来验证对方发过来的证书文件,所有由同一个根证书签名的证书都应该能验证通过。
2、tls常用方法
func Server(conn net.Conn, config *Config) *Conn
服务器返回一个新的TLS服务器端连接,使用conn作为底层传输。配置配置必须非零,并且必须包含至少一个证书或者设置GetCertificate。
func Client(conn net.Conn, config *Config) *Conn
func NewListener(inner net.Listener, config *Config) net.Listener
NewListener创建一个 Listener,它接受来自内部 Listener 的连接并将每个连接包装在 Server 中。配置必须非零,并且必须包含至少一个证书或者设置 GetCertificate。func Listen(network, laddr string, config *Config) (net.Listener, error)
Listen 会使用 net.Listen 创建一个 TLS 侦听器来接受给定网络地址上的连接。配置配置必须非零,并且必须包含至少一个证书或者设置 GetCertificate。func DialWithDialer(dialer *net.Dialer, network, addr string, config *Config) (*Conn, error)
DialWithDialer 使用 dialer.Dial 连接到给定的网络地址,然后启动TLS handshake,返回生成的 TLS 连接。拨号程序中给出的任何超时或截止日期都适用于连接和 TLS handshake。
DialWithDialer 将零配置解释为等同于零配置;请参阅 Config 的文档以了解默认值。func Dial(network, addr string, config *Config) (*Conn, error)
拨号使用 net.Dial 连接到给定的网络地址,然后启动 TLS handshake,返回生成的 TLS 连接。拨号将零配置解释为等同于零配置;请参阅 Config 的文档以了解默认值。func LoadX509KeyPair(certFile, keyFile string) (Certificate, error)
LoadX509KeyPair读取并解析来自一对文件的证书(公钥)/私钥对。证书和私钥文件必须包含PEM编码数据。证书文件可能包含leaf证书后的中间证书以形成证书链。成功返回时,Certificate.Leaf 将为零,因为不会保留解析的证书形式。func X509KeyPair(certPEMBlock, keyPEMBlock []byte) (Certificate, error)
X509KeyPair从一对PEM编码数据中解析公钥/私钥对。成功返回时,Certificate.Leaf将为零,因为不会保留解析的证书形式。
3、服务端单向认证示例
生成服务端密钥:openssl genrsa -out key.pem 2048
生成服务端证书:openssl req -new -x509 -key key.pem -out cert.pem -days 3650
私钥和证书存放在config/server目录下。
服务端代码server.go:
package main
import (
"bufio"
"crypto/tls"
"fmt"
"net"
)
func main() {
cert, err := tls.LoadX509KeyPair("../config/server/cert.pem", "../config/server/key.pem")
if err != nil {
fmt.Println(err)
return
}
config := &tls.Config{Certificates: []tls.Certificate{cert}}
ln, err := tls.Listen("tcp", ":1443", config)
if err != nil {
fmt.Println(err)
return
}
defer ln.Close()
for {
conn, err := ln.Accept()
if err != nil {
fmt.Println(err)
continue
}
go handleConn(conn)
}
}
func handleConn(conn net.Conn) {
defer conn.Close()
r := bufio.NewReader(conn)
for {
msg, err := r.ReadString('\n')
if err != nil {
fmt.Println(err)
return
}
println(msg)
n, err := conn.Write([]byte(msg))
if err != nil {
fmt.Println(n, err)
return
}
}
}
客户端代码client.go:
package main
import (
"crypto/tls"
"fmt"
)
func main() {
conf := &tls.Config{
InsecureSkipVerify: true,
}
conn, err := tls.Dial("tcp", "127.0.0.1:1443", conf)
if err != nil {
fmt.Println(err)
return
}
defer conn.Close()
n, err := conn.Write([]byte("hello world\n"))
if err != nil {
fmt.Println(n, err)
return
}
buf := make([]byte, 100)
n, err = conn.Read(buf)
if err != nil {
fmt.Println(n, err)
return
}
println(string(buf[:n]))
}
InsecureSkipVerify如果设置为true,则不会校验证书以及证书中的主机名和服务器主机名是否一致。
4、服务端客户端双向认证示例
生成服务端密钥:openssl genrsa -out key.pem 2048
生成服务端证书:openssl req -new -x509 -key key.pem -out cert.pem -days 3650
私钥和证书存放在config/server目录下。
生成客户端密钥:openssl genrsa -out client.key 2048
生成客户端密钥:openssl req -new -x509 -key client.key -out client.pem -days 3650
私钥和证书存放在config/client目录下。
服务端客户端双向认证时,服务端需要验证客户端的真实性,需要服务端和客户端进行一点额外的配置。
服务端server.go代码:
package main
import (
"bufio"
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"net"
)
func main() {
cert, err := tls.LoadX509KeyPair("../config/server/cert.pem", "../config/server/key.pem")
if err != nil {
fmt.Println(err)
return
}
certBytes, err := ioutil.ReadFile("../config/client/client.pem")
if err != nil {
panic("Unable to read cert.pem")
}
clientCertPool := x509.NewCertPool()
ok := clientCertPool.AppendCertsFromPEM(certBytes)
if !ok {
panic("failed to parse root certificate")
}
config := &tls.Config{
Certificates: []tls.Certificate{cert},
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: clientCertPool,
}
ln, err := tls.Listen("tcp", ":1443", config)
if err != nil {
fmt.Println(err)
return
}
defer ln.Close()
for {
conn, err := ln.Accept()
if err != nil {
fmt.Println(err)
continue
}
go handleConn(conn)
}
}
func handleConn(conn net.Conn) {
defer conn.Close()
r := bufio.NewReader(conn)
for {
msg, err := r.ReadString('\n')
if err != nil {
fmt.Println(err)
return
}
println(msg)
n, err := conn.Write([]byte(msg))
if err != nil {
fmt.Println(n, err)
return
}
}
}
客户端client.go代码:
package main
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
)
func main() {
cert, err := tls.LoadX509KeyPair("../config/client/client.pem", "../config/client/client.key")
if err != nil {
fmt.Println(err)
return
}
certBytes, err := ioutil.ReadFile("../config/client/client.pem")
if err != nil {
fmt.Println(err)
}
clientCertPool := x509.NewCertPool()
ok := clientCertPool.AppendCertsFromPEM(certBytes)
if !ok {
fmt.Println("failed to parse root certificate")
}
conf := &tls.Config{
RootCAs: clientCertPool,
Certificates: []tls.Certificate{cert},
InsecureSkipVerify: true,
}
conn, err := tls.Dial("tcp", "127.0.0.1:1443", conf)
if err != nil {
fmt.Println(err)
return
}
defer conn.Close()
n, err := conn.Write([]byte("hello world\n"))
if err != nil {
fmt.Println(n, err)
return
}
buf := make([]byte, 100)
n, err = conn.Read(buf)
if err != nil {
fmt.Println(n, err)
return
}
println(string(buf[:n]))
}
四、x509
1、x509简介
x509包提供了证书管理的相关操作。
PKCS(Public-Key Cryptography Standards),即公钥密码学标准,是由 RSA 实验室与其它安全系统开发商为促进公钥密码的发展而制订的一系列标准。
X.509是常见通用的证书格式,所有的证书都符合为公钥基础设施PKI(Public Key Infrastructure ) 制定的ITU-T X509国际标准。
PEM格式通常用于数字证书认证机构(Certificate Authorities,CA),扩展名为.pem,.crt,.cer,.key,内容为Base64编码的ASCII码文件。
DER格式与PEM不同之处在于其使用二进制而不是Base64编码的ASCII。扩展名为.der,但也经常使用.cer用作扩展名,所有类型的认证证书和私钥都可以存储为DER格式。
PEM编码转换为DER编码:openssl x509 -outform der -in certificate.pem -out certificate.der
DER编码转换为PEM编码:openssl x509 -inform der -in certificate.cer -out certificate.pem
2、x509常用方法
func ParsePKIXPublicKey(derBytes []byte) (pub interface{}, err error)
ParsePKIXPublicKey解析DER编码的公钥derBytes,成功返回*rsa.PublicKey,*dsa.PublicKey,*ecdsa.PublicKey
类型的公钥。func MarshalPKIXPublicKey(pub interface{}) ([]byte, error)
MarshalPKIXPublicKey将公钥pub序列化为DER编码的PKIX格式func ParsePKCS1PrivateKey(der []byte) (*rsa.PrivateKey, error)
ParsePKCS1PrivateKey对ASN.1 PKCS#1 DER编码的der进行解析,返回*rsa.PrivateKey
私钥func MarshalPKCS1PrivateKey(key *rsa.PrivateKey) []byte
MarshalPKCS1PrivateKey对key私钥序列化为ASN.1 DER编码格式func ParsePKCS1PublicKey(der []byte) (*rsa.PublicKey, error)
ParsePKCS1PublicKey对ASN.1 PKCS#1 DER编码的der进行解析,返回*rsa.PublicKey
公钥func MarshalPKCS1PublicKey(key *rsa.PublicKey) []byte
MarshalPKCS1PublicKey对key公钥序列化为ASN.1 DER编码格式func ParsePKCS8PrivateKey(der []byte) (key interface{}, err error)
ParsePKCS8PrivateKey对PKCS#8格式DER编码的私钥进行解析func MarshalPKCS8PrivateKey(key interface{}) ([]byte, error)
MarshalPKCS8PrivateKey将key私钥序列化为PKCS#8编码格式,key支持*rsa.PrivateKey,*ecdsa.PublicKey
。
五、pem
1、pem简介
pem实现了PEM数据编码,PEM编码源于 Privacy Enhanced Mail。目前最常见的PEM编码用在TLS密钥和证书中。
2、pem常用方法
type Block struct {
Type string // The type, taken from the preamble (i.e. "RSA PRIVATE KEY").
Headers map[string]string // Optional headers.
Bytes []byte // The decoded bytes of the contents. Typically a DER encoded ASN.1 structure.
}
Block类型表示PEM编码结构,编码格式如下:
-----BEGIN Type-----
Headers
base64-encoded Bytes
-----END Type-----
func Decode(data []byte) (p *Block, rest []byte)
Decode将在data输入中找到下一个PEM格式化的Block(证书,私钥等)。返回Block和输入的其余部分。如果没有找到PEM数据,则p为nil,并且整个输入在rest返回。func Encode(out io.Writer, b *Block) error
Encode将b进行编码,并写入outfunc EncodeToMemory(b *Block) []byte
将b进行编码并返回编码结果
3、pem示例
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"os"
)
//生成RSA私钥和公钥,保存到文件中
func GenerateRSAKeyPair(bits int, private string, public string) {
//GenerateKey函数使用随机数据生成器random生成一对具有指定字位数的RSA密钥
//Reader是一个全局、共享的密码用强随机数生成器
privateKey, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
panic(err)
}
//保存私钥
//通过x509标准将得到的RSA私钥序列化为ASN.1 的 DER编码字符串
X509PrivateKey := x509.MarshalPKCS1PrivateKey(privateKey)
//使用pem格式对x509输出的内容进行编码
//创建文件保存私钥
privateFile, err := os.Create(private)
if err != nil {
panic(err)
}
defer privateFile.Close()
//构建一个pem.Block结构体对象
privateBlock := pem.Block{Type: "RSA Private Key", Bytes: X509PrivateKey}
//将数据保存到文件
pem.Encode(privateFile, &privateBlock)
//保存公钥
//获取公钥的数据
publicKey := privateKey.PublicKey
//X509对公钥编码
X509PublicKey, err := x509.MarshalPKIXPublicKey(&publicKey)
if err != nil {
panic(err)
}
//pem格式编码
//创建用于保存公钥的文件
publicFile, err := os.Create(public)
if err != nil {
panic(err)
}
defer publicFile.Close()
//创建一个pem.Block结构体对象
publicBlock := pem.Block{Type: "RSA Public Key", Bytes: X509PublicKey}
//保存到文件
pem.Encode(publicFile, &publicBlock)
}
func getPrivateKeyLength(private string) (int, error) {
privateKey, err := ioutil.ReadFile(private)
if err != nil {
fmt.Println(err)
}
block, _ := pem.Decode(privateKey)
if block == nil {
return 0, errors.New("Private RSA Key error")
}
priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
fmt.Println(err)
}
return priv.N.BitLen(), nil
}
func getPublicKeyLength(public string) (int, error) {
publicKey, err := ioutil.ReadFile(public)
if err != nil {
fmt.Println(err)
}
block, _ := pem.Decode(publicKey)
if block == nil {
return 0, errors.New("Public RSA Key error")
}
pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return 0, err
}
pub := pubInterface.(*rsa.PublicKey)
return pub.N.BitLen(), nil
}
func main() {
GenerateRSAKeyPair(2048, "private.pem", "public.pem")
n, _ := getPrivateKeyLength("private.pem")
fmt.Println(n)
n, _ = getPublicKeyLength("public.pem")
fmt.Println(n)
}
六、rsa
1、rsa简介
1977年,Ron Rivest、Adi Shamir、Leonard Adleman三人在美国公布了一种公钥加密算法,即RSA公钥加密算法。RSA是目前最有影响力和最常用的公钥加密算法,可用于数据加密和数字签名。
OpenSSL生成私钥:openssl genrsa -out private.pem 1024
OpenSSL生成公钥:openssl rsa -in private.pem -pubout -out public.pem
RSA私钥中包含公钥,私钥的数据结构如下:
type PrivateKey struct {
PublicKey // public part.
D *big.Int // private exponent
Primes []*big.Int // prime factors of N, has >= 2 elements.
// Precomputed contains precomputed values that speed up private
// operations, if available.
Precomputed PrecomputedValues
}
2、rsa常用方法
func EncryptOAEP(hash hash.Hash, random io.Reader, pub *PublicKey, msg []byte, label []byte) ([]byte, error)
使用RSA-OAEP算法对信息进行加密func DecryptOAEP(hash hash.Hash, random io.Reader, priv *PrivateKey, ciphertext []byte, label []byte) ([]byte, error)
使用RSA-OAEP算法对信息进行解密func GenerateKey(random io.Reader, bits int) (*PrivateKey, error)
生成私钥func (priv *PrivateKey) Public() crypto.PublicKey
根据私钥获取公钥func (priv *PrivateKey) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error)
生成私钥的签名func (priv *PrivateKey) Decrypt(rand io.Reader, ciphertext []byte, opts crypto.DecrypterOpts) (plaintext []byte, err error)
利用私钥对加密信息进行解密
3、rsa加解密示例
RSA用于数据加密时,消息发送方利用对方的公钥进行加密,消息接受方收到密文时使用自己的私钥进行解密。
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"os"
)
//生成RSA私钥和公钥,保存到文件中
func GenerateRSAKeyPair(bits int, private string, public string) {
//GenerateKey函数使用随机数据生成器random生成一对具有指定字位数的RSA密钥
//Reader是一个全局、共享的密码用强随机数生成器
privateKey, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
panic(err)
}
//保存私钥
//通过x509标准将得到的ras私钥序列化为ASN.1 的 DER编码字符串
X509PrivateKey := x509.MarshalPKCS1PrivateKey(privateKey)
//使用pem格式对x509输出的内容进行编码
//创建文件保存私钥
privateFile, err := os.Create(private)
if err != nil {
panic(err)
}
defer privateFile.Close()
//构建一个pem.Block结构体对象
privateBlock := pem.Block{Type: "RSA Private Key", Bytes: X509PrivateKey}
//将数据保存到文件
pem.Encode(privateFile, &privateBlock)
//保存公钥
//获取公钥的数据
publicKey := privateKey.PublicKey
//X509对公钥编码
X509PublicKey, err := x509.MarshalPKIXPublicKey(&publicKey)
if err != nil {
panic(err)
}
//pem格式编码
//创建用于保存公钥的文件
publicFile, err := os.Create(public)
if err != nil {
panic(err)
}
defer publicFile.Close()
//创建一个pem.Block结构体对象
publicBlock := pem.Block{Type: "RSA Public Key", Bytes: X509PublicKey}
//保存到文件
pem.Encode(publicFile, &publicBlock)
}
//RSA加密
func RSAEncrypt(plainText []byte, path string) []byte {
//打开文件
file, err := os.Open(path)
if err != nil {
panic(err)
}
defer file.Close()
//读取文件的内容
info, _ := file.Stat()
buf := make([]byte, info.Size())
file.Read(buf)
//pem解码
block, _ := pem.Decode(buf)
//x509解码
publicKeyInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
panic(err)
}
//类型断言
publicKey := publicKeyInterface.(*rsa.PublicKey)
//对明文进行加密
cipherText, err := rsa.EncryptPKCS1v15(rand.Reader, publicKey, plainText)
if err != nil {
panic(err)
}
//返回密文
return cipherText
}
//RSA解密
func RSADecrypt(cipherText []byte, path string) []byte {
//打开文件
file, err := os.Open(path)
if err != nil {
panic(err)
}
defer file.Close()
//获取文件内容
info, _ := file.Stat()
buf := make([]byte, info.Size())
file.Read(buf)
//pem解码
block, _ := pem.Decode(buf)
//X509解码
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
panic(err)
}
//对密文进行解密
plainText, _ := rsa.DecryptPKCS1v15(rand.Reader, privateKey, cipherText)
//返回明文
return plainText
}
func main() {
//生成密钥对,保存到文件
GenerateRSAKeyPair(2048, "private.pem", "public.pem")
message := []byte("hello world")
//加密
cipherText := RSAEncrypt(message, "public.pem")
fmt.Println("Encrypt:", cipherText)
//解密
plainText := RSADecrypt(cipherText, "private.pem")
fmt.Println("DeEncrypt:", string(plainText))
}
4、rsa数字签名示例
RSA在用于数字签名时,发送方通常先对消息生成散列值,再利用私钥对散列值进行签名,接收方收到消息及签名时,也先对消息生成散列值(与发送方使用同种单向散列函数),利用发送方发的公钥、签名以及自己生成的散列值进行签名验证。
package main
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"fmt"
"os"
)
//生成RSA私钥和公钥,保存到文件中
func GenerateRSAKey(bits int, private string, public string) {
//GenerateKey函数使用随机数据生成器random生成一对具有指定字位数的RSA密钥
//Reader是一个全局、共享的密码用强随机数生成器
privateKey, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
panic(err)
}
//保存私钥
//通过x509标准将得到的ras私钥序列化为ASN.1 的 DER编码字符串
X509PrivateKey := x509.MarshalPKCS1PrivateKey(privateKey)
//使用pem格式对x509输出的内容进行编码
//创建文件保存私钥
privateFile, err := os.Create(private)
if err != nil {
panic(err)
}
defer privateFile.Close()
//构建一个pem.Block结构体对象
privateBlock := pem.Block{Type: "RSA Private Key", Bytes: X509PrivateKey}
//将数据保存到文件
pem.Encode(privateFile, &privateBlock)
//保存公钥
//获取公钥的数据
publicKey := privateKey.PublicKey
//X509对公钥编码
X509PublicKey, err := x509.MarshalPKIXPublicKey(&publicKey)
if err != nil {
panic(err)
}
//pem格式编码
//创建用于保存公钥的文件
publicFile, err := os.Create(public)
if err != nil {
panic(err)
}
defer publicFile.Close()
//创建一个pem.Block结构体对象
publicBlock := pem.Block{Type: "RSA Public Key", Bytes: X509PublicKey}
//保存到文件
pem.Encode(publicFile, &publicBlock)
}
//读取RSA私钥
func GetRSAPrivateKey(path string) *rsa.PrivateKey {
//读取文件内容
file, err := os.Open(path)
if err != nil {
panic(err)
}
defer file.Close()
info, _ := file.Stat()
buf := make([]byte, info.Size())
file.Read(buf)
//pem解码
block, _ := pem.Decode(buf)
//X509解码
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
return privateKey
}
//读取RSA公钥
func GetRSAPublicKey(path string) *rsa.PublicKey {
//读取公钥内容
file, err := os.Open(path)
if err != nil {
panic(err)
}
defer file.Close()
info, _ := file.Stat()
buf := make([]byte, info.Size())
file.Read(buf)
//pem解码
block, _ := pem.Decode(buf)
//x509解码
publicKeyInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
panic(err)
}
publicKey := publicKeyInterface.(*rsa.PublicKey)
return publicKey
}
//对消息的散列值进行数字签名
func GetSign(msg []byte, path string) []byte {
//取得私钥
privateKey := GetRSAPrivateKey(path)
//计算散列值
hash := sha256.New()
hash.Write(msg)
bytes := hash.Sum(nil)
//SignPKCS1v15使用RSA PKCS#1 v1.5规定的RSASSA-PKCS1-V1_5-SIGN签名方案计算签名
sign, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, bytes)
if err != nil {
panic(sign)
}
return sign
}
//验证数字签名
func VerifySign(msg []byte, sign []byte, path string) bool {
//取得公钥
publicKey := GetRSAPublicKey(path)
//计算消息散列值
hash := sha256.New()
hash.Write(msg)
bytes := hash.Sum(nil)
//验证数字签名
err := rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, bytes, sign)
return err == nil
}
//测试RSA数字签名
func main() {
//生成密钥文件
GenerateRSAKey(2048, "private.pem", "public.pem")
//模拟发送方
//要发送的消息
msg := []byte("hello world")
//生成签名
sign := GetSign(msg, "private.pem")
//模拟接收方
//接受到的消息
acceptmsg := []byte("hello world")
//接受到的签名
acceptsign := sign
//验证签名
ok := VerifySign(acceptmsg, acceptsign, "public.pem")
if ok {
fmt.Println("Signature is accepted")
} else {
fmt.Println("Signature is not accepted")
}
}
七、aes
1、aes简介
AES(Advanced Encryption Standard),即高级加密标准,是DES的替代标准。AES加密算法经历了公开选拔,最终在2000年由比利时密码学家Joan Daemen和Vincent Rijmen设计的Rijndael算法被选中,成为AES标准。
AES算法基于排列和置换运算。排列是对数据重新进行安排,置换是将一个数据单元替换为另一个。AES 使用几种不同的方法来执行排列和置换运算。 AES是一个迭代的、对称密钥分组的密码,可以使用128、192和256位密钥,并且用128位(16字节)分组加密和解密数据。
AES是目前比较流行的对称加密算法,是一种分组密码(block cipher)算法,AES的分组长度为128比特(16字节),而密钥长度可以是128比特、192比特或256比特。
2、aes常用方法
func NewCipher(key []byte) (cipher.Block, error)
NewCiphe r创建并返回一个新的cipher.Block。参数是AES密钥,可以是AES-128,AES-192或AES-256。func cipher.NewCBCEncrypter(b Block, iv []byte) BlockMode
NewCBCEncrypter返回一个BlockMode,使用给定的Block以密码块链接模式加密。iv的长度必须与块的块大小相同。func (x *cbcEncrypter) CryptBlocks(dst, src []byte)
对分组进行加密,dst为输出参数,src为填充后的分组func cipher.NewCBCDecrypter(b Block, iv []byte) BlockMode
NewCBCDecrypter返回一个BlockMode,使用给定的Block以密码块链接模式解密。iv的长度必须与Block的块大小相同,并且必须与用于加密数据的iv相匹配。func (x *cbcDecrypter) CryptBlocks(dst, src []byte)
堆分组进行解密,dst为输出参数,src为密文,解密后dst需要删除填充才能得到明文。func cipher.NewCTR(block Block, iv []byte) Stream
NewCTR返回一个Stream,使用计数器模式下的给定Block加密/解密。iv的长度必须与块的块大小相同。
type Stream interface {
XORKeyStream(dst, src []byte)
}
func (x *ctr) XORKeyStream(dst, src []byte)
加解密,src为输入参数,可以为要加密的明文或要解密的密文,dst为输出参数。
3、CBC分组模式
CBC模式(密码分组链接模式)是常用的一种分组密码的模式。
CBC模式中的第一个分组需要用初始化向量IV(一个随机的且长度为一个分组长度的比特序列)进行异或操作再进行加密,而后面的每一个分组都要先和前一个分组加密后的密文分组进行异或操作,然后再加密。加密是连续的,不能进行并行操作。
在CBC模式中,每个明文块先与前一个密文块进行异或后,再进行加密。因此,每个密文块都依赖于它前面的所有明文块。同时,为了保证每条消息的唯一性,在第一个块中需要使用初始化向量。
CBC是最为常用的工作模式,主要缺点在于加密过程是串行的,无法被并行化,而且消息必须被填充到块大小的整数倍。解决后一个问题的一种方法是利用密文窃取。
在加密时,明文中的微小改变会导致其后的全部密文块发生改变,而在解密时,从两个邻接的密文块中即可得到一个明文块。因此,解密过程可以被并行化,而解密时,密文中一位的改变只会导致其对应的明文块完全改变和下一个明文块中对应位发生改变,不会影响到其它明文的内容。
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"fmt"
)
//对明文进行填充
func Padding(plainText []byte, blockSize int) []byte {
//计算要填充的长度
n := blockSize - len(plainText)%blockSize
//对原来的明文填充n个n
temp := bytes.Repeat([]byte{byte(n)}, n)
plainText = append(plainText, temp...)
return plainText
}
//对密文删除填充
func UnPadding(cipherText []byte) []byte {
//取出密文最后一个字节end
end := cipherText[len(cipherText)-1]
//删除填充
cipherText = cipherText[:len(cipherText)-int(end)]
return cipherText
}
//AEC加密(CBC模式)
func AESCBCEncrypt(rawData []byte, key []byte, iv []byte) []byte {
//指定加密算法,返回一个AES算法的Block接口对象
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
//进行填充
rawData = Padding(rawData, block.BlockSize())
//指定分组模式,返回一个BlockMode接口对象
blockMode := cipher.NewCBCEncrypter(block, iv)
//加密连续数据库
cipherText := make([]byte, len(rawData))
blockMode.CryptBlocks(cipherText, rawData)
//返回密文
return cipherText
}
//AEC解密(CBC模式)
func AESCBCDecrypt(cipherText []byte, key []byte, iv []byte) []byte {
//指定解密算法,返回一个AES算法的Block接口对象
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
//指定分组模式,返回一个BlockMode接口对象
blockMode := cipher.NewCBCDecrypter(block, iv)
//解密
plainText := make([]byte, len(cipherText))
blockMode.CryptBlocks(plainText, cipherText)
//删除填充
plainText = UnPadding(plainText)
return plainText
}
func main() {
message := []byte("Hello go!")
//指定密钥AES-128,16byte
key := []byte("1111111111111111")
//指定初始向量IV,长度和block的块尺寸一致
iv := []byte("12345678abcdefgh")
//加密
cipherText := AESCBCEncrypt(message, key, iv)
fmt.Println("Encrypt: ", cipherText)
//解密
plainText := AESCBCDecrypt(cipherText, key, iv)
fmt.Println("Decrypt:", string(plainText))
}
4、CTR分组模式
CTR分组模式每次加密时都会生成一个不同的值来作为计数器的初始值,每个分组对应一个逐次累加的计数器,通过对计数器进行加密来生成密钥流,再将密钥流与明文分组进行异或操作得到密文分组。
package main
import (
"crypto/aes"
"crypto/cipher"
"fmt"
)
//AEC加密和解密(CRT模式)
func AECCTRCrypt(text []byte, key []byte, counter []byte) []byte {
//指定加密、解密算法为AES,返回一个AES的Block接口对象
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
//指定分组模式
blockMode := cipher.NewCTR(block, counter)
//执行加密、解密操作
message := make([]byte, len(text))
blockMode.XORKeyStream(message, text)
//返回明文或密文
return message
}
func main() {
message := []byte("Hello go")
//指定密钥AES-128,16byte
key := []byte("1234567812345678")
//指定计数器,长度必须等于block的块尺寸
counter := []byte("12345678abcdefgh")
//加密
cipherText := AECCTRCrypt(message, key, counter)
fmt.Println("Encrypt: ", cipherText)
//解密
plainText := AECCTRCrypt(cipherText, key, counter)
fmt.Println("Decrypt: ", string(plainText))
}
八、hmac
1、hmac简介
hmac 包实现了美国联邦信息处理标准出版物198中定义的HMAC(Keyed-Hash Message Authentication Code),即加密哈希认证码。HMAC 是使用密钥签署消息的加密散列。接收器通过使用相同的密钥重新计算它来验证散列。
2、hmac常用方法
func New(h func() hash.Hash, key []byte) hash.Hash
New函数返回一个采用hash.Hash作为底层hash接口、key作为密钥的HMAC算法的hash接口。func Equal(mac1, mac2 []byte) bool
比较两个MAC是否相同,而不会泄露对比时间信息。(以规避时间侧信道***;指通过计算比较时花费的时间的长短来获取密码的信息,用于密码破解)
func (h *hmac) Sum(in []byte) []byte
func (h *hmac) Write(p []byte) (n int, err error)
hash.Hash继承了io.Writer,因此可以将其当成一个输入流进行内容的更新。
type Writer interface {
Write(p []byte) (n int, err error)
}
Write方法将p中的内容读入后存入到hash.Hash,最后在Sum方法通过内部函数checkSum计算出其校验和。
Sum函数是对hash.Hash对象内部存储的内容进行校验和计算然后将其追加到data的后面形成一个新的byte切片。通常将data置为nil。
Sum方法返回一个Size大小的byte数组,对于MD5返回一个128bit的16字节byte数组。
3、hmac示例
package main
import (
"crypto/hmac"
"crypto/sha256"
"fmt"
"io"
)
func generateHMAC(rawData, key string) string {
h := hmac.New(sha256.New, []byte(key))
io.WriteString(h, rawData)
return fmt.Sprintf("%x", h.Sum(nil))
}
func generateSHA256(s string) string {
h := sha256.New()
h.Write([]byte(s))
return fmt.Sprintf("%x", h.Sum(nil))
}
func main() {
data := "Hello go"
key := "123456"
str := generateHMAC(data, key)
fmt.Println(str)
str = generateSHA256(data)
fmt.Println(str)
}
// output:
// 143543e2c55feb51051dccc99687c6ad215e0dca37127ecce67efc92b1d7f697
// 61c35d7993613995b32d1c5bd6d3bbdbbcf3305bb19a135b99a779a8c782d0f7
九、ecdsa
1、ecdsa简介
ECDSA(Elliptic Curve Digital Signature Algorithm)即椭圆曲线数字签名算法。
ECC(Elliptic Curve Cryptography),即椭圆曲线加密算法,是基于椭圆曲线数学理论实现的一种非对称加密算法。与RSA算法相比,ECC可以使用更短的密钥,来实现与RSA相当或更高的安全。据研究,160位ECC加密安全性相当于1024位RSA加密,210位ECC加密安全性相当于2048位RSA加密。
Go语言的ecdsa包目前智能用于数字签名,使用私钥加密,使用公钥做校验。
2、ecdsa常用方法
type PublicKey struct {
elliptic.Curve
X, Y *big.Int
}
type PrivateKey struct {
PublicKey
D *big.Int
}
ecdsa包的私钥数据结构中包含公钥。func GenerateKey(c elliptic.Curve, rand io.Reader) (*PrivateKey, error)
生成ECDSA密钥对,可用的椭圆曲线为elliptic.P224()、elliptic.P256()、elliptic.P384()、elliptic.P521()func (priv *PrivateKey) Public() crypto.PublicKey
从私钥获取公钥func (priv *PrivateKey) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error)
从私钥生成签名func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err error)
使用私钥生成签名。签名使用私钥priv来签名散列。如果散列长度大于私钥的曲线顺序的位长度,则散列将被截断为该长度。将签名作为一对整数返回。私钥的安全性取决于rand的熵。func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool
利用公钥进行签名认证
3、ecdsa示例
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha1"
"crypto/x509"
"encoding/pem"
"fmt"
"math/big"
"os"
)
//生成密钥对
func generateECCKeyPair(c elliptic.Curve, privateKeyFile string, publicKeyFile string) error {
//使用ECDSA生成密钥对
privateKey, err := ecdsa.GenerateKey(c, rand.Reader)
if err != nil {
return err
}
//使用509
private, err := x509.MarshalECPrivateKey(privateKey)
if err != nil {
return err
}
//pem
block := pem.Block{
Type: "esdsa private key",
Bytes: private,
}
file, err := os.Create(privateKeyFile)
if err != nil {
return err
}
err = pem.Encode(file, &block)
if err != nil {
return err
}
file.Close()
//处理公钥
public := privateKey.PublicKey
//x509序列化
publicKey, err := x509.MarshalPKIXPublicKey(&public)
if err != nil {
return err
}
//pem
public_block := pem.Block{
Type: "ecdsa public key",
Bytes: publicKey,
}
file, err = os.Create(publicKeyFile)
if err != nil {
return err
}
//pem编码
err = pem.Encode(file, &public_block)
if err != nil {
return err
}
return nil
}
//ECC签名--私钥
func ECCSignature(sourceData []byte, privateKeyFile string) ([]byte, []byte) {
//读取私钥
file, err := os.Open(privateKeyFile)
if err != nil {
panic(err)
}
info, err := file.Stat()
buf := make([]byte, info.Size())
file.Read(buf)
//pem解密
block, _ := pem.Decode(buf)
//x509解密
privateKey, err := x509.ParseECPrivateKey(block.Bytes)
if err != nil {
panic(err)
}
//哈希运算
hashText := sha1.Sum(sourceData)
//数字签名
r, s, err := ecdsa.Sign(rand.Reader, privateKey, hashText[:])
if err != nil {
panic(err)
}
rText, err := r.MarshalText()
if err != nil {
panic(err)
}
sText, err := s.MarshalText()
if err != nil {
panic(err)
}
defer file.Close()
return rText, sText
}
//ECC认证
func ECCVerify(rText, sText, sourceData []byte, publicKeyFilePath string) bool {
//读取公钥文件
file, err := os.Open(publicKeyFilePath)
if err != nil {
panic(err)
}
info, err := file.Stat()
if err != nil {
panic(err)
}
buf := make([]byte, info.Size())
file.Read(buf)
//pem解码
block, _ := pem.Decode(buf)
//x509
publicStream, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
panic(err)
}
//接口转换成公钥
publicKey := publicStream.(*ecdsa.PublicKey)
hashText := sha1.Sum(sourceData)
var r, s big.Int
r.UnmarshalText(rText)
s.UnmarshalText(sText)
//认证
res := ecdsa.Verify(publicKey, hashText[:], &r, &s)
defer file.Close()
return res
}
func main() {
generateECCKeyPair(elliptic.P256(), "./private.pem", "./public.pem")
rawData := []byte("Hello go")
r, s := ECCSignature(rawData, "./private.pem")
res := ECCVerify(r, s, rawData, "./public.pem")
if res {
fmt.Println("Signature is Accepted")
}
}
有疑问加站长微信联系(非本文作者)