密码学及golang实现
本文目的仅为各种加密算法,在日常使用中的调用操作,快速定位所在包。
具体的加密底层数学原理,还需查阅研究底层源码。
Hash哈希家族
Hash散列
作用:大大提高查询遍历时的效率
底层结构:一个数组,数组中每个元素中都存储的是一个链表。
记忆:拉链法、链表的数组
Java1.7对比:在java中HashMap中,数组中链表每次插入新节点时,是头插法,插入到最面!为什么这么做呢?(因为每次插入到最前面可以直接插入,而如果插入到最后面时,则还需要进行循环遍历!所以插入到最前面提高效率)
md5加密
package main
//包
import(
"fmt"
"crypto/md5"
"encoding/hex"
)
func main(){
data := []byte("hello world")
s := fmt.Sprintf("%x", md5.Sum(data))
fmt.Println(s)
// 也可以用这种方式
h := md5.New()
h.Write(data)
s = hex.EncodeToString(h.Sum(nil))
fmt.Println(s)
}
Sha256加密
package main
import(
"fmt"
"crypto/sha256"
"io"
"log"
"os"
)
func main() {
// 第一种调用方法
sum := sha256.Sum256([]byte("hello world\n"))
fmt.Printf("%x\n", sum)
// 第二种调用方法
h := sha256.New()
h.Write([]byte("hello world\n"))
fmt.Printf("%x\n", h.Sum(nil))
}
Sha256文件哈希值
// 对文件加密
f, err := os.Open("test.txt")
if err != nil {
log.Fatal(err)
}
defer f.Close()
h = sha256.New()
if _, err := io.Copy(h, f); err != nil {
log.Fatal(err)
}
fmt.Printf("%x\n", h.Sum(nil))
Ripemd160加密
package main
import (
"fmt"
"golang.org/x/crypto/ripemd160"
)
func main() {
hasher := ripemd160.New()
hasher.Write([]byte("The quick brown fox jumps over the lazy dog"))
hashBytes := hasher.Sum(nil)
hashString := fmt.Sprintf("%x", hashBytes)
fmt.Println(hashString)
}
对称加密和解密
模式、初始向量、填充
-
加密模式
- ECB模式 全称Electronic Codebook模式,译为电子密码本模式
- CBC模式 全称Cipher Block Chaining模式,译为密文分组链接模式
- CFB模式 全称Cipher FeedBack模式,译为密文反馈模式
- OFB模式 全称Output Feedback模式,译为输出反馈模式。
- CTR模式 全称Counter模式,译为计数器模式。
-
初始向量
当加密第一个明文分组时,由于不存在 “前一个密文分组”,因此需要事先准备一个长度为一个分组的比特序列来代替 “前一个密文分组”,这个比特序列称为初始化向量(InitializationVector),通常缩写为 IV。
-
填充方式
当明文长度不为分组长度的整数倍时,需要在最后一个分组中填充一些数据使其凑满一个分组长度。
-
NoPadding
API或算法本身不对数据进行处理,加密数据由加密双方约定填补算法。例如若对字符串数据进行加解密,可以补充\0或者空格,然后trim
-
PKCS5Padding
加密前:数据字节长度对8取余,余数为m,若m>0,则补足8-m个字节,字节数值为8-m,即差几个字节就补几个字节,字节数值即为补充的字节数,若为0则补充8个字节的8
解密后:取最后一个字节,值为m,则从数据尾部删除m个字节,剩余数据即为加密前的原文。
加密字符串为为AAA,则补位为AAA55555;加密字符串为BBBBBB,则补位为BBBBBB22;加密字符串为CCCCCCCC,则补位为CCCCCCCC88888888。
-
PKCS7Padding
PKCS7Padding 的填充方式和PKCS5Padding 填充方式一样。只是加密块的字节数不同。PKCS5Padding明确定义了加密块是8字节,PKCS7Padding加密快可以是1-255之间。
-
Des
key密钥长度8Byte
des加密
使用DES加密 (des.NewCipher)
,加密模式为CBC (cipher.NewCBCEncrypter(block, key))
,填充方式 PKCS5Padding
密钥是8byte
import (
"bytes"
"crypto/cipher"
"crypto/des"
"encoding/base64"
"fmt"
)
//加密函数:
func DesEncrypt(origData, key []byte) ([]byte, error) {
block, err := des.NewCipher(key)
if err != nil {
return nil, err
}
origData = PKCS5Padding(origData, block.BlockSize())
blockMode := cipher.NewCBCEncrypter(block, key)
crypted := make([]byte, len(origData))
blockMode.CryptBlocks(crypted, origData)
return crypted, nil
}
des解密
//解密函数:
func DesDecrypt(crypted, key []byte) ([]byte, error) {
block, err := des.NewCipher(key)
if err != nil {
return nil, err
}
blockMode := cipher.NewCBCDecrypter(block, key)
origData := make([]byte, len(crypted))
blockMode.CryptBlocks(origData, crypted)
origData = PKCS5UnPadding(origData)
return origData, nil
}
3DES
Key密钥长度24Byte
对比DES,发现只是换了NewTripleDESCipher。不过,需要注意的是,密钥长度必须24byte,否则直接返回错误。
3des加密
//3DES加密
func TripleDesEncrypt(origData, key []byte) ([]byte, error) {
block, err := des.NewTripleDESCipher(key)
if err != nil {
return nil, err
}
origData = PKCS5Padding(origData, block.BlockSize())
blockMode := cipher.NewCBCEncrypter(block, key[:8])
crypted := make([]byte, len(origData))
blockMode.CryptBlocks(crypted, origData)
return crypted, nil
}
3des解密
// 3DES解密
func TripleDesDecrypt(crypted, key []byte) ([]byte, error) {
block, err := des.NewTripleDESCipher(key)
if err != nil {
return nil, err
}
blockMode := cipher.NewCBCDecrypter(block, key[:8])
origData := make([]byte, len(crypted))
blockMode.CryptBlocks(origData, crypted)
origData = PKCS5UnPadding(origData)
return origData, nil
}
AES
AES密钥长度规定16Byte、24Byte、32Byte:
引自golang源码注释
// NewCipher creates and returns a new cipher.Block.
// The key argument should be the AES key,
// either 16, 24, or 32 bytes to select
// AES-128, AES-192, or AES-256.
//golang源码
func NewCipher(key []byte) (cipher.Block, error) {
k := len(key)
switch k {
default:
return nil, KeySizeError(k)
case 16, 24, 32:
break
}
return newCipher(key)
}
AES加密
使用CBC模式+PKCS7 填充方式实现AES的加密和解密
import (
"bytes"
"crypto/cipher"
"crypto/aes"
"encoding/base64"
"fmt"
)
//aes加密
func AesEncrypt(origData, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockSize := block.BlockSize()
origData = PKCS7Padding(origData, blockSize)
blockMode := cipher.NewCBCEncrypter(block, key[:blockSize])
crypted := make([]byte, len(origData))
blockMode.CryptBlocks(crypted, origData)
return crypted, nil
}
AES解密
//AES解密
func AesDecrypt(crypted, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockSize := block.BlockSize()
blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
origData := make([]byte, len(crypted))
blockMode.CryptBlocks(origData, crypted)
origData = PKCS7UnPadding(origData)
return origData, nil
}
PKCS5Padding填充方式实现
-
PKCS5Padding
加密前:数据字节长度对8取余,余数为m,若m>0,则补足8-m个字节,字节数值为8-m,即差几个字节就补几个字节,字节数值即为补充的字节数,若为0则补充8个字节的8解密后:取最后一个字节,值为m,则从数据尾部删除m个字节,剩余数据即为加密前的原文。
例:加密字符串为为AAA,则补位为AAA55555;加密字符串为BBBBBB,则补位为BBBBBB22;加密字符串为CCCCCCCC,则补位为CCCCCCCC88888888。
//PKCS5Padding填充方式
func PKCS5Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
func PKCS5UnPadding(origData []byte) []byte {
length := len(origData)
// 去掉最后一个字节 unpadding 次
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}
PKCS7Padding填充方式实现
- PKCS7Padding
PKCS7Padding 的填充方式和PKCS5Padding 填充方式一样。只是加密块的字节数不同。PKCS5Padding明确定义了加密块是8字节,PKCS7Padding加密块可以是1-255之间。
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext) % blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
func PKCS7UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}
非对称加密和解密
RSA加密和解密
openssl生成私钥
openssl genrsa -out rsa_private_key.pem 1024
openssl生成公钥
openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
实现 RSA 加密解密
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"errors"
"fmt"
)
// 可通过openssl产生
//openssl genrsa -out rsa_private_key.pem 1024
var privateKey = []byte(`
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDfw1/P15GQzGGYvNwVmXIGGxea8Pb2wJcF7ZW7tmFdLSjOItn9
kvUsbQgS5yxx+f2sAv1ocxbPTsFdRc6yUTJdeQolDOkEzNP0B8XKm+Lxy4giwwR5
LJQTANkqe4w/d9u129bRhTu/SUzSUIr65zZ/s6TUGQD6QzKY1Y8xS+FoQQIDAQAB
AoGAbSNg7wHomORm0dWDzvEpwTqjl8nh2tZyksyf1I+PC6BEH8613k04UfPYFUg1
0F2rUaOfr7s6q+BwxaqPtz+NPUotMjeVrEmmYM4rrYkrnd0lRiAxmkQUBlLrCBiF
u+bluDkHXF7+TUfJm4AZAvbtR2wO5DUAOZ244FfJueYyZHECQQD+V5/WrgKkBlYy
XhioQBXff7TLCrmMlUziJcQ295kIn8n1GaKzunJkhreoMbiRe0hpIIgPYb9E57tT
/mP/MoYtAkEA4Ti6XiOXgxzV5gcB+fhJyb8PJCVkgP2wg0OQp2DKPp+5xsmRuUXv
720oExv92jv6X65x631VGjDmfJNb99wq5QJBAMSHUKrBqqizfMdOjh7z5fLc6wY5
M0a91rqoFAWlLErNrXAGbwIRf3LN5fvA76z6ZelViczY6sKDjOxKFVqL38ECQG0S
pxdOT2M9BM45GJjxyPJ+qBuOTGU391Mq1pRpCKlZe4QtPHioyTGAAMd4Z/FX2MKb
3in48c0UX5t3VjPsmY0CQQCc1jmEoB83JmTHYByvDpc8kzsD8+GmiPVrausrjj4p
y2DQpGmUic2zqCxl6qXMpBGtFEhrUbKhOiVOJbRNGvWW
-----END RSA PRIVATE KEY-----
`)
//openssl
//openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
var publicKey = []byte(`
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDfw1/P15GQzGGYvNwVmXIGGxea
8Pb2wJcF7ZW7tmFdLSjOItn9kvUsbQgS5yxx+f2sAv1ocxbPTsFdRc6yUTJdeQol
DOkEzNP0B8XKm+Lxy4giwwR5LJQTANkqe4w/d9u129bRhTu/SUzSUIr65zZ/s6TU
GQD6QzKY1Y8xS+FoQQIDAQAB
-----END PUBLIC KEY-----
`)
// 加密
func RsaEncrypt(origData []byte) ([]byte, error) {
//解密pem格式的公钥
block, _ := pem.Decode(publicKey)
if block == nil {
return nil, errors.New("public key error")
}
// 解析公钥
pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
// 类型断言
pub := pubInterface.(*rsa.PublicKey)
//加密
return rsa.EncryptPKCS1v15(rand.Reader, pub, origData)
}
// 解密
func RsaDecrypt(ciphertext []byte) ([]byte, error) {
//解密
block, _ := pem.Decode(privateKey)
if block == nil {
return nil, errors.New("private key error!")
}
//解析PKCS1格式的私钥
priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
// 解密
return rsa.DecryptPKCS1v15(rand.Reader, priv, ciphertext)
}
func main() {
data, _ := RsaEncrypt([]byte("hello world"))
fmt.Println(base64.StdEncoding.EncodeToString(data))
origData, _ := RsaDecrypt(data)
fmt.Println(string(origData))
}
数字签名算法
dsa
数字签名算法(DSA,Digital Signature Algorithm),是一种公开密钥算法,不能用于加密,只能用于数字签名。主要用作为接收者验证数字的完整性和数据发送者的身份,DSA算法的安全性基于解离散对数的困难性。
package main
import (
"crypto/dsa"
"crypto/rand"
"fmt"
)
func main() {
var params dsa.Parameters
//生成参数
if e := dsa.GenerateParameters(¶ms, rand.Reader, dsa.L1024N160); e != nil {
fmt.Println(e)
}
//生成私钥
var priv dsa.PrivateKey
priv.Parameters = params
if e := dsa.GenerateKey(&priv, rand.Reader); e != nil {
fmt.Println(e)
}
//根据私钥生成公钥
pub := priv.PublicKey
//消息
message := []byte("hello world")
//使用私钥进行签名,产生整数对(r,s)
r, s, e := dsa.Sign(rand.Reader, &priv, message)
if e != nil {
fmt.Println(e)
}
//认证
fmt.Printf("认证 %q (r:%s,s:%s)\n", message, r, s)
if dsa.Verify(&pub, message, r, s) {
fmt.Println("认证正确!")
} else {
fmt.Println("认证失败!")
}
}
椭圆曲线Dsa加密算法
椭圆曲线加密算法
ECDSA的全名是Elliptic Curve DSA,即椭圆曲线DSA。它是Digital Signature Algorithm (DSA)应用了椭圆曲线加密算法的变种。椭圆曲线算法的原理很复杂,但是具有很好的公开密钥算法特性,通过公钥无法逆向获得私钥。
-
签名过程
假设要签名的消息是一个字符串:“Hello World!”。DSA签名的第一个步骤是对待签名的消息生成一个消息摘要。不同的签名算法使用不同的消息摘要算法。而ECDSA256使用SHA256生成256比特的摘要。
摘要生成结束后,应用签名算法对摘要进行签名:
产生一个随机数k
利用随机数k,计算出两个大数r和s。将r和s拼在一起就构成了对消息摘要的签名。
这里需要注意的是,因为随机数k的存在,对于同一条消息,使用同一个算法,产生的签名是不一样的。从函数的角度来理解,签名函数对同样的输入会产生不同的输出。因为函数内部会将随机值混入签名的过程。 -
验证过程
关于验证过程,这里不讨论它的算法细节。从宏观上看,消息的接收方从签名中分离出r和s,然后利用公开的密钥信息和s计算出r。如果计算出的r和接收到的r值相同,则表示验证成功。否则,表示验证失败。
package main
import (
"fmt"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"math/big"
)
func main() {
//明文
message := []byte("Hello world")
key, err := NewSigningKey()
if err != nil {
return
}
signature, err := Sign(message, key)
fmt.Printf("签名后:%x\n", signature)
if err != nil {
return
}
if !Verify(message, signature, &key.PublicKey) {
fmt.Println("验证失败!")
return
}else{
fmt.Println("验证成功!")
}
}
func NewSigningKey() (*ecdsa.PrivateKey, error) {
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
return key, err
}
// Sign signs arbitrary data using ECDSA.
func Sign(data []byte, privkey *ecdsa.PrivateKey) ([]byte, error) {
// hash message
digest := sha256.Sum256(data)
// sign the hash
r, s, err := ecdsa.Sign(rand.Reader, privkey, digest[:])
if err != nil {
return nil, err
}
// encode the signature {R, S}
// big.Int.Bytes() will need padding in the case of leading zero bytes
params := privkey.Curve.Params()
curveOrderByteSize := params.P.BitLen() / 8
rBytes, sBytes := r.Bytes(), s.Bytes()
signature := make([]byte, curveOrderByteSize*2)
copy(signature[curveOrderByteSize-len(rBytes):], rBytes)
copy(signature[curveOrderByteSize*2-len(sBytes):], sBytes)
return signature, nil
}
// Verify checks a raw ECDSA signature.
// Returns true if it's valid and false if not.
func Verify(data, signature []byte, pubkey *ecdsa.PublicKey) bool {
// hash message
digest := sha256.Sum256(data)
curveOrderByteSize := pubkey.Curve.Params().P.BitLen() / 8
r, s := new(big.Int), new(big.Int)
r.SetBytes(signature[:curveOrderByteSize])
s.SetBytes(signature[curveOrderByteSize:])
return ecdsa.Verify(pubkey, digest[:], r, s)
}