【知识点】

包含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,需要转换下。

接口文档:

aes ecb128和256区别 aes-256-cfb和gcm_php

文档上的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
}