首先需第三方Nuget包:Portable.BouncyCastle (源码来自http://www.bouncycastle.org/csharp/),支持.NET 4,.NET Standard 2.0
目录
示例(SM4 CBC 模式加密(使用 Zero Padding))
使用BouncyCastle指定填充方案
在BouncyCastle中,可以通过选择不同的PaddedBufferedBlockCipher
实现来指定填充方案。这里我们将展示如何使用零填充(Zero Padding)和PKCS7填充(PKCS7 Padding)。
零填充(Zero Padding)
对于零填充,您需要自定义填充处理,因为BouncyCastle不直接提供零填充实现。
PKCS7填充(PKCS7 Padding)
如果你想使用PKCS7填充,可以使用BouncyCastle中的PaddedBufferedBlockCipher
类直接指定Pkcs7Padding
。
示例(SM4 CBC 模式加密(使用 Zero Padding))
采用国密SM4 CBC 模式加密(使用 Zero Padding)
SM4HttpUtilsV2.cs
using System; using System.Collections.Generic; using System.Security; using System.Security.Cryptography; using System.Text; using Newtonsoft.Json; using System.Net.Http; using System.Threading.Tasks; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Engines; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Security; using Org.BouncyCastle.Crypto.Modes; using Org.BouncyCastle.Crypto.Paddings; public class SM4HttpUtilsV2 { public static Dictionary<string, string> CreateCommonParam(string appKey, string appSecret, string codeData, string iv) { // 时间戳 long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); // 签名 appKey + apiAppInfo.getAppSecret() + encryptString + timestamp Console.WriteLine($"签名: {appKey}{appSecret}{codeData}{timestamp}{iv}"); Console.WriteLine($"appKey={appKey}"); Console.WriteLine($"appSecret={appSecret}"); Console.WriteLine($"encryptStr={codeData}"); Console.WriteLine($"timestamp={timestamp}"); Console.WriteLine($"iv={iv}"); // 使用 MD5 生成签名 string sig = CalculateMD5Hash(appKey + appSecret + codeData + timestamp + iv); Console.WriteLine($"签名值: {sig}"); var requestParam = new Dictionary<string, string> { { "timestamp", timestamp.ToString() }, { "sign", sig }, { "iv", iv } }; return requestParam; } private static string CalculateMD5Hash(string input) { using (var md5 = MD5.Create()) { byte[] inputBytes = Encoding.UTF8.GetBytes(input); byte[] hashBytes = md5.ComputeHash(inputBytes); StringBuilder sb = new StringBuilder(); for (int i = 0; i < hashBytes.Length; i++) { sb.Append(hashBytes[i].ToString("x2")); } return sb.ToString(); } } /// <summary> /// Post请求数据 /// </summary> /// <param name="url"></param> /// <param name="appKey"></param> /// <param name="appSecret"></param> /// <param name="parameters"></param> /// <returns></returns> public static async Task<string> PostJsonAsync(string url, string appKey, string appSecret, Dictionary<string, object> obj ) { string requestBody = JsonConvert.SerializeObject(obj); Console.WriteLine($"原始数据: {requestBody}"); // 生成随机的 IV(初始化向量) byte[] ivBytes = new byte[16]; using (var rng = RandomNumberGenerator.Create()) { rng.GetBytes(ivBytes); } string ivBase64 = Convert.ToBase64String(ivBytes); // 使用 SM4 进行加密(需要实现 SM4 加密算法) string codeData = EncryptSM4CBC(appSecret, requestBody, ivBytes); Console.WriteLine($"原始: {codeData}"); var requestParam = new Dictionary<string, object> { { "appKey", appKey }, { "version", "1" }, { "encryptStr", codeData } }; foreach (var param in CreateCommonParam(appKey, appSecret, codeData, ivBase64)) { requestParam.Add(param.Key, param.Value); } Console.WriteLine($"请求数据: {JsonConvert.SerializeObject(requestParam)}"); using (var httpClient = new HttpClient()) { var content = new StringContent(JsonConvert.SerializeObject(requestParam), Encoding.UTF8, "application/json"); HttpResponseMessage response = await httpClient.PostAsync(url, content); string result = await response.Content.ReadAsStringAsync(); Console.WriteLine(result); var ret = JsonConvert.DeserializeObject<Dictionary<string, object>>(result); if (ret.ContainsKey("data") && !string.IsNullOrEmpty(ret["data"]?.ToString())) { string data = ret["data"].ToString(); return DecryptSM4CBC(appSecret, data, ivBytes); } else { return result; } } } /// <summary> /// SM4 加密 CBC 模式 /// </summary> /// <param name="key"></param> /// <param name="data"></param> /// <param name="iv"></param> /// <returns></returns> public static string EncryptSM4CBC(string key, string data, byte[] iv) { byte[] keyBytes = Encoding.UTF8.GetBytes(key); byte[] dataBytes = Encoding.UTF8.GetBytes(data); // 应用 Zero Padding dataBytes = ApplyZeroPadding(dataBytes, 16); // SM4 的块大小是 16 字节 SM4Engine engine = new SM4Engine(); CbcBlockCipher cipher = new CbcBlockCipher(engine); PaddedBufferedBlockCipher bufferedCipher = new PaddedBufferedBlockCipher(cipher); KeyParameter keyParam = new KeyParameter(keyBytes); ParametersWithIV keyParamWithIV = new ParametersWithIV(keyParam, iv); bufferedCipher.Init(true, keyParamWithIV); byte[] outputBytes = new byte[bufferedCipher.GetOutputSize(dataBytes.Length)]; int length = bufferedCipher.ProcessBytes(dataBytes, 0, dataBytes.Length, outputBytes, 0); length += bufferedCipher.DoFinal(outputBytes, length); // 直接返回加密后的Base64字符串 return Convert.ToBase64String(outputBytes, 0, length); } /// <summary> /// 自定义 Zero Padding 方法 /// </summary> /// <param name="input"></param> /// <param name="blockSize"></param> /// <returns></returns> public static byte[] ApplyZeroPadding(byte[] input, int blockSize) { int paddingLength = blockSize - (input.Length % blockSize); if (paddingLength == blockSize) { return input; // 无需填充 } byte[] paddedData = new byte[input.Length + paddingLength]; Array.Copy(input, paddedData, input.Length); return paddedData; } /// <summary> /// SM4 解密 CBC 模式 /// </summary> /// <param name="key"></param> /// <param name="encryptedData"></param> /// <param name="iv"></param> /// <returns></returns> public static string DecryptSM4CBC(string key, string encryptedData, byte[] iv) { byte[] keyBytes = Encoding.UTF8.GetBytes(key); byte[] encryptedBytes = Convert.FromBase64String(encryptedData); // 应用 Zero Padding encryptedBytes = ApplyZeroPadding(encryptedBytes, 16); // SM4 的块大小是 16 字节 SM4Engine engine = new SM4Engine(); CbcBlockCipher cipher = new CbcBlockCipher(engine); BufferedBlockCipher bufferedCipher = new BufferedBlockCipher(cipher); KeyParameter keyParam = new KeyParameter(keyBytes); ParametersWithIV keyParamWithIV = new ParametersWithIV(keyParam, iv); bufferedCipher.Init(false, keyParamWithIV); byte[] outputBytes = new byte[bufferedCipher.GetOutputSize(encryptedBytes.Length)]; int length = bufferedCipher.ProcessBytes(encryptedBytes, 0, encryptedBytes.Length, outputBytes, 0); try { length += bufferedCipher.DoFinal(outputBytes, length); } catch (InvalidCipherTextException e) { throw new Exception("解密失败:密文损坏或密钥/IV不正确", e); } return Encoding.UTF8.GetString(outputBytes, 0, length); } }
当您遇到
Org.BouncyCastle.Crypto.InvalidCipherTextException: "pad block corrupted"
错误时,这通常意味着解密过程中,填充的块(pad block)不符合预期的格式或长度。
需要添加如下代码,指定填充方案
/// <summary>
/// 自定义 Zero Padding 方法
/// </summary>
/// <param name="input"></param>
/// <param name="blockSize"></param>
/// <returns></returns>
public static byte[] ApplyZeroPadding(byte[] input, int blockSize)
{
int paddingLength = blockSize - (input.Length % blockSize);
if (paddingLength == blockSize)
{
return input; // 无需填充
}
byte[] paddedData = new byte[input.Length + paddingLength];
Array.Copy(input, paddedData, input.Length);
return paddedData;
}
代码解析
-
自定义 Zero Padding:
ApplyZeroPadding
方法用于将数据填充到符合块大小(16字节)的倍数。- 如果数据已经是块大小的倍数,则不进行填充。
-
加密过程:
- 创建一个
SM4Engine
实例,并将其包装在CbcBlockCipher
中以使用CBC模式。 - 使用
BufferedBlockCipher
来处理加密操作。 - 使用提供的密钥和IV参数初始化加密器。
- 创建一个
-
处理填充后的数据:
- 在加密之前,调用
ApplyZeroPadding
方法对数据进行零填充。 - 加密完成后,输出结果为Base64编码的字符串。
- 在加密之前,调用