天天看點

Go加密算法總結

前言

加密解密在實際開發中應用比較廣泛,常用加解密分為:“對稱式”、“非對稱式”和”數字簽名“。

對稱式:對稱加密(也叫私鑰加密)指加密和解密使用相同密鑰的加密算法。具體算法主要有DES算法,3DES算法,TDEA算法,Blowfish算法,RC5算法,IDEA算法。

非對稱加密(公鑰加密):指加密和解密使用不同密鑰的加密算法,也稱為公私鑰加密。具體算法主要有RSA、Elgamal、背包算法、Rabin、D-H、ECC(橢圓曲線加密算法)。

數字簽名:數字簽名是非對稱密鑰加密技術與數字摘要技術的應用。主要算法有md5、hmac、sha1等。

以下介紹golang語言主要的加密解密算法實作。

md5

MD5資訊摘要算法是一種被廣泛使用的密碼散列函數,可以産生出一個128位(16進制,32個字元)的散列值(hash value),用于確定資訊傳輸完整一緻。
func GetMd5String(s string) string {
    h := md5.New()
    h.Write([]byte(s))
    return hex.EncodeToString(h.Sum(nil))
}
           

hmac

HMAC是密鑰相關的哈希運算消息認證碼(Hash-based Message Authentication Code)的縮寫,

它通過一個标準算法,在計算哈希的過程中,把key混入計算過程中。

和我們自定義的加salt算法不同,Hmac算法針對所有雜湊演算法都通用,無論是MD5還是SHA-1。采用Hmac替代我們自己的salt算法,可以使程式算法更标準化,也更安全。

//key随意設定 data 要加密資料
func Hmac(key, data string) string {
    hash:= hmac.New(md5.New, []byte(key)) // 建立對應的md5哈希加密算法
    hash.Write([]byte(data))
    return hex.EncodeToString(hash.Sum([]byte("")))
}
func HmacSha256(key, data string) string {
    hash:= hmac.New(sha256.New, []byte(key)) //建立對應的sha256哈希加密算法
    hash.Write([]byte(data))
    return hex.EncodeToString(hash.Sum([]byte("")))
}
           

sha1

SHA-1可以生成一個被稱為消息摘要的160位(20位元組)散列值,散列值通常的呈現形式為40個十六進制數。
func Sha1(data string) string {
    sha1 := sha1.New()
    sha1.Write([]byte(data))
    return hex.EncodeToString(sha1.Sum([]byte("")))
}
           

AES

密碼學中的進階加密标準(Advanced Encryption Standard,AES),又稱Rijndael加密法,是美國聯邦***采用的一種區塊加密标準。這個标準用來替代原先的DES(Data Encryption Standard),已經被多方分析且廣為全世界所使用。AES中常見的有三種解決方案,分别為AES-128、AES-192和AES-256。如果采用真正的128位加密技術甚至256位加密技術,蠻力攻擊要取得成功需要耗費相當長的時間。

AES有五種加密模式

/*
	電碼本模式(Electronic Codebook Book (ECB))、
  密碼分組連結模式(Cipher Block Chaining (CBC))、
  電腦模式(Counter (CTR))、
  密碼回報模式(Cipher FeedBack (CFB))
  輸出回報模式(Output FeedBack (OFB))
*/
           
ECB模式

出于安全考慮,golang預設并不支援ECB模式。

package main

import (
    "crypto/aes"
    "fmt"
)

func AESEncrypt(src []byte, key []byte) (encrypted []byte) {
    cipher, _ := aes.NewCipher(generateKey(key))
    length := (len(src) + aes.BlockSize) / aes.BlockSize
    plain := make([]byte, length*aes.BlockSize)
    copy(plain, src)
    pad := byte(len(plain) - len(src))
    for i := len(src); i < len(plain); i++ {
        plain[i] = pad
    }
    encrypted = make([]byte, len(plain))
    // 分組分塊加密
    for bs, be := 0, cipher.BlockSize(); bs <= len(src); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
        cipher.Encrypt(encrypted[bs:be], plain[bs:be])
    }

    return encrypted
}

func AESDecrypt(encrypted []byte, key []byte) (decrypted []byte) {
    cipher, _ := aes.NewCipher(generateKey(key))
    decrypted = make([]byte, len(encrypted))
    //
    for bs, be := 0, cipher.BlockSize(); bs < len(encrypted); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
        cipher.Decrypt(decrypted[bs:be], encrypted[bs:be])
    }

    trim := 0
    if len(decrypted) > 0 {
        trim = len(decrypted) - int(decrypted[len(decrypted)-1])
    }

    return decrypted[:trim]
}

func generateKey(key []byte) (genKey []byte) {
    genKey = make([]byte, 16)
    copy(genKey, key)
    for i := 16; i < len(key); {
        for j := 0; j < 16 && i < len(key); j, i = j+1, i+1 {
            genKey[j] ^= key[i]
        }
    }
    return genKey
}
func main()  {

    source:="hello world"
    fmt.Println("原字元:",source)
    //16byte密鑰
    key:="1443flfsaWfdas"
    encryptCode:=AESEncrypt([]byte(source),[]byte(key))
    fmt.Println("密文:",string(encryptCode))

    decryptCode:=AESDecrypt(encryptCode,[]byte(key))

    fmt.Println("解密:",string(decryptCode))
}
           
CBC模式
package main
import(
    "bytes"
    "crypto/aes"
    "fmt"
    "crypto/cipher"
    "encoding/base64"
)
func main() {
    orig := "hello world"
    key := "0123456789012345"
    fmt.Println("原文:", orig)
    encryptCode := AesEncrypt(orig, key)
    fmt.Println("密文:" , encryptCode)
    decryptCode := AesDecrypt(encryptCode, key)
    fmt.Println("解密結果:", decryptCode)
}
func AesEncrypt(orig string, key string) string {
    // 轉成位元組數組
    origData := []byte(orig)
    k := []byte(key)
    // 分組秘鑰
    // NewCipher該函數限制了輸入k的長度必須為16, 24或者32
    block, _ := aes.NewCipher(k)
    // 擷取秘鑰塊的長度
    blockSize := block.BlockSize()
    // 補全碼
    origData = PKCS7Padding(origData, blockSize)
    // 加密模式
    blockMode := cipher.NewCBCEncrypter(block, k[:blockSize])
    // 建立數組
    cryted := make([]byte, len(origData))
    // 加密
    blockMode.CryptBlocks(cryted, origData)
    return base64.StdEncoding.EncodeToString(cryted)
}
func AesDecrypt(cryted string, key string) string {
    // 轉成位元組數組
    crytedByte, _ := base64.StdEncoding.DecodeString(cryted)
    k := []byte(key)
    // 分組秘鑰
    block, _ := aes.NewCipher(k)
    // 擷取秘鑰塊的長度
    blockSize := block.BlockSize()
    // 加密模式
    blockMode := cipher.NewCBCDecrypter(block, k[:blockSize])
    // 建立數組
    orig := make([]byte, len(crytedByte))
    // 解密
    blockMode.CryptBlocks(orig, crytedByte)
    // 去補全碼
    orig = PKCS7UnPadding(orig)
    return string(orig)
}
//補碼
//AES加密資料塊分組長度必須為128bit(byte[16]),密鑰長度可以是128bit(byte[16])、192bit(byte[24])、256bit(byte[32])中的任意一個。
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)]
}
           
CRT模式
package main

import (
    "bytes"
    "crypto/aes"
    "crypto/cipher"
    "fmt"
)
//加密
func aesCtrCrypt(plainText []byte, key []byte) ([]byte, error) {

    //1. 建立cipher.Block接口
    block, err := aes.NewCipher(key)
    if err != nil {
        return nil, err
    }
    //2. 建立分組模式,在crypto/cipher包中
    iv := bytes.Repeat([]byte("1"), block.BlockSize())
    stream := cipher.NewCTR(block, iv)
    //3. 加密
    dst := make([]byte, len(plainText))
    stream.XORKeyStream(dst, plainText)

    return dst, nil
}


func main() {
    source:="hello world"
    fmt.Println("原字元:",source)

    key:="1443flfsaWfdasds"
    encryptCode,_:=aesCtrCrypt([]byte(source),[]byte(key))
    fmt.Println("密文:",string(encryptCode))

    decryptCode,_:=aesCtrCrypt(encryptCode,[]byte(key))

    fmt.Println("解密:",string(decryptCode))
}
           
CFB模式
package main

import (
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "encoding/hex"
    "fmt"
    "io"
)
func AesEncryptCFB(origData []byte, key []byte) (encrypted []byte) {
    block, err := aes.NewCipher(key)
    if err != nil {
        //panic(err)
    }
    encrypted = make([]byte, aes.BlockSize+len(origData))
    iv := encrypted[:aes.BlockSize]
    if _, err := io.ReadFull(rand.Reader, iv); err != nil {
        //panic(err)
    }
    stream := cipher.NewCFBEncrypter(block, iv)
    stream.XORKeyStream(encrypted[aes.BlockSize:], origData)
    return encrypted
}
func AesDecryptCFB(encrypted []byte, key []byte) (decrypted []byte) {
    block, _ := aes.NewCipher(key)
    if len(encrypted) < aes.BlockSize {
        panic("ciphertext too short")
    }
    iv := encrypted[:aes.BlockSize]
    encrypted = encrypted[aes.BlockSize:]

    stream := cipher.NewCFBDecrypter(block, iv)
    stream.XORKeyStream(encrypted, encrypted)
    return encrypted
}
func main() {
    source:="hello world"
    fmt.Println("原字元:",source)
    key:="ABCDEFGHIJKLMNO1"//16位
    encryptCode:=AesEncryptCFB([]byte(source),[]byte(key))
    fmt.Println("密文:",hex.EncodeToString(encryptCode))
    decryptCode:=AesDecryptCFB(encryptCode,[]byte(key))

    fmt.Println("解密:",string(decryptCode))
}
           
OFB模式
package main

import (
    "bytes"
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "encoding/hex"
    "fmt"
    "io"
)
func aesEncryptOFB( data[]byte,key []byte) ([]byte, error) {
    data = PKCS7Padding(data, aes.BlockSize)
    block, _ := aes.NewCipher([]byte(key))
    out := make([]byte, aes.BlockSize + len(data))
    iv := out[:aes.BlockSize]
    if _, err := io.ReadFull(rand.Reader, iv); err != nil {
        return nil, err
    }

    stream := cipher.NewOFB(block, iv)
    stream.XORKeyStream(out[aes.BlockSize:], data)
    return out, nil
}

func aesDecryptOFB( data[]byte,key []byte) ([]byte, error) {
    block, _ := aes.NewCipher([]byte(key))
    iv  := data[:aes.BlockSize]
    data = data[aes.BlockSize:]
    if len(data) % aes.BlockSize != 0 {
        return nil, fmt.Errorf("data is not a multiple of the block size")
    }

    out := make([]byte, len(data))
    mode := cipher.NewOFB(block, iv)
    mode.XORKeyStream(out, data)

    out= PKCS7UnPadding(out)
    return out, nil
}
//補碼
//AES加密資料塊分組長度必須為128bit(byte[16]),密鑰長度可以是128bit(byte[16])、192bit(byte[24])、256bit(byte[32])中的任意一個。
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)]
}
func main() {
    source:="hello world"
    fmt.Println("原字元:",source)
    key:="1111111111111111"//16位  32位均可
    encryptCode,_:=aesEncryptOFB([]byte(source),[]byte(key))
    fmt.Println("密文:",hex.EncodeToString(encryptCode))
    decryptCode,_:=aesDecryptOFB(encryptCode,[]byte(key))

    fmt.Println("解密:",string(decryptCode))
}
           
RSA加密簡介
rsa加密算法簡史
RSA是1977年由羅納德·李維斯特(Ron Rivest)、阿迪·薩莫爾(Adi Shamir)和倫納德·阿德曼(Leonard Adleman)一起提出的。當時他們三人都在麻省理工學院工作。RSA就是他們三人姓氏開頭字母拼在一起組成的。
rsa加密算法實作原理
學過算法的朋友都知道,計算機中的算法其實就是數學運算。是以,再講解RSA加密算法之前,有必要了解一下一些必備的數學知識。我們就從數學知識開始講解。
必備數學知識
RSA加密算法中,隻用到素數、互質數、指數運算、模運算等幾個簡單的數學知識。是以,我們也需要了解這幾個概念即可
素數
素數又稱質數,指在一個大于1的自然數中,除了1和此整數自身外,不能被其他自然數整除的數。這個概念,我們在上國中,甚至國小的時候都學過了,這裡就不再過多解釋了。
互質數
百度百科上的解釋是:公因數隻有1的兩個數,叫做互質數。;***上的解釋是:互質,又稱互素。若N個整數的最大公因子是1,則稱這N個整數互質。

常見的互質數判斷方法主要有以下幾種:

/*
	1、兩個不同的質數一定是互質數。例如,2與7、13與19。
  2、一個質數,另一個不為它的倍數,這兩個數為互質數。例如,3與10、5與 26。
  3、相鄰的兩個自然數是互質數。如 15與 16。
              4、相鄰的兩個奇數是互質數。如 49與 51。
  5、較大數是質數的兩個數是互質數。如97與88。
  6、小數是質數,大數不是小數的倍數的兩個數是互質數。例如 7和 16。
  7、2和任何奇數是互質數。例如2和87。
  8、1不是質數也不是合數,它和任何一個自然數在一起都是互質數。如1和9908。
  9、輾轉相除法。
*/
           
指數運算
/*
	 指數運算又稱乘方計算,計算結果稱為幂。nm指将n自乘m次。把nm看作乘方的結果,叫做”n的m次方”或”n的m次方”。其中,n稱為“底數”,m稱為“指數”。
*/
           
模運算
/*
	 模運算即求餘運算。“模”是“Mod”的音譯。和模運算緊密相關的一個概念是“同餘”。數學上,當兩個整數除以同一個正整數,若得相同餘數,則二整數同餘。

  兩個整數a,b,若它們除以正整數m所得的餘數相等,則稱a,b對于模m同餘,記作: a ≡ b (mod m);讀作:a同餘于b模m,或者,a與b關于模m同餘。例如:26 ≡ 14 (mod 12)。
*/
           

RSA加密算法

公鑰和密鑰的産生
假設Alice想要通過一個不可靠的媒體接收Bob的一條私人訊息。她可以用以下的方式來産生一個公鑰和一個私鑰:
/*
	 1、随意選擇兩個大的質數p和q,p不等于q,計算N=pq。
   2、根據歐拉函數,求得r = (p-1)(q-1)
   3、選擇一個小于 r 的整數 e,求得 e 關于模 r 的模反元素,命名為d。(模反元素存在,當且僅當e與r互質)
   4、将 p 和 q 的記錄銷毀。
       (N,e)是公鑰,(N,d)是私鑰。Alice将她的公鑰(N,e)傳給Bob,而将她的私鑰(N,d)藏起來。
*/
           
加密消息

假設Bob想給Alice送一個消息m,他知道Alice産生的N和e。他使用起先與Alice約好的格式将m轉換為一個小于N的整數n,比如他可以将每一個字轉換為這個字的Unicode碼,然後将這些數字連在一起組成一個數字。假如他的資訊非常長的話,他可以将這個資訊分為幾段,然後将每一段轉換為n。用下面這個公式他可以将n加密為c:

  

ne ≡ c (mod N)

計算c并不複雜。Bob算出c後就可以将它傳遞給Alice。
解密消息

Alice得到Bob的消息c後就可以利用她的密鑰d來解碼。她可以用以下這個公式來将c轉換為n:

  

cd ≡ n (mod N)

得到n後,她可以将原來的資訊m重新複原。
解碼的原理是
  

cd ≡ n e·d(mod N)

以及

ed ≡ 1 (mod p-1)

ed ≡ 1 (mod q-1)。

由費馬小定理可證明(因為p和q是質數)

  

n e·d ≡ n (mod p)

 和  

n e·d ≡ n (mod q)

這說明(因為p和q是不同的質數,是以p和q互質)

  

n e·d ≡ n (mod pq)

簽名消息
RSA也可以用來為一個消息署名。假如甲想給乙傳遞一個署名的消息的話,那麼她可以為她的消息計算一個散列值(Message digest),然後用她的密鑰(private key)加密這個散列值并将這個“署名”加在消息的後面。這個消息隻有用她的公鑰才能被解密。乙獲得這個消息後可以用甲的公鑰解密這個散列值,然後将這個資料與他自己為這個消息計算的散列值相比較。假如兩者相符的話,那麼他就可以知道發信人持有甲的密鑰,以及這個消息在傳播路徑上沒有被篡改過。

Golang加密解密之RSA

概要

這是一個非對稱加密算法,一般通過公鑰加密,私鑰解密。

在加解密過程中,使用openssl生産密鑰。執行如下操作:

建立私鑰
openssl genrsa -out private.pem 1024 
//密鑰長度,1024覺得不夠安全的話可以用2048,但是代價也相應增大
           
建立公鑰
openssl rsa -in private.pem -pubout -out public.pem
// 這樣便生産了密鑰。
           
一般地,各個語言也會提供API,用于生成密鑰。在Go中,可以檢視

encoding/pem

包和

crypto/x509

包。

加密解密這塊,涉及到很多标準

Go RSA加密
  1. rsa加解密, 必須會去查crypto/ras這個包
Package rsa implements RSA encryption as specified in PKCS#1.
           

這是該包的說明:實作RSA加密技術,基于PKCS#1規範。

對于什麼是PKCS#1,可以查閱相關資料。PKCS(公鑰密碼标準),而#1就是RSA的标準。可以檢視:PKCS系列簡介

從該包中函數的名稱,可以看到有兩對加解密的函數。

EncryptOAEP和DecryptOAEP
EncryptPKCS1v15和DecryptPKCS1v15
           

這稱作加密方案,詳細可以檢視,PKCS #1 v2.1 RSA 算法标準

可見,當與其他語言互動時,需要确定好使用哪種方案。

PublicKey和PrivateKey兩個類型分别代表公鑰和私鑰,關于這兩個類型中成員該怎麼設定,這涉及到RSA加密算法,本文中,這兩個類型的執行個體通過解析文章開頭生成的密鑰得到。

2 . 解析密鑰得到PublicKey和PrivateKey的執行個體

這個過程,我也是花了好些時間(主要對各種加密的各種東東不熟):怎麼将openssl生成的密鑰檔案解析到公鑰和私鑰執行個體呢?

encoding/pem

包中,看到了—–BEGIN Type—–這樣的字樣,這正好和openssl生成的密鑰形式差不多,那就試試。

在該包中,一個block代表的是PEM編碼的結構,關于PEM,請查閱相關資料。我們要解析密鑰,當然用Decode方法:

/*
	func Decode(data []byte) (p *Block, rest []byte)
*/
           

這樣便得到了一個Block的執行個體(指針)。

解析來看

crypto/x509

。為什麼是x509呢?這又涉及到一堆概念。先不管這些,我也是看encoding和crypto這兩個包的子包摸索出來的。

在x509包中,有一個函數:

func ParsePKIXPublicKey(derBytes []byte) (pub interface{}, err error)
           

從該函數的說明:ParsePKIXPublicKey parses a DER encoded public key. These values are typically found in PEM blocks with “BEGIN PUBLIC KEY”。可見這就是解析PublicKey的。另外,這裡說到了PEM,可以上面的encoding/pem對了。

而解析私鑰的,有好幾個方法,從上面的介紹,我們知道,RSA是PKCS#1,剛好有一個方法:

func ParsePKCS1PrivateKey(der []byte) (key *rsa.PrivateKey, err error)
           

傳回的就是rsa.PrivateKey

加密解密實作

加密

func RsaEncrypt(origData []byte) ([]byte, error) {
  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!")
  }
  priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
  if err != nil {
    return nil, err
  }
  return rsa.DecryptPKCS1v15(rand.Reader, priv, ciphertext)
}
           

使用例子

package main
 
import (
  "fmt"
)

func main() {
  data, err := RsaEncrypt([]byte("test"))
  if err != nil {
    panic(err)
  }
  origData, err := RsaDecrypt(data)
  if err != nil {
    panic(err)
  }
  fmt.Println(string(origData))
}

// 此例子是加密完test後立馬解密
           

參考:

https://segmentfault.com/a/1190000024557845

https://www.jb51.net/article/89884.htm