编码与加密(对称加密与非对称加密)

编码与加密

  • 只记录作用和场景使用,不探索原理(哥们数学不好.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))
}
posted @ 2024-06-14 20:27  yuzhongrun  阅读(101)  评论(0编辑  收藏  举报