MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Go加密与解密库

2022-03-313.9k 阅读

Go 语言中的加密解密基础概念

在深入探讨 Go 语言的加密与解密库之前,我们先来了解一些基本的加密解密概念。

加密是将明文(可理解的信息)通过特定的算法转换为密文(不可理解的信息)的过程,其目的是确保信息在传输或存储过程中的保密性,防止未授权的访问。解密则是加密的逆过程,它使用特定的密钥将密文还原为明文。

常见的加密算法主要分为两类:对称加密和非对称加密。

对称加密

对称加密使用相同的密钥进行加密和解密。这种加密方式速度快,适用于对大量数据的加密。常见的对称加密算法有 AES(高级加密标准)、DES(数据加密标准)等。以 AES 为例,它支持 128 位、192 位和 256 位的密钥长度。

非对称加密

非对称加密使用一对密钥:公钥和私钥。公钥用于加密数据,私钥用于解密数据。这种加密方式安全性高,常用于身份验证和密钥交换等场景,如 RSA 算法。非对称加密的缺点是运算速度相对较慢,不适用于对大量数据的直接加密。

Go 语言中的加密解密库概述

Go 语言在标准库中提供了丰富的加密解密功能,同时也有许多优秀的第三方库可供使用。标准库中的 crypto 包是核心,它包含了各种加密算法的实现,如 crypto/aes 用于 AES 对称加密,crypto/rsa 用于 RSA 非对称加密等。

第三方库如 gopkg.in/square/go-jose.v2 提供了更高级的 JSON Web 加密(JWE)和 JSON Web 签名(JWS)功能,方便在 Web 应用中进行安全的信息传输。

对称加密:AES 算法实现

AES 加密

在 Go 语言中使用 AES 进行加密,首先需要导入 crypto/aescrypto/cipher 包。以下是一个简单的 AES - 256 加密示例:

package main

import (
    "bytes"
    "crypto/aes"
    "crypto/cipher"
    "fmt"
)

func encryptAES256(plaintext, key []byte) ([]byte, error) {
    block, err := aes.NewCipher(key)
    if err!= nil {
        return nil, err
    }

    blockSize := block.BlockSize()
    plaintext = pkcs7Padding(plaintext, blockSize)
    blockMode := cipher.NewCBCEncrypter(block, key[:blockSize])
    ciphertext := make([]byte, len(plaintext))
    blockMode.CryptBlocks(ciphertext, plaintext)
    return ciphertext, nil
}

func pkcs7Padding(data []byte, blockSize int) []byte {
    padding := blockSize - len(data)%blockSize
    padtext := bytes.Repeat([]byte{byte(padding)}, padding)
    return append(data, padtext...)
}

在上述代码中,encryptAES256 函数实现了 AES - 256 加密。首先通过 aes.NewCipher 创建一个 AES 加密块,然后对明文进行 PKCS7 填充,接着使用 CBC(Cipher Block Chaining)模式进行加密。

AES 解密

解密是加密的逆过程,以下是对应的解密代码:

func decryptAES256(ciphertext, key []byte) ([]byte, error) {
    block, err := aes.NewCipher(key)
    if err!= nil {
        return nil, err
    }

    blockSize := block.BlockSize()
    blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
    plaintext := make([]byte, len(ciphertext))
    blockMode.CryptBlocks(plaintext, ciphertext)
    plaintext = pkcs7Unpadding(plaintext)
    return plaintext, nil
}

func pkcs7Unpadding(data []byte) []byte {
    length := len(data)
    unpadding := int(data[length - 1])
    return data[:(length - unpadding)]
}

decryptAES256 函数首先创建 AES 解密块,使用 CBC 模式解密,最后进行 PKCS7 去填充操作。

非对称加密:RSA 算法实现

RSA 密钥生成

在使用 RSA 进行加密和解密之前,需要生成一对公私钥。以下是生成 RSA 密钥对的代码:

package main

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

func generateRSAKey(bits int) error {
    privateKey, err := rsa.GenerateKey(rand.Reader, bits)
    if err!= nil {
        return err
    }

    privateKeyPEM := &pem.Block{
        Type:  "RSA PRIVATE KEY",
        Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
    }

    privateFile, err := os.Create("private.pem")
    if err!= nil {
        return err
    }
    defer privateFile.Close()

    err = pem.Encode(privateFile, privateKeyPEM)
    if err!= nil {
        return err
    }

    publicKey := &privateKey.PublicKey
    publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey)
    if err!= nil {
        return err
    }

    publicKeyPEM := &pem.Block{
        Type:  "RSA PUBLIC KEY",
        Bytes: publicKeyBytes,
    }

    publicFile, err := os.Create("public.pem")
    if err!= nil {
        return err
    }
    defer publicFile.Close()

    err = pem.Encode(publicFile, publicKeyPEM)
    if err!= nil {
        return err
    }

    return nil
}

上述代码通过 rsa.GenerateKey 生成指定长度(如 2048 位)的 RSA 密钥对,并将公私钥分别保存为 PEM 格式的文件。

RSA 加密

使用公钥进行加密:

func encryptRSA(plaintext []byte, publicKeyPath string) ([]byte, error) {
    publicKeyFile, err := os.ReadFile(publicKeyPath)
    if err!= nil {
        return nil, err
    }

    block, _ := pem.Decode(publicKeyFile)
    publicKey, err := x509.ParsePKIXPublicKey(block.Bytes)
    if err!= nil {
        return nil, err
    }

    rsaPublicKey := publicKey.(*rsa.PublicKey)
    ciphertext, err := rsa.EncryptPKCS1v15(rand.Reader, rsaPublicKey, plaintext)
    if err!= nil {
        return nil, err
    }

    return ciphertext, nil
}

encryptRSA 函数读取公钥文件,解析公钥,然后使用 rsa.EncryptPKCS1v15 方法对明文进行加密。

RSA 解密

使用私钥进行解密:

func decryptRSA(ciphertext []byte, privateKeyPath string) ([]byte, error) {
    privateKeyFile, err := os.ReadFile(privateKeyPath)
    if err!= nil {
        return nil, err
    }

    block, _ := pem.Decode(privateKeyFile)
    privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
    if err!= nil {
        return nil, err
    }

    plaintext, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, ciphertext)
    if err!= nil {
        return nil, err
    }

    return plaintext, nil
}

decryptRSA 函数读取私钥文件,解析私钥,使用 rsa.DecryptPKCS1v15 方法对密文进行解密。

哈希函数

哈希函数也是加密领域的重要组成部分。哈希函数将任意长度的数据映射为固定长度的哈希值。在 Go 语言中,crypto 包提供了多种哈希函数的实现,如 MD5、SHA - 1、SHA - 256 等。

SHA - 256 哈希计算

以下是计算 SHA - 256 哈希值的示例:

package main

import (
    "crypto/sha256"
    "fmt"
)

func calculateSHA256(data []byte) string {
    hash := sha256.Sum256(data)
    return fmt.Sprintf("%x", hash)
}

在上述代码中,calculateSHA256 函数使用 sha256.Sum256 计算数据的 SHA - 256 哈希值,并将其格式化为十六进制字符串。

数字签名

数字签名用于验证消息的完整性和发送者的身份。它结合了哈希函数和非对称加密。发送者使用私钥对消息的哈希值进行签名,接收者使用公钥验证签名。

生成数字签名

以下是生成 RSA 数字签名的代码:

package main

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

func signData(data []byte, privateKeyPath string) ([]byte, error) {
    privateKeyFile, err := os.ReadFile(privateKeyPath)
    if err!= nil {
        return nil, err
    }

    block, _ := pem.Decode(privateKeyFile)
    privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
    if err!= nil {
        return nil, err
    }

    hasher := sha256.New()
    hasher.Write(data)
    hashed := hasher.Sum(nil)

    signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hashed)
    if err!= nil {
        return nil, err
    }

    return signature, nil
}

signData 函数读取私钥文件,计算数据的 SHA - 256 哈希值,然后使用私钥对哈希值进行签名。

验证数字签名

验证签名的代码如下:

func verifySignature(data, signature []byte, publicKeyPath string) bool {
    publicKeyFile, err := os.ReadFile(publicKeyPath)
    if err!= nil {
        return false
    }

    block, _ := pem.Decode(publicKeyFile)
    publicKey, err := x509.ParsePKIXPublicKey(block.Bytes)
    if err!= nil {
        return false
    }

    rsaPublicKey := publicKey.(*rsa.PublicKey)

    hasher := sha256.New()
    hasher.Write(data)
    hashed := hasher.Sum(nil)

    err = rsa.VerifyPKCS1v15(rsaPublicKey, crypto.SHA256, hashed, signature)
    return err == nil
}

verifySignature 函数读取公钥文件,计算数据的哈希值,然后使用公钥验证签名是否有效。

基于第三方库的加密解密应用:JWE 和 JWS

JSON Web 加密(JWE)

gopkg.in/square/go-jose.v2 库提供了 JWE 的实现。以下是一个简单的 JWE 加密示例:

package main

import (
    "fmt"
    "gopkg.in/square/go-jose.v2"
    "gopkg.in/square/go-jose.v2/jwt"
)

func encryptJWE(plaintext string, key []byte) (string, error) {
    signer, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.HS256, Key: key}, nil)
    if err!= nil {
        return "", err
    }

    token, err := jwt.Signed(signer).Claims(jwt.Claims{
        "payload": plaintext,
    }).CompactSerialize()
    if err!= nil {
        return "", err
    }

    return token, nil
}

上述代码使用 HS256 算法对包含明文的 JWT 进行签名,实现了简单的 JWE 功能。

JSON Web 签名(JWS)

同样使用 gopkg.in/square/go-jose.v2 库实现 JWS:

func verifyJWS(token string, key []byte) (bool, error) {
    parsed, err := jwt.ParseSigned(token)
    if err!= nil {
        return false, err
    }

    var claims jwt.Claims
    err = parsed.Claims(jose.SigningKey{Algorithm: jose.HS256, Key: key}, &claims)
    if err!= nil {
        return false, err
    }

    return true, nil
}

verifyJWS 函数用于验证 JWS 签名的有效性,它解析 JWT 并验证签名。

安全注意事项

在使用加密解密库时,有一些重要的安全注意事项需要牢记。

密钥管理

密钥是加密解密的核心,必须妥善管理。密钥应该足够随机且长度足够,避免使用可预测的密钥。对于对称加密,密钥的泄露会导致数据完全暴露;对于非对称加密,私钥的保护至关重要。密钥应该存储在安全的地方,如硬件安全模块(HSM)。

填充方式

在对称加密中,填充方式的选择很重要。不同的填充方式有不同的特点和适用场景。PKCS7 填充是一种常用的方式,但要确保在加密和解密过程中使用相同的填充方式。

算法选择

根据具体的应用场景选择合适的加密算法。对于大量数据的加密,对称加密算法如 AES 更为合适;对于身份验证和密钥交换,非对称加密算法如 RSA 更合适。同时要关注算法的安全性,避免使用已被证明不安全的算法,如 MD5 已不适合用于安全敏感的场景。

随机数生成

在加密过程中,如密钥生成、IV(初始化向量)生成等,需要使用安全的随机数生成器。Go 语言的 crypto/rand 包提供了安全的随机数生成功能,应避免使用普通的伪随机数生成器。

通过深入理解 Go 语言的加密解密库,并遵循安全最佳实践,我们能够构建安全可靠的应用程序,保护数据的机密性、完整性和可用性。无论是处理用户敏感信息,还是进行安全的通信,掌握这些技术都是至关重要的。