使用 C# 调用聚合数据身份证四要素校验(国密版):id_card_four_factors/sm,SM2 封装 sm4secret、SM4-CBC 加密四要素、HMAC-SM3 请求签名。业务字段为姓名、身份证号、有效期起止(yyyyMMdd)。
- 请求地址: https://apis.juhe.cn/id_card_four_factors/sm
- 加密: SM2 公钥加密「16 字节 SM4 密钥 + 16 字节 IV」拼接体 → sm4secret(Base64);四要素为 SM4-CBC 密文(Base64);HMAC-SM3 签名
- 传输: HTTP POST,Content-Type: application/x-www-form-urlencoded
四要素: 姓名、身份证号、有效期开始、有效期结束。日期为 yyyyMMdd;长期有效时结束日期为 00000000。
说明: 企业类接口,需资质;SM2 公钥向商务获取。OpenId 用于 HMAC,与参数 key(AppKey)不同。
流程摘要
- 随机 16 字节 SM4 密钥 + 16 字节 IV,拼接为 byte[],SM2 公钥加密后 Base64 → sm4secret(密文 C1C2C3 经 ASN.1/DER 再 Base64)
- 四要素明文以 SM4-CBC(PKCS5),UTF-8,分别加密后 Base64
- HMAC-SM3:消息 = idcard+realname+start_date+end_date+key,密钥 = OpenId,结果 Base64 → signature
- POST:key、sm4secret、四个加密字段、signature
成功时 error_code=0;result.res 为 1 一致、2 不一致。result.signature 为服务端对返回数据的签名,与请求中 signature 不同。
NuGet 包:BouncyCastle.Cryptography 版本 2.6.2
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.GM;
using Org.BouncyCastle.Asn1.X9;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Macs;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Paddings;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities.Encoders;
using Org.BouncyCastle.Crypto.Modes;
public class JuheIdCardSm
{
private const string ApiUrl = "https://apis.juhe.cn/id_card_four_factors/sm";
private const string ApiKey = "92d***********************bb";
private const string OpenId = "JHc***********************46";
public static string VerifyIdCardFourFactors(string realName, string idCard,
string startDate, string endDate, string sm2PublicKeyPath)
{
try
{
Console.WriteLine("=== 身份证四要素核验(国密版)===\n");
byte[] sm4Key = GenerateRandomBytes(16);
byte[] sm4Iv = GenerateRandomBytes(16);
Console.WriteLine("1. 生成SM4密钥与IV:");
Console.WriteLine($" SM4密钥(Hex): {Hex.ToHexString(sm4Key)}");
Console.WriteLine($" SM4 IV(Hex): {Hex.ToHexString(sm4Iv)}");
Console.WriteLine($"\n2. 读取SM2公钥: {sm2PublicKeyPath}");
AsymmetricKeyParameter sm2PublicKey = LoadSm2PublicKeyFromPem(sm2PublicKeyPath);
Console.WriteLine(" ✓ 公钥加载成功");
Console.WriteLine("\n3. SM2加密密钥IV生成sm4secret:");
byte[] keyIv = CombineBytes(sm4Key, sm4Iv);
string sm4Secret = Sm2Encrypt(sm2PublicKey, keyIv, useDer: true);
Console.WriteLine(" ✓ 完成");
Console.WriteLine("\n4. SM4-CBC加密业务字段:");
string encRealName = Sm4CbcEncrypt(realName, sm4Key, sm4Iv);
string encIdCard = Sm4CbcEncrypt(idCard, sm4Key, sm4Iv);
string encStart = Sm4CbcEncrypt(startDate, sm4Key, sm4Iv);
string encEnd = Sm4CbcEncrypt(endDate, sm4Key, sm4Iv);
Console.WriteLine(" ✓ 加密完成");
Console.WriteLine("\n5. 生成HMAC-SM3签名:");
string signature = HmacSm3Sign(OpenId, idCard, realName, startDate, endDate, ApiKey);
Console.WriteLine(" ✓ 签名完成");
Console.WriteLine("\n6. 发送请求...");
var parameters = new Dictionary<string, string>
{
{ "key", ApiKey },
{ "sm4secret", sm4Secret },
{ "realname", encRealName },
{ "idcard", encIdCard },
{ "start_date", encStart },
{ "end_date", encEnd },
{ "signature", signature }
};
string response = SendPostForm(ApiUrl, parameters);
Console.WriteLine("\n7. 接口响应:");
Console.WriteLine(response);
return response;
}
catch (Exception ex)
{
Console.WriteLine($"❌ 调用失败: {ex.Message}");
Console.WriteLine(ex.ToString());
return null;
}
}
#region SM2 加密
private static string Sm2Encrypt(AsymmetricKeyParameter publicKey, byte[] data, bool useDer)
{
SM2Engine engine = new SM2Engine(SM2Engine.Mode.C1C2C3);
engine.Init(true, new ParametersWithRandom(publicKey, new SecureRandom()));
byte[] cipherRaw = engine.ProcessBlock(data, 0, data.Length);
byte[] cipherFinal = useDer ? ConvertToDer(cipherRaw) : cipherRaw;
return Convert.ToBase64String(cipherFinal);
}
private static byte[] ConvertToDer(byte[] cipher)
{
if (cipher[0] != 0x04)
throw new ArgumentException("SM2密文必须以0x04开头");
byte[] x = new byte[32];
byte[] y = new byte[32];
byte[] c3 = new byte[32];
Array.Copy(cipher, 1, x, 0, 32);
Array.Copy(cipher, 33, y, 0, 32);
int c2Len = cipher.Length - 97;
byte[] c2 = new byte[c2Len];
Array.Copy(cipher, 65, c2, 0, c2Len);
Array.Copy(cipher, 65 + c2Len, c3, 0, 32);
Asn1EncodableVector v = new Asn1EncodableVector();
v.Add(new DerInteger(new BigInteger(1, x)));
v.Add(new DerInteger(new BigInteger(1, y)));
v.Add(new DerOctetString(c3));
v.Add(new DerOctetString(c2));
return new DerSequence(v).GetEncoded(Asn1Encodable.Der);
}
/// <summary>
/// 【标准兼容版】加载X.509/PKCS#8格式SM2公钥(解决Invalid point encoding 48)
/// </summary>
private static AsymmetricKeyParameter LoadSm2PublicKeyFromPem(string path)
{
try
{
// 读取PEM文件内容
string pem = File.ReadAllText(path)
.Replace("-----BEGIN PUBLIC KEY-----", "")
.Replace("-----END PUBLIC KEY-----", "")
.Replace("\r", "")
.Replace("\n", "")
.Trim();
// Base64解码公钥字节
byte[] publicKeyBytes = Convert.FromBase64String(pem);
// 解析X.509标准公钥(兼容所有标准SM2公钥)
AsymmetricKeyParameter publicKey = PublicKeyFactory.CreateKey(publicKeyBytes);
return publicKey;
}
catch (Exception ex)
{
throw new Exception($"SM2公钥加载失败:{ex.Message}", ex);
}
}
#endregion
#region SM4-CBC
private static string Sm4CbcEncrypt(string plainText, byte[] key, byte[] iv)
{
byte[] data = Encoding.UTF8.GetBytes(plainText);
var cipher = new PaddedBufferedBlockCipher(new CbcBlockCipher(new SM4Engine()), new Pkcs7Padding());
cipher.Init(true, new ParametersWithIV(new KeyParameter(key), iv));
byte[] enc = new byte[cipher.GetOutputSize(data.Length)];
int len = cipher.ProcessBytes(data, 0, data.Length, enc, 0);
len += cipher.DoFinal(enc, len);
byte[] res = new byte[len];
Buffer.BlockCopy(enc, 0, res, 0, len);
return Convert.ToBase64String(res);
}
#endregion
#region HMAC-SM3
private static string HmacSm3Sign(string openId, string idCard, string realName,
string startDate, string endDate, string apiKey)
{
string msg = idCard + realName + startDate + endDate + apiKey;
byte[] msgBytes = Encoding.UTF8.GetBytes(msg);
byte[] keyBytes = Encoding.UTF8.GetBytes(openId);
HMac hmac = new HMac(new SM3Digest());
hmac.Init(new KeyParameter(keyBytes));
hmac.BlockUpdate(msgBytes, 0, msgBytes.Length);
byte[] sign = new byte[hmac.GetMacSize()];
hmac.DoFinal(sign, 0);
return Convert.ToBase64String(sign);
}
#endregion
#region 工具
private static byte[] GenerateRandomBytes(int length)
{
byte[] bytes = new byte[length];
new SecureRandom().NextBytes(bytes);
return bytes;
}
private static byte[] CombineBytes(byte[] a, byte[] b)
{
byte[] res = new byte[a.Length + b.Length];
Buffer.BlockCopy(a, 0, res, 0, a.Length);
Buffer.BlockCopy(b, 0, res, a.Length, b.Length);
return res;
}
private static string SendPostForm(string url, Dictionary<string, string> parameters)
{
using (WebClient client = new WebClient())
{
client.Encoding = Encoding.UTF8;
var data = new System.Collections.Specialized.NameValueCollection();
foreach (var p in parameters) data.Add(p.Key, p.Value);
byte[] resp = client.UploadValues(url, "POST", data);
return Encoding.UTF8.GetString(resp);
}
}
#endregion
public static void Main()
{
try
{
string publicKeyPath = @"C:\publickey550.pem";
string realName = "项**";
string idCard = "210*********X";
string startDate = "20170606";
string endDate = "20270606";
VerifyIdCardFourFactors(realName, idCard, startDate, endDate, publicKeyPath);
}
catch (Exception ex)
{
Console.WriteLine($"❌ 失败: {ex.Message}");
}
Console.ReadLine();
}
}