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

目录

编码与加密

  • 只记录作用和场景使用,不探索原理(哥们数学不好.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))
    }

相关推荐
小徐Chao努力11 天前
【安全】加密算法原理与实战
安全·https·des·ssl·加密·aes·rsa
佟格湾21 天前
.Net中对称加密的实现
对称加密
伊织code1 个月前
macOS 使用 enca 识别 文件编码类型(比 file 命令准确)
macos·文件·编码·file·iconv·enca
伊织code1 个月前
macOS 使用 iconv 转化文件编码
macos·乱码·文件·编码·转换·iconv
喝养乐多长不高1 个月前
HTTPS加密原理详解
网络·网络协议·http·https·证书·非对称加密·对称加密
星尘安全2 个月前
GitHub Copilot 越狱漏洞
人工智能·ai·copilot·编码·越狱
荣--3 个月前
HiJobQueue:一个简单的线程安全任务队列
c++·编码
羌芜4 个月前
对中文乱码的理解,遇到乱码该怎么办。
编码
core5124 个月前
一文说清flink从编码到部署上线
flink·部署·yarn·编码·cdc·guava30·guava18
01空间5 个月前
Aes加解密
java·aes