登录
原创

264-人脸实名认证接口 golang 版本 AES 示例代码

发布于 2025-12-04 阅读 44
  • 后端
原创

package main

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/md5"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"strings"
)

// APIConfig API配置
type APIConfig struct {
	Key    string // API Key
	OpenID string // OpenID,用于生成加密密钥
}

// VerifyRequest 人脸对比请求参数
type VerifyRequest struct {
	IDCard   string // 身份证号码
	RealName string // 真实姓名
	Image    string // 人脸图片base64编码(不含data:image/jpg;base64,前缀)
	Thousand string // 可选,1:返回千分制分数
}

// VerifyResult 返回结果
type VerifyResult struct {
	Message string `json:"message"` // 识别说明
	Res     int    `json:"res"`     // 比对结果
	Score   string `json:"score"`   // 比对分值(res为1时存在)
	Code    string `json:"code"`    // res为2时存在
	Desc    string `json:"desc"`    // res为2时存在
}

// APIResponse API响应
type APIResponse struct {
	ErrorCode int          `json:"error_code"` // 返回码,0计费一次
	Reason    string       `json:"reason"`     // 返回说明
	Result    VerifyResult `json:"result"`     // 返回数据
}

// getAESKey 根据OpenID生成AES加密密钥
// 规则:OpenId经过Md5后取前16位,并转小写
func getAESKey(openID string) string {
	hash := md5.Sum([]byte(openID))
	hashStr := fmt.Sprintf("%x", hash) // 转换为小写16进制字符串
	return hashStr[:16]                // 取前16位
}

// aesEncrypt AES/ECB/PKCS5Padding 加密
func aesEncrypt(plaintext, key string) (string, error) {
	block, err := aes.NewCipher([]byte(key))
	if err != nil {
		return "", err
	}

	// PKCS5Padding
	plainBytes := []byte(plaintext)
	padding := aes.BlockSize - len(plainBytes)%aes.BlockSize
	padtext := append(plainBytes, strings.Repeat(string(byte(padding)), padding)...)

	// ECB 模式加密
	encrypted := make([]byte, len(padtext))
	blockMode := NewECBEncrypter(block)
	blockMode.CryptBlocks(encrypted, padtext)

	// Base64编码
	return base64.StdEncoding.EncodeToString(encrypted), nil
}

// ECB 模式实现
type ecb struct {
	b         cipher.Block
	blockSize int
}

func NewECBEncrypter(b cipher.Block) cipher.BlockMode {
	return &ecb{b: b, blockSize: b.BlockSize()}
}

func (x *ecb) BlockSize() int { return x.blockSize }

func (x *ecb) CryptBlocks(dst, src []byte) {
	if len(src)%x.blockSize != 0 {
		panic("crypto/cipher: input not full blocks")
	}
	if len(dst) < len(src) {
		panic("crypto/cipher: output smaller than input")
	}
	for len(src) > 0 {
		x.b.Encrypt(dst, src[:x.blockSize])
		src = src[x.blockSize:]
		dst = dst[x.blockSize:]
	}
}

// VerifyFace 调用人脸对比加密版API
func VerifyFace(config APIConfig, req VerifyRequest) (*APIResponse, error) {
	apiURL := "http://apis.juhe.cn/verifyface/verifyEncry"

	// 生成AES密钥
	aesKey := getAESKey(config.OpenID)

	// 加密身份证号码
	encryptedIDCard, err := aesEncrypt(req.IDCard, aesKey)
	if err != nil {
		return nil, fmt.Errorf("加密身份证号码失败: %v", err)
	}

	// 加密姓名
	encryptedRealName, err := aesEncrypt(req.RealName, aesKey)
	if err != nil {
		return nil, fmt.Errorf("加密姓名失败: %v", err)
	}

	// 加密图片base64
	encryptedImage, err := aesEncrypt(req.Image, aesKey)
	if err != nil {
		return nil, fmt.Errorf("加密图片失败: %v", err)
	}

	// 构建POST表单数据
	formData := url.Values{}
	formData.Set("key", config.Key)
	formData.Set("idcard", encryptedIDCard)
	formData.Set("realname", encryptedRealName)
	formData.Set("image", encryptedImage)
	if req.Thousand != "" {
		formData.Set("thousand", req.Thousand)
	}

	// 发送POST请求
	resp, err := http.Post(
		apiURL,
		"application/x-www-form-urlencoded",
		strings.NewReader(formData.Encode()),
	)
	if err != nil {
		return nil, fmt.Errorf("发送请求失败: %v", err)
	}
	defer resp.Body.Close()

	// 读取响应
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("读取响应失败: %v", err)
	}

	// 解析JSON响应
	var apiResp APIResponse
	if err := json.Unmarshal(body, &apiResp); err != nil {
		return nil, fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, string(body))
	}

	return &apiResp, nil
}

// imageToBase64 读取本地图片文件并转换为base64编码
func imageToBase64(imagePath string) (string, error) {
	// 读取图片文件
	imageData, err := os.ReadFile(imagePath)
	if err != nil {
		return "", fmt.Errorf("读取图片文件失败: %v", err)
	}

	// 转换为base64编码
	base64Str := base64.StdEncoding.EncodeToString(imageData)
	return base64Str, nil
}

// 解释比对结果
func explainResult(result VerifyResult) string {
	switch result.Res {
	case 1:
		return fmt.Sprintf("✓ 身份证号与姓名匹配,且比对成功。相似度分数: %s (推荐阈值≥60)", result.Score)
	case 2:
		return fmt.Sprintf("✗ 身份证核查成功,但人脸比对失败。错误码: %s, 说明: %s", result.Code, result.Desc)
	case 3:
		return "✗ 身份证号与姓名不匹配"
	default:
		return "未知结果"
	}
}

func main() {
	// 示例用法
	config := APIConfig{
		Key:    "7a312b221d9a8cce31xxxxxxxxxx",      // 请替换为你的API Key
		OpenID: "JH285250c81e065e255dxxxxxxxxxxxxx", // 请替换为你的OpenID
	}

	// 读取本地图片并转换为base64
	imagePath := "/Users/java0904/test264-go/face.jpg" // 请将图片放在项目根目录,或使用完整路径
	fmt.Printf("正在读取图片: %s\n", imagePath)
	imageBase64, err := imageToBase64(imagePath)
	if err != nil {
		fmt.Printf("读取图片失败: %v\n", err)
		return
	}
	fmt.Printf("图片读取成功,大小: %.2f KB\n", float64(len(imageBase64))/1024*3/4) // base64解码后的大小

	request := VerifyRequest{
		IDCard:   "320382xxxxxxxxxxxx19", // 请替换为实际身份证号码
		RealName: "魏xxxxxxx",             // 请替换为实际姓名
		Image:    imageBase64,            // 使用读取的图片base64编码
		Thousand: "",                     // 可选,传"1"返回千分制分数
	}

	fmt.Println("正在调用人脸对比API...")
	result, err := VerifyFace(config, request)
	if err != nil {
		fmt.Printf("调用失败: %v\n", err)
		return
	}

	// 输出结果
	fmt.Printf("\n=== API响应 ===\n")
	fmt.Printf("返回码: %d\n", result.ErrorCode)
	fmt.Printf("返回说明: %s\n", result.Reason)
	fmt.Printf("识别说明: %s\n", result.Result.Message)
	fmt.Printf("比对结果: %s\n", explainResult(result.Result))

	// 详细结果(JSON格式)
	jsonResult, _ := json.MarshalIndent(result, "", "  ")
	fmt.Printf("\n=== 完整响应(JSON) ===\n%s\n", string(jsonResult))
}

评论区

眉上的汗水,眉下的泪水,你总要选择一样

0

0

4

举报