.NET Core C# 版微信小程序加密数据解密算法示例
微信官方文档 加密数据解密算法 中只提供了 c++、Node、PHP、Python 四种语言的示例代码。
本文中的示例是基于 .NET Core 2.2 的 C# 语言版本。
1. 安装所需包
用到了 Json 的反序列化,需要额外安装 Newtonsoft.Json 包。
powershell
Install-Package Newtonsoft.Json -Version 12.0.2
2. 创建解密后数据所需的模型
这里以获取用户手机号码的返回值为例。
WXGetPhoneNumberResult.cs
csharp
namespace WXBizDataCryptSample
{
/// <summary>
/// 微信获取手机号结果模型
/// </summary>
public class WXGetPhoneNumberResult : WXBaseWatermarkResult
{
/// <summary>
/// 用户绑定的手机号(国外手机号会有区号)
/// </summary>
public string PhoneNumber { get; set; }
/// <summary>
/// 没有区号的手机号
/// </summary>
public string PurePhoneNumber { get; set; }
/// <summary>
/// 区号
/// </summary>
public string CountryCode { get; set; }
}
}
WXBaseWatermarkResult.cs
csharp
namespace WXBizDataCryptSample
{
/// <summary>
/// 微信结果基类
/// </summary>
public class WXBaseWatermarkResult
{
/// <summary>
/// 水印
/// </summary>
public WXWatermark Watermark { get; set; }
}
}
WXWatermark.cs
其中 Appid 因为不想被前端获知,所以添加了 JsonIgnore 特性。
但是反序列化解密后的结果时还是需要该字段的,到时还要加一些额外的处理以指定其不被忽略。
csharp
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System;
namespace WXBizDataCryptSample
{
/// <summary>
/// 微信水印
/// </summary>
public class WXWatermark
{
/// <summary>
/// AppID
/// </summary>
[JsonIgnore]
public string Appid { get; set; }
/// <summary>
/// 时间戳(Unix 时间)
/// </summary>
[JsonConverter(typeof(UnixDateTimeConverter))]
public DateTime Timestamp { get; set; }
}
}
3. 创建解密类
WXBizDataCrypt.cs
参考官方文档中其它语言版本的代码逻辑写的示例代码,其中使用 Newtonsoft.Json 将解密后的 Json 字符串反序列化为实体。
csharp
using Newtonsoft.Json;
using System;
using System.Security.Cryptography;
using System.Text;
namespace WXBizDataCryptSample
{
/// <summary>
/// 微信小程序用户加密数据的解密
/// </summary>
/// <typeparam name="T">解密后的实体类型(需继承自 WXBaseResult 类)</typeparam>
public class WXBizDataCrypt<T> where T : WXBaseWatermarkResult
{
private string _appId;
private string _sessionKey;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="appId">小程序的 appid</param>
/// <param name="sessionKey">用户在小程序登录后获取的会话密钥</param>
public WXBizDataCrypt(string appId, string sessionKey)
{
_appId = appId;
_sessionKey = sessionKey;
}
/// <summary>
/// 检验数据的真实性,获取解密后的明文并反序列化.
/// </summary>
/// <param name="encryptedData">加密的用户数据</param>
/// <param name="iv">与用户数据一同返回的初始向量</param>
/// <returns>解密并反序列化后的实体(解密失败时返回实体类型的默认值)</returns>
public T DecryptData(string encryptedData, string iv)
{
// 解密数据
var result = Decrypt(encryptedData, iv);
if (string.IsNullOrEmpty(result))
{
return default(T);
}
// 反序列化解密结果
var jsonResolver = new PropertyIncludeSerializerContractResolver();
jsonResolver.IncludeProperty(typeof(WXWatermark), "Appid");
var serializerSettings = new JsonSerializerSettings();
serializerSettings.ContractResolver = jsonResolver;
var dataObj = JsonConvert.DeserializeObject<T>(result, serializerSettings);
// 验证 AppID
if (dataObj.Watermark?.Appid != _appId)
{
return default(T);
}
return dataObj;
}
/// <summary>
/// 使用 AES 解密用户数据
/// </summary>
/// <param name="encryptedData">加密的用户数据</param>
/// <param name="iv">与用户数据一同返回的初始向量</param>
/// <returns>解密后的字符串</returns>
private string Decrypt(string encryptedData, string iv)
{
// 验证参数及密钥
if (string.IsNullOrEmpty(_sessionKey) || _sessionKey.Length != 24)
{
return string.Empty;
}
if (string.IsNullOrEmpty(iv) || iv.Length != 24)
{
return string.Empty;
}
AesManaged aes = new AesManaged();
aes.KeySize = 256;
aes.BlockSize = 128;
aes.Mode = CipherMode.CBC;
aes.IV = Convert.FromBase64String(iv);
aes.Key = Convert.FromBase64String(_sessionKey);
aes.Padding = PaddingMode.PKCS7;
var cipher = Convert.FromBase64String(encryptedData);
byte[] decryptText = aes.CreateDecryptor().TransformFinalBlock(cipher, 0, cipher.Length);
return Encoding.UTF8.GetString(decryptText);
}
}
}
PropertyIncludeSerializerContractResolver.cs
由于 WXWatermark.Appid 属性指定了 [JsonIgnore]
特性,反序列化时会忽略该字段。
但处理中需要用该字段做判断,需要在反序列化时不要忽略该属性。
PropertyIncludeSerializerContractResolver 类就是用来实现这个功能的。其可以在反序列化时指定某个类的某个字段始终不被忽略,不管是否指定了 [JsonIgnore]
特性。
csharp
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.Reflection;
namespace WXBizDataCryptSample
{
/// <summary>
/// 属性包含序列化分解器
/// </summary>
public class PropertyIncludeSerializerContractResolver : DefaultContractResolver
{
/// <summary>
/// 需要序列化的类型属性的字典
/// </summary>
private readonly Dictionary<Type, HashSet<string>> _includes;
/// <summary>
/// 构造函数
/// </summary>
public PropertyIncludeSerializerContractResolver()
{
_includes = new Dictionary<Type, HashSet<string>>();
}
/// <summary>
/// 添加需要序列化的类型属性
/// </summary>
/// <param name="type"></param>
/// <param name="jsonPropertyNames"></param>
public void IncludeProperty(Type type, params string[] jsonPropertyNames)
{
if (!_includes.ContainsKey(type))
_includes[type] = new HashSet<string>();
foreach (var prop in jsonPropertyNames)
_includes[type].Add(prop);
}
/// <summary>
/// 重载 DefaultContractResolver.CreateProperty 方法
/// </summary>
/// <param name="member"></param>
/// <param name="memberSerialization"></param>
/// <returns></returns>
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (IsInclude(property.DeclaringType, property.PropertyName))
{
property.ShouldSerialize = i => true;
property.Ignored = false;
}
return property;
}
/// <summary>
/// 判断属性是否在序列化属性字典中
/// </summary>
/// <param name="type"></param>
/// <param name="jsonPropertyName"></param>
/// <returns></returns>
private bool IsInclude(Type type, string jsonPropertyName)
{
if (!_includes.ContainsKey(type))
return false;
return _includes[type].Contains(jsonPropertyName);
}
}
}
4. 测试代码
csharp
using Newtonsoft.Json;
using System;
namespace WXBizDataCryptSample
{
class Program
{
static void Main(string[] args)
{
var crypt = new WXBizDataCrypt<WXGetPhoneNumberResult>(
appId: TestData.APP_ID,
sessionKey: TestData.SESSION_KEY);
var result = crypt.DecryptData(
encryptedData: TestData.ENCRYPTED_DATA,
iv: TestData.IV);
Console.WriteLine(JsonConvert.SerializeObject(result));
Console.WriteLine("press <enter> to exit.");
Console.ReadLine();
}
}
}