Go基于crypto库实现AES封装加密以及协同PHP8 使用openssl AES加密使用

前言

要实现Go与PHP 8之间的AES加密协同工作,我们需要确保两端使用相同的加密模式、密钥长度、以及密钥和初始化向量(IV)。下面,我将提供一个详细的教程,说明如何在Go中使用crypto/aescrypto/cipher库来实现AES加密,并在PHP 8中使用OpenSSL来解密这些数据(反之亦然)。
在这里插入图片描述

Go基于基础Crypto库实现AES封装加密

在Go语言中,使用标准库crypto/aescrypto/cipher可以实现AES加密的封装。以下是一个使用AES-CBC模式进行加密和解密的简单示例。AES-CBC(Cipher Block Chaining)模式是一种常见的加密模式,它使用前一个密文块来加密下一个明文块,从而增强了加密的安全性。

首先,你需要安装Go环境。然后,你可以按照以下步骤创建一个AES加密和解密的封装:

  1. 生成AES密钥和IV(初始化向量):AES密钥的长度通常是16(AES-128)、24(AES-192)或32(AES-256)字节。IV也应该是与块大小相同(对于AES,块大小是16字节)。

  2. 加密过程:使用cipher.NewCBCEncrypter创建一个加密器,并使用它来加密数据。

  3. 解密过程:使用cipher.NewCBCDecrypter创建一个解密器,并使用它来解密数据。

以下是完整的示例代码:

package main

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"io"
)

// Encrypt 使用AES CBC模式加密数据
func Encrypt(plaintext, key []byte) (ciphertext, iv []byte, err error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, nil, err
	}

	// 生成随机的IV
	iv = make([]byte, aes.BlockSize)
	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
		return nil, nil, err
	}

	// 加密
	ciphertext = make([]byte, aes.BlockSize+len(plaintext))
	mode := cipher.NewCBCEncrypter(block, iv)
	mode.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)

	// 返回IV和密文
	return ciphertext, iv, nil
}

// Decrypt 使用AES CBC模式解密数据
func Decrypt(ciphertext, key, iv []byte) (plaintext []byte, err error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}

	if len(ciphertext) < aes.BlockSize {
		return nil, fmt.Errorf("ciphertext too short")
	}
	if len(ciphertext)%aes.BlockSize != 0 {
		return nil, fmt.Errorf("ciphertext is not a multiple of the block size")
	}

	// 解密
	plaintext = make([]byte, len(ciphertext)-aes.BlockSize)
	mode := cipher.NewCBCDecrypter(block, iv)
	mode.CryptBlocks(plaintext, ciphertext[aes.BlockSize:])

	// 去除填充(这里假设使用了PKCS#7填充)
	plaintext = PKCS7Unpad(plaintext)

	return plaintext, nil
}

// PKCS7Unpad removes the PKCS#7 padding from a slice of bytes.
func PKCS7Unpad(data []byte) []byte {
	length := len(data)
	unpadding := int(data[length-1])
	return data[:(length - unpadding)]
}

func main() {
	key := []byte("this is a key123") // 应使用更安全的密钥生成方法
	plaintext := []byte("hello world")

	ciphertext, iv, err := Encrypt(plaintext, key)
	if err != nil {
		panic(err)
	}

	fmt.Printf("IV: %s\nCiphertext: %s\n", hex.EncodeToString(iv), hex.EncodeToString(ciphertext))

	decryptedText, err := Decrypt(ciphertext, key, iv)
	if err != nil {
		panic(err)
	}

	fmt.Println("Decrypted text:", string(decryptedText))
}

注意

  • 密钥key应该是一个安全的随机字节序列,上面的示例中仅用于演示。
  • IV(初始化向量)每次加密时都应该是随机的,以确保加密的安全性。
  • 在实际应用中,你应当确保密文、密钥和IV都安全地存储和传输。
  • 解密函数中的PKCS7Unpad用于去除加密时可能添加的填充,以还原原始数据。这个例子假设使用了PKCS#7填充,这是AES加密中常用的填充方式。

其他事项

当然,我们可以继续讨论关于AES加密在Go语言中的实现,特别是关于如何更健壮地处理错误、如何管理密钥和IV的安全存储,以及如何在网络应用中安全地传输这些数据。

密钥和IV的管理
  • 密钥生成:在实际应用中,你应该使用安全的随机数生成器来生成密钥。Go的crypto/rand包提供了这样的功能。
  • 密钥存储:密钥应该安全地存储在硬件安全模块(HSM)或经过加密的数据库中,而不是硬编码在源代码中。
  • IV的传输:IV不需要保密,但它必须与密文一起安全地传输到解密方,以便解密过程能够正确进行。
传输&模式和性能
  • 加密数据的传输:当加密数据需要在网络上传输时,你应该使用TLS(传输层安全性协议)或类似的协议来保护数据的机密性和完整性。TLS会加密整个传输层的数据包,从而保护你的加密数据不被中间人攻击。

  • 加密模式的选择:除了CBC模式外,AES还支持其他几种模式,如ECB(电子密码本模式)、CFB(密码反馈模式)、OFB(输出反馈模式)和CTR(计数器模式)等。每种模式都有其特定的用途和安全性考虑。CBC模式是最常用的模式之一,因为它结合了加密和完整性校验的功能(尽管不是强校验)。然而,对于某些应用场景,你可能需要选择其他模式。

  • 填充和去填充:在上面的示例中,我们使用了PKCS#7填充来确保数据块的大小符合AES的要求。在解密时,我们需要去除这个填充。但是,如果密文在传输过程中被篡改,去填充过程可能会失败。因此,你应该在解密过程中检查并处理这种潜在的错误。

  • 性能考虑:AES加密是一个计算密集型的操作,特别是在处理大量数据时。因此,在性能敏感的应用中,你可能需要考虑使用硬件加速(如AES-NI指令集)或优化你的加密和解密逻辑。

示例扩展

下面是一个扩展的示例,它包括了更详细的错误处理和日志记录(假设你有一个日志库):

package main

import (
	// ... 导入必要的包
	"log"
)

func main() {
	// ... 生成密钥和IV

	plaintext := []byte("sensitive data")
	ciphertext, iv, err := Encrypt(plaintext, key)
	if err != nil {
		log.Fatalf("Failed to encrypt data: %v", err)
	}

	// ... 将ciphertext和iv安全地存储或传输

	// 假设我们稍后需要解密
	decryptedText, err := Decrypt(ciphertext, key, iv)
	if err != nil {
		log.Fatalf("Failed to decrypt data: %v", err)
	}

	log.Printf("Decrypted text: %s", decryptedText)
}

// ... Encrypt 和 Decrypt 函数的实现(与上面相同)

// LogError 是一个简单的错误日志记录函数(假设)
func LogError(err error) {
	// 使用你的日志库来记录错误
	log.Printf("Error: %v", err)
}

PHP端 OpenSSL 解密

在PHP端,我们使用OpenSSL函数来解密由Go生成的AES加密数据。
在这里插入图片描述

<?php

function decryptAES($ciphertext, $key) {
    // Base64解码
    $ciphertextDec = base64_decode($ciphertext);

    // 解密
    $decrypted = openssl_decrypt($ciphertextDec, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, substr($ciphertextDec, 0, 16));

    // 去除PKCS#7填充
    $length = strlen($decrypted);
    $padding = ord($decrypted[$length - 1]);
    $decrypted = substr($decrypted, 0, -$padding);

    return $decrypted;
}

$key = "your-32-bytes-long-secret-key-here";
$ciphertext = "..."; // 这里是从Go端得到的Base64加密字符串

$decrypted = decryptAES($ciphertext, $key);
echo "Decrypted text: " . $decrypted . PHP_EOL;

注意事项

  1. 密钥和IV:确保Go和PHP端使用的密钥和IV(特别是IV)完全相同。在上面的Go示例中,IV是随机生成的,但在实际应用中,如果需要在两端解密相同的数据,IV必须一致。如果只是想简单地演示加密和解密,可以将IV作为加密函数的一部分返回给PHP端。

  2. 错误处理:在生产环境中,应该增加更多的错误处理逻辑来确保加密和解密操作的健壮性。

  3. 填充:这里使用了PKCS#7填充(Go中的PKCS7Padding和PHP中的手动去除填充)。确保两端使用相同的填充机制。

  4. Base64编码:加密后的数据通常是二进制的,不适合直接作为字符串传输或存储。Go示例中使用了Base64编码,PHP示例中相应地进行了Base64解码。

通过以上步骤,你应该能够在确保Go和PHP 8之间的AES加密协同工作无缝进行。我们将重点关注如何在需要时传递IV(初始化向量),因为IV在加密和解密过程中必须是相同的。

Go端:修改以返回IV

在Go的加密函数中,我们可以修改它以返回IV,这样PHP端就可以使用这个IV来解密数据。

package main

import (
    "bytes"
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "encoding/base64"
    "fmt"
    "io"
)

// ... (PKCS7Padding 和 PKCS7Unpadding 函数保持不变)

// EncryptAESWithIV 加密数据并返回IV
func EncryptAESWithIV(plaintext, key []byte) (string, []byte, error) {
    block, err := aes.NewCipher(key)
    if err != nil {
        return "", nil, err
    }

    // 生成IV
    iv := make([]byte, aes.BlockSize)
    if _, err := io.ReadFull(rand.Reader, iv); err != nil {
        return "", nil, err
    }

    // 加密
    mode := cipher.NewCBCEncrypter(block, iv)
    ciphertext := make([]byte, aes.BlockSize+len(plaintext))
    mode.CryptBlocks(ciphertext[aes.BlockSize:], PKCS7Padding(plaintext, aes.BlockSize))
    copy(ciphertext[:aes.BlockSize], iv)

    // 转换为Base64
    ciphertextBase64 := base64.StdEncoding.EncodeToString(ciphertext)

    return ciphertextBase64, iv, nil
}

func main() {
    key := []byte("your-32-bytes-long-secret-key-here")
    plaintext := []byte("Hello, world!")

    encrypted, iv, err := EncryptAESWithIV(plaintext, key)
    if err != nil {
        panic(err)
    }

    fmt.Println("Encrypted (Base64):", encrypted)
    fmt.Println("IV (Base64):", base64.StdEncoding.EncodeToString(iv)) // 可选:将IV也转换为Base64方便查看或传输

    // 在实际应用中,你需要将encrypted和iv(可能是Base64编码的)发送给PHP端
}

PHP端:使用提供的IV进行解密

在PHP端,你现在将接收到加密后的数据和IV(可能是Base64编码的),你需要先解码IV,然后使用它来解密数据。

<?php

function decryptAESWithIV($ciphertext, $key, $ivBase64) {
    // Base64解码IV
    $iv = base64_decode($ivBase64);

    // Base64解码加密数据
    $ciphertextDec = base64_decode($ciphertext);

    // 解密
    $decrypted = openssl_decrypt($ciphertextDec, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);

    // 去除PKCS#7填充
    $length = strlen($decrypted);
    $padding = ord($decrypted[$length - 1]);
    $decrypted = substr($decrypted, 0, -$padding);

    return $decrypted;
}

$key = "your-32-bytes-long-secret-key-here";
$ciphertext = "..."; // 从Go端接收的Base64加密字符串
$ivBase64 = "...";   // 从Go端接收的Base64编码的IV

$decrypted = decryptAESWithIV($ciphertext, $key, $ivBase64);
echo "Decrypted text: " . $decrypted . PHP_EOL;

注意事项

  • IV的传输:IV不需要保密,但它必须与加密时使用的IV完全相同。因此,你可以安全地将IV与加密数据一起发送给解密方。
  • 安全性:确保你的密钥(key)是安全的,并且只在你信任的系统之间共享。
  • 错误处理:在PHP端,你可能还想添加一些错误处理逻辑来检查openssl_decrypt是否成功执行。
  • 测试:在生产环境中部署之前,请确保在多种情况下测试你的加密和解密逻辑,以确保其可靠性和安全性。

总结

我们在日常开发中,会经常遇到和其他平台数据做对接,接口与接口对接过程中,除了必要签名授权外,我们还需要对敏感数据进行加密,使用的比较多就是AES对称加密,但是每个系统之间的开发语言并非都是一致的,不同的业务系统使用的编程语言都是各异,所以在加解密的过程中,我们要协调双方的加密方式,密钥和IV以及填充方式等,以上就用Go和PHP做对比,使用AES CBC模式进行加密处理,确保双方的系统交互没有信息泄露的问题,当然Java、Python、C等其他语言,都是可以,这里就不一一举例了(重点我对这些语言也不是很熟,哈哈),但是在实际应用中,你可能还需要考虑其他安全因素,如使用HTTPS来保护数据传输过程中的安全等。好了,本篇内容到此结束。

  • 14
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bobo-rs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值