【知识点】
包含php的知识点:str_pad、sha1、pack、base64_encode、base64_decode、strlen、substr、openssl_decrypt、openssl_encrypt。
包含的go知识点:
sha1 哈希加密,要注意下返回的是字节数组
string([]byte) 字节数组转换成字符串,注意这里转换的二进制字符串,很多乱码
hex.EncodeToString 字节数组转成16进制,示例中有用到来转换sha1得到的值
strconv.ParseUint 字符串转换为无符号int类型,对应php的pack方法
base64.StdEncoding.DecodeString base64解码,对应php的base64_decode
base64.StdEncoding.EncodeString base64编码,对应php的base64_encode
strings.Replace 替换字符串,用于替换base64字符串的安全字符-和_
rand 随机字符串
crypto/cipher aes加解密模块
参考:aes-256-gcm_python3_php7_golang_mb5fe94870638be的技术博客_51CTO博客
【需求背景】
在对接美团小游戏的时候,支付成功通知,使用了【sha1】的签名还有【aes-256-gcm】的加密数据。官方只有php7.1以上的例子,但是项目使用了golang,需要转换下。
接口文档:
文档上的php-demo例子:
//PHP demo(版本使用7.1以上)
/**
* 生成密钥
* $appId:开发者后台看
* $appSecret:开发者后台看
*/
function createKey($appId, $appSecret) {
$aaa = base64_encode(str_pad(sha1($appId.'&'.$appSecret,true), 32,pack('V', 0)));
return $aaa;
}
/**
* 签名sha1算法
* $secretKey: 由上面的 createKey方法生成
* $encryptData 通知报文的data密文内容
*/
function encryptSHA1Str($secretKey, $encryptData) {
$aaa = $encryptData.$secretKey;
$sign = sha1($aaa);
return $sign;
}
/**
* AES-GCM-256解密算法
* $secretKey: 由上面的 createKey方法生成
* $encryptData 通知报文的data密文内容
*/
function decryptWithAESGCM256($secretKey, $encryptData) {
$decodeEncrypt = urlsafe_b64decode($encryptData);
$decodeSecret = base64_decode($secretKey);
$data = openssl_decrypt(substr($decodeEncrypt, 16, -16), 'aes-256-gcm', $decodeSecret, 1, substr($decodeEncrypt, 0, 16), substr($decodeEncrypt, -16, 16));
return $data;
}
function urlsafe_b64decode($string) {
$data = str_replace(array('-','_'),array('+','/'),$string);
$mod4 = strlen($data) % 4;
if ($mod4) {
$data .= substr('====', $mod4);
}
return base64_decode($data);
}
【GO代码示例】
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/sha1"
"encoding/base64"
"errors"
"fmt"
"io"
"math/rand"
"strconv"
"strings"
)
func main(){
secretKey := GenSecretKey("appid12456","appsecret123456")
data :="{\"bizOrderId\":\"55001151519674220220723112146446\",\"mgcId\":\"234112419424942\",\"mgcOrderId\":\"106631144674\",\"payStatus\":\"OK\",\"price\":\"100\"}\n"
encryptData,_ := SetCallbackData(secretKey,data) //得到加密后的base64字符串
fmt.Println(encryptData)
decryptData,_ := GetCallbackData(secretKey,encryptData) //得到解密后的json字符串
fmt.Println(decryptData)
}
//实现php的sha1()方法,返回的是byte转换的字符串,如果需要转成16进制,可以使用hex.EncodeToString
func GetSha1(str string) string {
h := sha1.New()
io.WriteString(h, str)
return string(h.Sum(nil))
}
//右边补全字符串实现方法,主要实现php的str_pad()方法
func StrPadRight(input string, padLength int, padString string) string {
output := ""
inputLen := len(input)
if inputLen >= padLength {
return input
}
ll := padLength - inputLen
for i := 1; i <= ll; i = i + len(padString) {
output += padString
}
return input + output
}
//生成密钥
func GenSecretKey(appId string, appSecret string) (secretKey string) {
key := appId + "&" + appSecret
sha1_str := GetSha1(key)
str10 := "0"
pack64, _ := strconv.ParseUint(str10, 10, 32) //对应php的pack()方法,字符串转换为uint类型
fmt.Println(pack64)
pack32 := uint32(pack64)
str_pad := StrPadRight(sha1_str, 32, string(pack32))
secretKey = base64.StdEncoding.EncodeToString([]byte(str_pad))
return secretKey
}
func GetCallbackData(secretKey string, encryptData string) (result_str string, err error) {
decodeSecretKeyByte, _ := base64.StdEncoding.DecodeString(secretKey)
decodeSecretKey := string(decodeSecretKeyByte)
//$data = openssl_decrypt(substr($decodeEncrypt, 16, -16), 'aes-256-gcm', $decodeSecret, 1, substr($decodeEncrypt, 0, 16), substr($decodeEncrypt, -16, 16));
orderSuccessPayInfoByte, err2 := DecodeAesGcm(encryptData, decodeSecretKey,"")
result_str = string(orderSuccessPayInfoByte)
if err2 != nil {
return result_str, errors.New("decode error")
}
return result_str, err
}
func SetCallbackData(secretKey string, data string) (result_str string, err error) {
decodeSecretKeyByte, _ := base64.StdEncoding.DecodeString(secretKey)
decodeSecretKey := string(decodeSecretKeyByte)
orderSuccessPayInfoByte, err2 := EncodeAesGcm(data, decodeSecretKey,"")
result_str = string(orderSuccessPayInfoByte)
if err2 != nil {
return result_str, errors.New("encode error")
}
return result_str, err
}
//url安全模式decode字符串
func UrlSafeB64decode(str string) (result []byte) {
str = strings.Replace(str, "-", "+", -1)
str = strings.Replace(str, "_", "/", -1)
mod4 := len(str) % 4
if mod4 != 0 {
str = str + "===="[0:mod4]
}
result, _ = base64.StdEncoding.DecodeString(str)
return result
}
//base64字符串,替换转换为安全模式
func UrlSafeB64encode(str string) (result string) {
str = strings.Replace(str, "+", "-", -1)
str = strings.Replace(str, "/", "_", -1)
return str
}
//使用aes-256-gcm方式解密字符串,主要针对php的openssl_decrypt()方法,注意在php7.1后增加了tag和add参数
func DecodeAesGcm(encryptData string, hex_key string,hex_add string) ([]byte, error) {
tagSize :=16 //nonceSize,tag的长度,用于open时候生成tag,默认12
key := []byte(hex_key)
add := []byte(hex_add)
block, err := aes.NewCipher(key) //生成加解密用的block
if err != nil {
return []byte(""), err
}
//根据不同加密算法,也有不同tag长度的方法设定和调用,比如NewGCMWithTagSize、newGCMWithNonceAndTagSize
aesgcm, err := cipher.NewGCMWithNonceSize(block, tagSize)
if err != nil {
return []byte(""), err
}
decodeEncryptStr := UrlSafeB64decode(encryptData)
ciphertext := decodeEncryptStr
if len(ciphertext) <= aesgcm.NonceSize() { // 长度应该>iv
return []byte(""), errors.New("string: too short") //解密失败
}
iv := ciphertext[:aesgcm.NonceSize()] //分离出IV
ciphertext = ciphertext[aesgcm.NonceSize():] // 密文,tag是调用open方法时候通过密文和前面new时候传的size来进行截取的
plaintext, err := aesgcm.Open(nil, iv, ciphertext, add)
return plaintext, err
}
//使用aes-256-gcm加密数据,主要针对php的openssl_encrypt()方法,注意在php7.1后增加了tag和add参数
func EncodeAesGcm(data string, hex_key string, hex_add string) (result string, error error) {
tagSize :=16 //nonceSize,tag的长度,用于open时候生成tag,默认12
key := []byte(hex_key)
add := []byte(hex_add)
block, err := aes.NewCipher(key) //生成加解密用的block
if err != nil {
return result, err
}
//根据不同加密算法,也有不同tag长度的方法设定和调用,比如NewGCMWithTagSize、newGCMWithNonceAndTagSize
aesgcm, err := cipher.NewGCMWithNonceSize(block, tagSize)
if err != nil {
return result, err
}
plaintext := []byte(data)
iv := make([]byte, tagSize) // NonceSize=12
rand.Read(iv) //获取随机值
ciphertext := aesgcm.Seal(iv, iv, plaintext, add) //加密,密文为:iv+密文+tag
result = base64.StdEncoding.EncodeToString(ciphertext)
result = UrlSafeB64encode(result)
return result, nil // 生成的BS64
}