编码与加密(对称加密与非对称加密)
编码与加密
- 只记录作用和场景使用,不探索原理(哥们数学不好.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))
}