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创建一个解密器,并使用它来解密数据。

以下是完整的示例代码:

go 复制代码
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指令集)或优化你的加密和解密逻辑。

示例扩展

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

go 复制代码
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 复制代码
<?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来解密数据。

go 复制代码
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 复制代码
<?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来保护数据传输过程中的安全等。好了,本篇内容到此结束。

相关推荐
ServBay3 小时前
告别面条代码,PSL 5.0 重构 PHP 性能与安全天花板
后端·php
JaguarJack2 天前
FrankenPHP 原生支持 Windows 了
后端·php·服务端
BingoGo2 天前
FrankenPHP 原生支持 Windows 了
后端·php
JaguarJack3 天前
PHP 的异步编程 该怎么选择
后端·php·服务端
BingoGo3 天前
PHP 的异步编程 该怎么选择
后端·php
JaguarJack4 天前
为什么 PHP 闭包要加 static?
后端·php·服务端
ServBay5 天前
垃圾堆里编码?真的不要怪 PHP 不行
后端·php
用户962377954485 天前
CTF 伪协议
php
BingoGo7 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php
JaguarJack7 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php·服务端