将 PHP AES 加密移植到 Golang
Porting PHP AES encryption to Golang
我的电子商务提供商在 PHP、Java、JavaScript、C# 和 Python 中有这个库来加密我的请求,因为我的 API是用Go做的,我自然而然地想,为什么不用Go呢?
哦,天哪……我不知道自己在做什么。
这是原始的 PHP 代码:
class AesCrypto {
/**
* Encrypt string with a given key
* @param strToEncrypt
* @param key
* @return String encrypted string
*/
public static function encrypt($plaintext, $key128) {
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-128-cbc'));
$cipherText = openssl_encrypt($plaintext, 'AES-128-CBC', hex2bin($key128), 1, $iv);
return base64_encode($iv.$cipherText);
}
}
我用 Go 尝试了几种略有不同的方法,我想最低限度是这样的:
func encrypt(text string, key string) string {
data := []byte(text)
block, _ := aes.NewCipher([]byte(key))
gcm, err := cipher.NewGCM(block)
if err != nil {
panic(err.Error())
}
nonce := make([]byte, gcm.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
panic(err.Error())
}
ciphertext := gcm.Seal(nonce, nonce, data, nil)
encoded := base64.StdEncoding.EncodeToString([]byte(ciphertext))
return encoded
}
我创建了这个函数来加密和解密,它们工作正常,但是当我将它发送给我的提供商时它不起作用。
key
是由电子商务提供商分配的,长度为 32 个字节,我知道长度“告诉”newCipher
到 select AES-256,对吗?那么它将永远不会对应于 AES-128,如 PHP 函数中所示。
除了检查我的电子商务提供商的服务或尝试使用 PHP 代码解密外,我该如何移植此 PHP 代码?
这是另一个尝试(来自 Go 加密文档):
func encrypt4(text string, keyString string) string {
key, _ := hex.DecodeString(keyString)
plaintext := []byte(text)
if len(plaintext)%aes.BlockSize != 0 {
panic("plaintext is not a multiple of the block size")
}
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
ciphertext := make([]byte, aes.BlockSize+len(plaintext))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
panic(err)
}
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)
final := base64.StdEncoding.EncodeToString(ciphertext)
return final
}
GCM 与 CBC 模式不同。密钥是十六进制编码的,所以一个 32 字节的字符串代表一个 16 字节(或 128 位)的密钥。
在 CBC 模式下,必须填充明文,使其成为块大小的倍数。 PHP 的 openssl_encrypt 自动执行此操作(使用 PKCS#5/7),但在 Go 中必须明确执行。
将所有内容放在一起,我们最终得到 CBC encryption example in the docs:
的细微变化
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"encoding/hex"
"io"
)
func encrypt(plaintext, key16 string) string {
padded := pkcs7pad([]byte(plaintext), aes.BlockSize)
key, err := hex.DecodeString(key16)
if err != nil {
panic(err)
}
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
buffer := make([]byte, aes.BlockSize+len(padded)) // IV followed by ciphertext
iv, ciphertext := buffer[:aes.BlockSize], buffer[aes.BlockSize:]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
panic(err)
}
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(ciphertext, padded)
return base64.StdEncoding.EncodeToString(buffer)
}
func pkcs7pad(plaintext []byte, blockSize int) []byte {
padding := blockSize - len(plaintext)%blockSize
return append(plaintext, bytes.Repeat([]byte{byte(padding)}, padding)...)
}
我的电子商务提供商在 PHP、Java、JavaScript、C# 和 Python 中有这个库来加密我的请求,因为我的 API是用Go做的,我自然而然地想,为什么不用Go呢?
哦,天哪……我不知道自己在做什么。
这是原始的 PHP 代码:
class AesCrypto {
/**
* Encrypt string with a given key
* @param strToEncrypt
* @param key
* @return String encrypted string
*/
public static function encrypt($plaintext, $key128) {
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-128-cbc'));
$cipherText = openssl_encrypt($plaintext, 'AES-128-CBC', hex2bin($key128), 1, $iv);
return base64_encode($iv.$cipherText);
}
}
我用 Go 尝试了几种略有不同的方法,我想最低限度是这样的:
func encrypt(text string, key string) string {
data := []byte(text)
block, _ := aes.NewCipher([]byte(key))
gcm, err := cipher.NewGCM(block)
if err != nil {
panic(err.Error())
}
nonce := make([]byte, gcm.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
panic(err.Error())
}
ciphertext := gcm.Seal(nonce, nonce, data, nil)
encoded := base64.StdEncoding.EncodeToString([]byte(ciphertext))
return encoded
}
我创建了这个函数来加密和解密,它们工作正常,但是当我将它发送给我的提供商时它不起作用。
key
是由电子商务提供商分配的,长度为 32 个字节,我知道长度“告诉”newCipher
到 select AES-256,对吗?那么它将永远不会对应于 AES-128,如 PHP 函数中所示。
除了检查我的电子商务提供商的服务或尝试使用 PHP 代码解密外,我该如何移植此 PHP 代码?
这是另一个尝试(来自 Go 加密文档):
func encrypt4(text string, keyString string) string {
key, _ := hex.DecodeString(keyString)
plaintext := []byte(text)
if len(plaintext)%aes.BlockSize != 0 {
panic("plaintext is not a multiple of the block size")
}
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
ciphertext := make([]byte, aes.BlockSize+len(plaintext))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
panic(err)
}
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)
final := base64.StdEncoding.EncodeToString(ciphertext)
return final
}
GCM 与 CBC 模式不同。密钥是十六进制编码的,所以一个 32 字节的字符串代表一个 16 字节(或 128 位)的密钥。
在 CBC 模式下,必须填充明文,使其成为块大小的倍数。 PHP 的 openssl_encrypt 自动执行此操作(使用 PKCS#5/7),但在 Go 中必须明确执行。
将所有内容放在一起,我们最终得到 CBC encryption example in the docs:
的细微变化package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"encoding/hex"
"io"
)
func encrypt(plaintext, key16 string) string {
padded := pkcs7pad([]byte(plaintext), aes.BlockSize)
key, err := hex.DecodeString(key16)
if err != nil {
panic(err)
}
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
buffer := make([]byte, aes.BlockSize+len(padded)) // IV followed by ciphertext
iv, ciphertext := buffer[:aes.BlockSize], buffer[aes.BlockSize:]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
panic(err)
}
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(ciphertext, padded)
return base64.StdEncoding.EncodeToString(buffer)
}
func pkcs7pad(plaintext []byte, blockSize int) []byte {
padding := blockSize - len(plaintext)%blockSize
return append(plaintext, bytes.Repeat([]byte{byte(padding)}, padding)...)
}