GO-使用golang解密使用php的openssl_encrypt加密的结果以及key的长度不满足要求时的处理方法
不同语言之间的通讯尤其涉及到标准加密的情况的时候往往有很多不标准的事情闹得人焦头烂额的.今天就在处理php和go的加密通讯的时候遇到了问题.
首先来看下正常的情况下的通讯
php加密部分
<?php
$iv = "1234567890abcdef";
$key = "abcdef1234567890";
//使用OPENSSL_RAW_DATA,多一次转换,方便说明白通讯过程
echo base64_encode(openssl_encrypt("hello world", "AES-128-CBC", $key, OPENSSL_RAW_DATA, $iv));
结果
CtqXZg7SH5ACIK7gWwOu4w==
go解密部分
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"fmt"
)
func main() {
originData , _ := base64.StdEncoding.DecodeString( "CtqXZg7SH5ACIK7gWwOu4w==" )
iv := []byte("1234567890abcdef")
keyByteString := "abcdef1234567890"
cipherBlock, err := aes.NewCipher([]byte(keyByteString))
if err != nil{
fmt.Println(err)
}
cipher.NewCBCDecrypter(cipherBlock, iv).CryptBlocks(originData, originData)
fmt.Println(string(PKCS5UnPadding(originData)))
}
//这个方法是直接找的网上的实现
func PKCS5UnPadding(src []byte) []byte {
length := len(src)
unpadding := int(src[length-1])
if length - unpadding < 0 {
return []byte("")
}
return src[:(length - unpadding)]
}
解密结果
hello world
Process finished with exit code 0
说明在AES-128-CBC的时候keyByteString是16个字符这个是固定的
再看下AES-256-CBC的时候
<?php
$iv = "1234567890abcdef";
$key = "abcdef12345678901234567890abcdef";
echo base64_encode(openssl_encrypt("hello world", "AES-256-CBC", $key, OPENSSL_RAW_DATA, $iv));
结果
RxpbfHR/FnUo5Mh9NRtudQ==
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"fmt"
)
func main() {
originData , _ := base64.StdEncoding.DecodeString( "RxpbfHR/FnUo5Mh9NRtudQ==" )
iv := []byte("1234567890abcdef")
keyByteString := "abcdef12345678901234567890abcdef"
cipherBlock, err := aes.NewCipher([]byte(keyByteString))
if err != nil{
fmt.Println(err)
}
cipher.NewCBCDecrypter(cipherBlock, iv).CryptBlocks(originData, originData)
fmt.Println(string(PKCS5UnPadding(originData)))
}
//这个方法是直接找的网上的实现
func PKCS5UnPadding(src []byte) []byte {
length := len(src)
unpadding := int(src[length-1])
if length - unpadding < 0 {
return []byte("")
}
return src[:(length - unpadding)]
}
结果
hello world
Process finished with exit code 0
其中iv是固定16位,而keyByteString则变为了32位
以上这几种情况都比较简单,而比较坑的是啥呢,
1.php在key的长度不满足要求的时候也可以加密成功!
2.go则必须要求keyByteString的长度满足要求!!!
所以这几涉及到了key在php中到底是怎处理的
先看下php的源码
PHP_FUNCTION(openssl_decrypt)
{
//省略无关代码
//注意这里这里有对参数的初始化处理,那么我们就可以猜测这里有对key的处理
if (php_openssl_cipher_init(cipher_type, cipher_ctx, &mode,
&password, &password_len, &free_password,
&iv, &iv_len, &free_iv, tag, tag_len, options, 0) == FAILURE ||
php_openssl_cipher_update(cipher_type, cipher_ctx, &mode, &outbuf, &outlen,
data, data_len, aad, aad_len, 0) == FAILURE) {
RETVAL_FALSE;
}
//省略好多
}
//再来看php_openssl_cipher_init方法
static int php_openssl_cipher_init(const EVP_CIPHER *cipher_type,
EVP_CIPHER_CTX *cipher_ctx, struct php_openssl_cipher_mode *mode,
char **ppassword, size_t *ppassword_len, zend_bool *free_password,
char **piv, size_t *piv_len, zend_bool *free_iv,
char *tag, int tag_len, zend_long options, int enc) /* {{{ */
{
//又省略好多
/* check and set key */
password_len = (int) *ppassword_len;
key_len = EVP_CIPHER_key_length(cipher_type);
//就是这里了这里PHP在key的长度不满足要求的长度时会自动重新填充一个新的key出来!而go中不支持这种操作!!!这就比较坑了,而解决的办法就是通过go实现key的填充
if (key_len > password_len) {
if ((OPENSSL_DONT_ZERO_PAD_KEY & options) && !EVP_CIPHER_CTX_set_key_length(cipher_ctx, password_len)) {
php_openssl_store_errors();
php_error_docref(NULL, E_WARNING, "Key length cannot be set for the cipher method");
return FAILURE;
}
key = emalloc(key_len);
memset(key, 0, key_len);
memcpy(key, *ppassword, password_len);
*ppassword = (char *) key;
*ppassword_len = key_len;
*free_password = 1;
}
//省略好多
}
那么如何解决这个问题呢
直接看看代码吧
<?php
$iv = "1234567890abcdef";
$key = "abc";
echo base64_encode(openssl_encrypt("hello world", "AES-128-CBC", $key, OPENSSL_RAW_DATA, $iv));
结果
7s3/y57WmZt+f1Pz5KzXlg==
go 的解密处理方法
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"fmt"
)
func main() {
originData , _ := base64.StdEncoding.DecodeString( "7s3/y57WmZt+f1Pz5KzXlg==" )
iv := []byte("1234567890abcdef")
//key为abc
key := "abc"
//初始化一个空的字节数组,这里的16为AES-128-CBC的要求的key的长度
keyByte := [16]byte{}
//转换key为字节数组
keyByteTemp := []byte(key)
//依次赋值,这里的3为key的len
for i := 0; i < 3; i++ {
keyByte[i] = keyByteTemp[i]
}
fmt.Println(keyByteTemp,keyByte)
//转换依次得到的keyByteString
keyByteString := string(keyByte[:])
//解密
cipherBlock, err := aes.NewCipher([]byte(keyByteString))
if err != nil{
fmt.Println(err)
}
cipher.NewCBCDecrypter(cipherBlock, iv).CryptBlocks(originData, originData)
fmt.Println(string(PKCS5UnPadding(originData)))
}
func PKCS5UnPadding(src []byte) []byte {
length := len(src)
unpadding := int(src[length-1])
if length - unpadding < 0 {
return []byte("")
}
return src[:(length - unpadding)]
}
结果
[97 98 99] [97 98 99 0 0 0 0 0 0 0 0 0 0 0 0 0]
hello world
Process finished with exit code 0
好了这里是针对AES-128-CBC的在key的长度不满足的情况做的处理
留给读者思考的问题
1.AES-256-CBC的key不满足长度的情况怎处理
2.在AES-128-CBC的情况iv不满足长度又要怎么处理呢