編碼與加密(對稱加密與非對稱加密)

yuzhongrun發表於2024-06-14

目錄
  • 編碼與加密
    • Base64編碼(可逆)
    • 十六進位制編碼(hex.EncodeToString函式)(可逆)
    • 雜湊演算法(不可逆)
      • MD5(不可逆)
      • SHA-256(不可逆)
      • MAC演算法(不可逆)
    • 加密演算法(可逆)
      • 對稱加密演算法(可逆)
        • DES(可逆)
        • AES(可逆)
        • 區別
      • 非對稱加密演算法(可逆)
        • RSA(可逆)
        • ECC(可逆)
        • PEM格式儲存金鑰
        • DER格式儲存金鑰
    • 加密模式
      • CBC模式
      • ECB模式

編碼與加密

  • 只記錄作用和場景使用,不探索原理(哥們數學不好.jpg
  • demo都是golang實現的

Base64編碼(可逆)

它的主要作用是將二進位制資料轉換為文字格式,從而便於在文字傳輸通道中傳遞。
因為傳輸二進位制資料時候比如在http中要以可見字元傳輸,所以需要將二進位制資料編碼為可見字元。

  • 標準 Base64 裡的 64 個可列印字元是 A-Za-z0-9+/

    編碼後長度是(n+2)/3*4
  • 如果待編碼內容的位元組數不是 3 的整數倍,那需要進行一些額外的處理。
    • 如果最後剩下 1 個位元組,那麼將補 4 個 0 位,編碼成 2 個 Base64 字元,然後補兩個 =:
    • 如果最後剩下 2 個位元組,那麼將補 2 個 0 位,編碼成 3 個 Base64 字元,然後補一個 =:
  • URL Safe Base64 編碼
    在 URL 的應用場景中,因為標準 Base64 索引表中的 / 和 + 會被 URLEncoder 轉義成 %XX 形式,但 % 是 SQL 中的萬用字元,直接用於資料庫操作會有問題。此時可以採用 URL Safe 的編碼器
    將 + 替換為 -
    將 / 替換為 _
ans := make([]byte, base64.StdEncoding.EncodedLen(len("abcd")))
base64.StdEncoding.Encode(ans,[]byte("abcs"))//編碼
fmt.Println(string(ans))
ans2 := make([]byte, base64.StdEncoding.DecodedLen(len(ans)))
base64.StdEncoding.Decode(ans2,ans)//解碼
fmt.Println(string(ans2))

十六進位制編碼(hex.EncodeToString函式)(可逆)

將位元組陣列轉換為十六進位制字串
將其每個位元組轉換為兩個十六進位制字元,並將結果組合成一個字串返回。但是比base64長
對比base64是十六進位制編碼可以直接參與運算,且方便檢視二進位制資料

雜湊演算法(不可逆)

雜湊演算法(Hash Function) 是一種將任意長度的資料轉換為固定長度的輸出,該輸出基於輸入資料,且唯一對應於輸入資料。雜湊函式是單向函式,它將輸入轉換為固定長度的輸出,而且不可逆。

  • 常見的雜湊演算法:MD5、SHA-1、SHA-256、SHA-384、SHA-512

MD5(不可逆)

返回定長16位元組的128位bit雜湊值
資料完整性校驗:MD5演算法常用於驗證資料的完整性。在資料傳輸過程中,傳送方可以計算資料的MD5雜湊值並將其傳送給接收方。接收方收到資料後,再次計算雜湊值並與傳送方提供的雜湊值進行比較。如果兩者匹配,則說明資料在傳輸過程中沒有被篡改。
密碼儲存:MD5演算法也常用於密碼儲存。將使用者密碼透過MD5雜湊後儲存在資料庫中,即使資料庫被洩露,攻擊者也無法直接獲取使用者的明文密碼。然而,由於MD5演算法存在已知的安全漏洞(如彩虹表攻擊和碰撞攻擊),現在已不推薦使用MD5來儲存密碼。更安全的做法是使用加鹽雜湊

func Test_MD5(t *testing.T) {
    salt := "123456"//鹽值
	s := "hello"//需要雜湊的字串
	h := md5.New()//建立一個md5物件
	h.Write([]byte(s)+[]byte(salt))//寫入需要雜湊的字串
	fmt.Println(h.Sum(nil))//Sum把雜湊值追加到引數*[]byte中,並返回
}

即使加鹽,MD5 仍然可以被破解。加鹽只是增加了破解的難度,但並沒有解決 MD5 本身的安全缺陷。以下是詳細的解釋:
加鹽的作用和侷限性
增加破解難度:加鹽主要是為了防止彩虹表攻擊(即預計算雜湊值的查詢表)。透過加入隨機鹽值,每個輸入的雜湊結果都會有所不同,即使輸入相同也會生成不同的雜湊值,這使得預計算雜湊表變得無效。
防止簡單暴力破解:如果每個輸入都帶有唯一的鹽值,攻擊者不能一次破解多個雜湊值,他們必須針對每個鹽值單獨進行暴力破解。
鹽值和輸入:鹽值並不需要保密,通常與雜湊值一起儲存。然而,鹽值的存在不會消除雜湊演算法的固有弱點。如果雜湊演算法易於碰撞(如 MD5),攻擊者仍然可以利用這些弱點進行攻擊。

SHA-256(不可逆)

返回定長32位元組的256bit雜湊值
SHA-256 是一種雜湊演算法,屬於安全雜湊演算法(Secure Hash Algorithm)家族的一員,用於將任意長度的輸入資料轉換為固定長度(256 位元,即 32 位元組)的雜湊值。SHA-256 具有以下特點:

  • 不可逆性:無法透過雜湊值反推出原始資料。
  • 固定輸出長度:不論輸入資料的長度如何,SHA-256 始終生成長度為 256 位的雜湊值。
  • 碰撞抗性:極難找到兩個不同的輸入資料生成相同的雜湊值。
  • 廣泛應用:用於資料完整性驗證、數字簽名、密碼學安全等領域。
  • 在實際應用中,SHA-256 通常用於生成資料的唯一標識或驗證資料在傳輸過程中是否被篡改。
func Test_SHA256(t *testing.T) {
	s := "hello"//需要雜湊的字串
	h := sha256.New()//建立一個sha256物件
	h.Write([]byte(s))//寫入需要雜湊的字串
	fmt.Println(hex.EncodeToString(h.Sum(nil)))//Sum把雜湊值追加到引數*[]byte中,並返回
}

MAC演算法(不可逆)

HMAC 是一種基於雜湊函式和金鑰的訊息認證碼。它結合了雜湊演算法(如 SHA-256、SHA-1、MD5 等)和金鑰,用於生成一種認證碼,用於驗證訊息的完整性和真實性。HMAC 的特點包括:

  • 結合性:使用雜湊函式和金鑰生成認證碼。
  • 安全性:提供對訊息完整性的強保證,即使雜湊函式被公開,只有知道金鑰的人才能驗證認證碼。
  • 靈活性:可以選擇不同的雜湊演算法,但常見選擇是 SHA-256。
    HMAC使用SHA-256 + 金鑰生成訊息認證程式碼MAX(Message Authentication Code)(不可逆)
    HS256(HMAC with SHA-256)生成的 token 主要用於 驗證訊息的完整性和真實性 ,但它更常用於生成和驗證身份認證的 token,例如 JSON Web Token (JWT)。
func Test_HMAC(t *testing.T) {
	key := []byte("secret")// 金鑰
	a := hmac.New(sha256.New, key)// 建立一個新的hmac物件
	a.Write([]byte("hello"))// 寫入需要簽名的資料
	b := a.Sum(nil)// 計算簽名並返回結果
	fmt.Println(base64.StdEncoding.EncodeToString(b))// 對結果進行Base64編碼
	//iKqz7ejTrflNJquQ07r9SiCDBww7zOnAFO4EpEOEfAs=
}

加密演算法(可逆)

加密演算法(Encryption Algorithm) 是將明文(plaintext)轉換為密文(ciphertext),或者將密文轉換為明文的演算法。
加密都是把一個block快加密成等長的密文,由於只能一個個塊加密,要配合不同的加密模式例如下面的CBC模式和ECB模式,才能達到加密的目的。
對稱加密比非對稱加密快

對稱加密演算法(可逆)

  • 是指加密和解密使用相同金鑰的加密演算法。
  • 加密和解密速度快,安全性高。
  • 對稱加密在開發中用的很多,如 AES,DES,3DES,RC。

DES(可逆)

  • 採用的金鑰長度為 56 位
  • 加密後長度和輸入相同
  • 安全性較低,目前不推薦使用

DES 加密演算法的金鑰長度應該是 8 個位元組(64 位),不過實際上只有 56 位被用於加密計算,而 8 位用於奇偶校驗,所以通常是使用 8 個位元組的金鑰
對於 DES 加密演算法而言,資料長度必須是加密塊大小的整數倍,即 8 位元組。
但是近些年使用越來越少,因為 DES 使用56位金鑰(金鑰長度越長越安全),以現代計算能力24小時內即可被破解。雖然如此,在某些簡單應用中,我們還是可以使用 DES 加密演算法。

var key = []byte("12345678") // 56位金鑰
//編碼一個blocksize長度的塊(8位)
func encoded(s []byte) []byte {
	block, _ := des.NewCipher(key) //Cipher是密碼的意思
	ans := make([]byte, len(s))
	block.Encrypt(ans, s) //encrypt加密一個block塊
	return ans
}
//解碼一個blocksize長度的塊(8位)
func decoded(s []byte) []byte {
	block, _ := des.NewCipher(key) //Cipher是密碼的意思
	ans := make([]byte, len(s))
	block.Decrypt(ans, s) //decrypt解密一個block塊
	return ans
}

AES(可逆)

  • 採用的金鑰長度為 128 位、192 位或 256 位
  • 加密後長度和輸入相同
  • 安全性高,目前推薦使用
func Test_AES(t *testing.T) {
	key := []byte("1234567890123456") // 16位元組金鑰
	plaintext := []byte("helloworldaaaaaa")// 16位元組明文
	ans:=encoded_aes(key,plaintext)
	fmt.Println(ans)
	fmt.Println(deccode_aes(key,ans))
}
// 編碼一個blocksize長度的塊(16位)
func encoded_aes(keys,s []byte) []byte {
	block, _ := aes.NewCipher(keys) //Cipher是密碼的意思
	ans := make([]byte, len(s))
	block.Encrypt(ans, s) //encrypt加密一個block塊
	return ans
}
func deccode_aes(keys,s []byte) []byte {
	block, _ := aes.NewCipher(keys) //Cipher是密碼的意思
	ans := make([]byte, len(s))
	block.Decrypt(ans, s) //encrypt加密一個block塊
	return ans
}

區別

AES(Advanced Encryption Standard)和DES(Data Encryption Standard)是兩種常見的對稱加密演算法,它們在多個方面有顯著的區別:

  • AES:支援多種金鑰長度,包括 AES-128、AES-192 和 AES-256,分別對應 128 位、192 位和 256 位金鑰長度。這些金鑰長度提供了不同級別的安全性,其中 AES-256 提供了最高階別的安全性。
  • DES:固定為 56 位的金鑰長度,但實際上只有 56 位被用作加密金鑰,其餘 8 位用作奇偶校驗,因此實際的加密強度是 56 位。由於金鑰長度短,DES 的安全性在現代計算環境下已經不足以應對安全威脅。

非對稱加密演算法(可逆)

  • 是指加密和解密使用不同的金鑰的加密演算法。
  • 加密速度慢,安全性高。
  • 私鑰加密,公鑰解密
  • 非對稱加密在開發中用的不多,如 RSA、DSA。

RSA(可逆)

  • 加密後資料長度 <= 金鑰位數 / 8 - 11
  • 公鑰和私鑰在本質上是大整數。它們是基於大整數數學運算的加密系統的核心組成部分。在許多公鑰加密演算法(如 RSA、ECC)中,公鑰和私鑰都可以表示
    在 RSA 加密系統中:
    • 私鑰由兩個主要大整陣列成:
      • n(模數):這是兩個大素數 p 和 q 的乘積。
      • d(私有指數):這是一個與模數 n 相關的整數,用於解密。
    • 公鑰由兩個主要大整陣列成:
      • n(模數):與私鑰中的模數相同。
      • e(公有指數):這是一個通常選定的較小的整數,用於加密。
    • 這些整數在 RSA 金鑰生成過程中透過數學運算生成並滿足特定的數學關係,使得 RSA 加密和解密可以進行。
func Test_RSA(t *testing.T) {
	// 生成私鑰和公鑰
	privateKey, _ := rsa.GenerateKey(rand.Reader, 2048)
	publicKey := &privateKey.PublicKey
	// 儲存私鑰和公鑰
	// res1 := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)})
	// res2 := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: x509.MarshalPKCS1PublicKey(publicKey)})
	// ioutil.WriteFile("private.pem", res1, 0600)
	// ioutil.WriteFile("public.pem", res2, 0600)
	//讀取私鑰和公鑰
	data, _ := ioutil.ReadFile("private.pem")
	block, _ := pem.Decode(data)
	privateKey, _ = x509.ParsePKCS1PrivateKey(block.Bytes)
	data, _ = ioutil.ReadFile("public.pem")
	block, _ = pem.Decode(data)
	publicKey, _ = x509.ParsePKCS1PublicKey(block.Bytes)

	// 加密
	src := []byte("hello world")
	encrypted, _ := rsa.EncryptOAEP(sha256.New(), rand.Reader, publicKey, src, nil)

	fmt.Println("加密後的資料:",base64.StdEncoding.EncodeToString(encrypted))

	// 解密
	decrypted, _ := rsa.DecryptOAEP(sha256.New(), rand.Reader, privateKey, encrypted, nil)
	fmt.Println(string(decrypted))
}

ECC(可逆)

ECC 金鑰
在橢圓曲線加密(ECC)中:

  • 私鑰 是一個大整數 d,這是在某個範圍內的隨機數。
  • 公鑰 是橢圓曲線上的一個點 Q,它是透過橢圓曲線上的基點 G 乘以私鑰 d 得到的,即 Q = d * G。
  • 橢圓曲線加密依賴於橢圓曲線上的點運算,私鑰和公鑰在數學上有著密切的關係。

PEM格式儲存金鑰

pem.EncodeToMemory 是 Go 語言標準庫中的一個函式,位於 encoding/pem 包內,用於將 PEM 編碼的塊(pem.Block)編碼為位元組切片並返回。
什麼是 PEM 編碼
PEM(Privacy-Enhanced Mail)編碼是一種用於表示加密物件(如證書、私鑰、公鑰等)的標準。PEM 格式的檔案通常以 -----BEGIN ----- 和 -----END ----- 包圍資料塊,並且資料塊使用 Base64 編碼。
例如:public.pem 檔案的內容可能如下所示:

-----BEGIN PUBLIC KEY-----
MIIBCgKCAQEAvHxBCN85ydD+qwZvnIC1jt2X6PCivCgmgF0whfRfZE2CSukKJfWs
BZaPDdV8Zus/LPuH7PJs1rOazwQxyoewSy0hO2h66eAoyfCkGAwi30+lshop/oiK
BrHQW0g3S7Uywcfx+WQpFxP+YtWqXqhSGmb/Y83gYpvVtWUm5zzWleg1Iv1M2ihs
KBR+SxAoLRQmnycEmHlOorw138VR0wnH8Gmh9xNZ/+RV6wIOQVmf1VHu+dJnmf21
wAMdua9nOoibxrVz69IzjixiXi5M1El2jncAMGjRBJsMbwtfWPbABVju6f6PK13y
nYcf1rwlRcchxTeAwVnMCppMStrdhl2fTQIDAQAB
-----END PUBLIC KEY-----

pem.EncodeToMemory 的作用
pem.EncodeToMemory 的主要作用是將一個 pem.Block 物件編碼為 PEM 格式的位元組切片,方便儲存或傳輸。
示例程式碼
下面是一個使用 pem.EncodeToMemory 的示例,它生成一個 RSA 私鑰,並將其編碼為 PEM 格式的位元組切片,然後列印出來:

package main

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"fmt"
)

func main() {
	// 生成 RSA 私鑰
	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		fmt.Println("Error generating RSA key:", err)
		return
	}

	// 將私鑰轉換為 PKCS#1 格式的 ASN.1 DER 編碼
	privateKeyDER := x509.MarshalPKCS1PrivateKey(privateKey)

	// 建立一個 PEM Block
	privateKeyBlock := &pem.Block{
		Type:  "RSA PRIVATE KEY",
		Bytes: privateKeyDER,
	}

	// 將 PEM Block 編碼為位元組切片
	privateKeyPEM := pem.EncodeToMemory(privateKeyBlock)

	// 列印 PEM 編碼的私鑰
	fmt.Println(string(privateKeyPEM))
}

DER格式儲存金鑰

DER(Distinguished Encoding Rules)格式是一種二進位制編碼格式,用於儲存 ASN.1 編碼的結構。
DER 格式的檔案通常以 0x30 開始,以 0x00 結束。

加密模式

CBC模式

  • 加密過程:
    對第一個資料塊(明文)進行加密時,使用 IV 與明文進行異或運算,然後將結果再與加密演算法的金鑰進行加密,得到第一個密文塊。
    對後續的資料塊,將前一個密文塊與當前的明文進行異或運算,然後再與金鑰進行加密,得到當前的密文塊。
    這樣一直進行下去,每個密文塊都依賴於前一個密文塊的加密結果,因此形成了一條“鏈”。
  • 解密過程:
    解密過程與加密過程相反。首先使用 IV 與第一個密文塊進行解密運算(使用解密演算法和金鑰),然後再與 IV 進行異或運算,得到第一個明文塊。
    對後續的密文塊,使用當前密文塊與前一個密文塊進行解密運算,然後再與前一個密文塊進行異或運算,得到當前的明文塊。
    這樣依次進行下去,直至得到所有的明文塊。
    AES-CBC demo
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])
	fmt.Println("unpadding:", origData[length-1])
	return origData[:(length - unpadding)]
 }
func Test_DES_CBC(t *testing.T) {
	key := []byte("12345678")                            // 8位金鑰
	plaintext := []byte("hello woqrld for study des cbc") // 8位元組明文
	iv := []byte("12345678")                             // 8位元組iv
	block, _ := des.NewCipher(key)
	mode := cipher.NewCBCEncrypter(block, iv) // 建立一個新的CBC加密器
	fmt.Println("補位前明文:", plaintext, len(plaintext))
	plaintext = PKCS7Padding(plaintext, block.BlockSize()) // 填充
	fmt.Println("加密前明文:", plaintext, len(plaintext))
	ciphertext := make([]byte, len(plaintext))          // 16位元組密文
	for i := 0; i < len(plaintext); i += des.BlockSize {
		blocks := plaintext[i : i+des.BlockSize] // 取出一個block塊
		mode.CryptBlocks(ciphertext[i:], blocks) // 加密一個block塊
	}
	fmt.Println("加密後密文:", ciphertext)

	
	fmt.Println("解密前密文:", ciphertext)
	block1, _ := des.NewCipher(key)
	mode1 := cipher.NewCBCDecrypter(block1, iv) // 建立一個新的CBC解密器
	plaintext1 := make([]byte, len(ciphertext))
	for i := 0; i < len(ciphertext); i += des.BlockSize {
		block2 := ciphertext[i : i+des.BlockSize] // 取出一個block塊
		mode1.CryptBlocks(plaintext1[i:], block2) // 解密一個block塊
	}
	fmt.Println("解密後明文:", plaintext1)
	plaintext1 = PKCS7UnPadding(plaintext1) // 去除填充
	fmt.Println("去除填充後明文:", string(plaintext1))
}

ECB模式

  • 加密過程:
    對每個資料塊(明文)進行加密時,使用加密演算法的金鑰進行加密,得到當前的密文塊。
  • 解密過程:
    對每個資料塊(密文)進行解密時,使用加密演算法的金鑰進行解密,得到當前的明文塊。

    ECB模式 demo
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])
	fmt.Println("unpadding:", origData[length-1])
	return origData[:(length - unpadding)]
}
func encrypt_DES_ECB(plaintext, key []byte) ([]byte, error) {
	block, err := des.NewCipher(key)
	if err != nil {
		return nil, err
	}
	//對明文進行填充
	plaintext = PKCS7Padding(plaintext, block.BlockSize())
	ciphertext := make([]byte, len(plaintext))
	//就是逐塊EDS加密後拼接
	for bs, be := 0, block.BlockSize(); bs < len(plaintext); bs, be = bs+block.BlockSize(), be+block.BlockSize() {
		block.Encrypt(ciphertext[bs:be], plaintext[bs:be])
	}
	return ciphertext, nil
}
func decrypt_DES_ECB(ciphertext, key []byte) ([]byte, error) {
	block, _ := des.NewCipher(key)

	lens := len(ciphertext)
	ans := make([]byte, lens)
	//就是逐塊EDS解密後拼接
	for bs, be := 0, block.BlockSize(); bs < len(ciphertext); bs, be = bs+block.BlockSize(), be+block.BlockSize() {
		block.Decrypt(ans[bs:be], ciphertext[bs:be])
	}
	//對密文進行去填充
	ans = PKCS7UnPadding(ans)
	return ans, nil
}
func Test_DES_ECB(t *testing.T) {
	// 加密
	key := []byte("12345678")
	src := []byte("hello world")
	ans, _ := encrypt_DES_ECB(src, key)
	fmt.Println(ans)
	// 解密
	ans1, _ := decrypt_DES_ECB(ans, key)
	fmt.Println(string(ans1))
}

相關文章