# 数据加解密

对于敏感数据,小游戏会对数据进行加密,开发者需要在服务端进行解密。

# 解密算法

  • 对称解密使用的算法为 AES-128-CBC,数据采用 PKCS#7 填充。
  • 对称解密的目标密文为 Base64_Decode(encryptedData)。
  • 对称解密秘钥 aeskey = Base64_Decode(session_key), aeskey 是 16 字节。
  • 对称解密算法初始向量为 Base64_Decode(iv),其中 iv 由数据接口返回。

# 解密步骤

  1. 对 encryptedData 进行 Base64 解码,得到密文数据
  2. 对 iv 进行 Base64 解码,得到初始向量
  3. 对 session_key 进行 Base64 解码,得到 AES 密钥
  4. 使用 AES-128-CBC 模式和 PKCS#7 填充方式,以及上述密钥和初始向量对密文进行解密
  5. 解析解密后的 JSON 数据

# 示例代码(Golang)

import (
	"crypto/aes"
	"crypto/cipher"
	"encoding/base64"
	"encoding/json"
	"fmt"
)

type DataCrypt struct {
	AppID      string
	SessionKey string
}

func NewDataCrypt(miniAppID, sessionKey string) *DataCrypt {
	return &DataCrypt{
		AppID:      miniAppID,
		SessionKey: sessionKey,
	}
}

func (w *DataCrypt) DecryptData(encryptedData, iv string) (string, error) {
	if w.SessionKey == "" {
		return "", fmt.Errorf("ErrIllegalAesKey")
	}
	if encryptedData == "" {
		return "", fmt.Errorf("ErrIllegalBuffer")
	}
	if iv == "" {
		return "", fmt.Errorf("ErrIllegalIv")
	}

	aesKey, err := base64.StdEncoding.DecodeString(w.SessionKey)
	if err != nil {
		return "", fmt.Errorf("DecodeBase64")
	}

	aesIV, err := base64.StdEncoding.DecodeString(iv)
	if err != nil {
		return "", fmt.Errorf("DecodeBase64")
	}

	if len(aesKey) != 16 && len(aesKey) != 32 {
		return "", fmt.Errorf("ErrIllegalAesKey")
	}

	if len(aesIV) != 16 {
		return "", fmt.Errorf("ErrIllegalIv")
	}
	
	aesCipher, err := base64.StdEncoding.DecodeString(encryptedData)
	if err != nil {
		return "", fmt.Errorf("DecodeBase64")
	}
	if len(aesCipher) == 0 || len(aesCipher)%16 != 0 {
		return "", fmt.Errorf("ErrIllegalBuffer")
	}
	
	block, err := aes.NewCipher(aesKey)
	if err != nil {
		return "", fmt.Errorf("IllegalAesKey")
	}

	blockMode := cipher.NewCBCDecrypter(block, aesIV)
	decrypted := make([]byte, len(aesCipher))
	blockMode.CryptBlocks(decrypted, aesCipher)

	unpaddedData, err := pkcs7Unpad(decrypted)
	if err != nil {
		return "", err
	}

	var dataObj map[string]interface{}
	err = json.Unmarshal(unpaddedData, &dataObj)
	if err != nil {
		return "", fmt.Errorf("IllegalBuffer")
	}

	watermark, ok := dataObj["watermark"].(map[string]interface{})
	if !ok {
		return "", fmt.Errorf("IllegalBuffer")
	}

	appIDFromWatermark, ok := watermark["appid"].(string)
	if !ok || appIDFromWatermark != w.AppID {
		return "", fmt.Errorf("IllegalBuffer")
	}

	return string(unpaddedData), nil
}

func pkcs7Unpad(data []byte) ([]byte, error) {
	length := len(data)
	if length == 0 {
		return nil, fmt.Errorf("pkcs7: input data is empty")
	}
	unpadding := int(data[length-1])
	for i := length - unpadding; i < length; i++ {
		if data[i] != byte(unpadding) {
			return nil, fmt.Errorf("pkcs7: invalid padding")
		}
	}
	return data[:(length - unpadding)], nil
}

func DecryptDemo() (string, error){
	miniAppID := "tapmxxxxxxxx"
	sessionKey := "xxxxxx"
	encryptedData := "xxxxxx"
	iv := "xxxxxx"

	pc := NewDataCrypt(miniAppID, sessionKey)
	return pc.DecryptData(encryptedData, iv)
}