随着网络游戏防沉迷系统的接入,实名认证验证也是一个必不可少的部分,网上有第三方的实名认证但基本上都收费,国家新闻出版署提供了免费的接口,不过需要自己去提交申请,获取应用标识 (APPID)应用密钥 (Secret Key)业务权限标识(BizId),并将请求服务器的IP设为白名单后,就可以调用https:// api.wlc.nppa.gov.cn/idcard/authentication/check接口来获取实名认真了。

这个接口中请求体body使用了AES-128/GCM + BASE64算法加密,签名使用了sha256。直接上完整的代码

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"sort"
	"strconv"
	"time"
)

// 国家新闻出版署配置信息
type nppaInfo struct {
	AppId     string // 应用标识
	SecretKey string // 应用密钥
	BizId     string // 业务权限标识
}

var nppa nppaInfo

// 输入国家新闻出版署配置信息
func NppaInit(appId, secretKey, bizId string) {
	nppa = nppaInfo{
		AppId:     appId,
		SecretKey: secretKey,
		BizId:     bizId,
	}
}

// 实名验证
// ai 	- 游戏内部成员标识,固定32位字符,一般使用 md5(用户ID)
// name - 实名信息中的姓名
// id 	- 实名信息中身份证号码
// 返回
// 		- result 是否验证成功
// 		- errcode 状态码
// 		- errmsg 状态描述
func NppaCheck(ai, name, id string) (result bool, errcode int, errmsg string ) {
	client := &http.Client{}

	// body 参数
	param := map[string]string{
		"ai":    ai,
		"name":  name,
		"idNum": id,
	}

	// AES-128/GCM + BASE64算法加密
	jsonParam, _ := json.Marshal(param)
	cipher, _ := gcmEncrypt(string(jsonParam))
	body, _ := json.Marshal(map[string]string{
		"data": cipher,
	})

	// post请求地址
	req, _ := http.NewRequest("POST", "https://api.wlc.nppa.gov.cn/idcard/authentication/check", bytes.NewReader(body))

	// header头验证信息
	headers := map[string]string{
		"appId":      nppa.AppId,
		"bizId":      nppa.BizId,
		"timestamps": strconv.FormatInt(time.Now().UnixNano()/1e6, 10),
	}

	// 签名
	headers["sign"] = sign(headers, string(body))
	headers["Content-Type"] = "application/json;charset=utf-8"
	for k, v := range headers {
		req.Header.Set(k, v)
	}

	// 请求
	resp, err := client.Do(req)
	if err != nil {
		return false, -1, "Http请求错误"
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return false, resp.StatusCode, "Http请求返回码错误"
	}

	str, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return false, -2, "Http请求返回数据为空"
	}

	var msg map[string]interface{}
	err = json.Unmarshal(str, &msg)
	if err != nil {
		return false, -3, "Http请求返回数据格式错误"
	}

	ec, ok := msg["errcode"]
	if !ok {
		return false, -3, "Http请求返回数据格式错误"
	}

	errcode = int(ec.(float64))
	if errcode != 0 {
		errmsg, _:= msg["errmsg"]
		return false, errcode, errmsg.(string)
	}

	data, ok := msg["data"]
	if !ok {
		return false, -3, "Http请求返回数据格式错误"
	}

	r, ok := data.(map[string]interface{})["result"]
	if !ok {
		return false, -3, "Http请求返回数据格式错误"
	}

	status, ok := r.(map[string]interface{})["status"]
	if !ok {
		return false, -3, "Http请求返回数据格式错误"
	}

	errcode = int(status.(float64))
	if errcode == 0{
		return true, 0, "认证成功"
	} else if errcode == 1 {
		return false, errcode, "认证中"
	}else {
		return false, errcode, "认证失败"
	}
}

func gcmEncrypt(originalText string) (string, error) {
	// 需要解码
	key, _ := hex.DecodeString(nppa.SecretKey)
	block, err := aes.NewCipher(key)
	if err != nil {
		return "", err
	}

	aesGcm, err := cipher.NewGCM(block)
	if err != nil {
		return "", err
	}

	// 向量
	nonce := make([]byte, aesGcm.NonceSize())
	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
		return "", err
	}

	cipherText := aesGcm.Seal(nonce, nonce, []byte(originalText), nil)

	// encode as base64 string
	encoded := base64.StdEncoding.EncodeToString(cipherText)
	return encoded, nil
}

func sign(headers map[string]string, body string) string {
	var data string
	var keys []string
	// key排序
	for k := range headers {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	// 拼接
	for _, k := range keys {
		data = data + k + headers[k]
	}
	data = nppa.SecretKey + data + body

	// 对字符串进行sha256哈希
	h := sha256.New()
	h.Write([]byte(data))
	sum := h.Sum(nil)
	return hex.EncodeToString(sum)
}

调用

func main() {
	// 初始化
	NppaInit("test-appId","2836e95fcd10e04b0069bb1ee659955b","test-bizId")

	// 使用
	userId := "123456"
	name := "岑吾"
	id := "xxxxxxxxxxxxxxxxxx"
	h := md5.New()
	h.Write([]byte(userId))
	ok,code,msg := NppaCheck(hex.EncodeToString(h.Sum(nil)), name, id)
	fmt.Println(ok,code,msg)
}

详细返回参数可见网络游戏防沉迷实名认证系统