- 編碼與加密
- 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
例如: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))
}