C# 使用国密SM4加密解密

首先需第三方Nuget包:Portable.BouncyCastle (源码来自http://www.bouncycastle.org/csharp/),支持.NET 4,.NET Standard 2.0

目录

使用BouncyCastle指定填充方案

零填充(Zero Padding)

PKCS7填充(PKCS7 Padding)

示例(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;
    }

代码解析

  1. 自定义 Zero Padding:

    • ApplyZeroPadding方法用于将数据填充到符合块大小(16字节)的倍数。
    • 如果数据已经是块大小的倍数,则不进行填充。
  2. 加密过程:

    • 创建一个SM4Engine实例,并将其包装在CbcBlockCipher中以使用CBC模式。
    • 使用BufferedBlockCipher来处理加密操作。
    • 使用提供的密钥和IV参数初始化加密器。
  3. 处理填充后的数据:

    • 在加密之前,调用ApplyZeroPadding方法对数据进行零填充。
    • 加密完成后,输出结果为Base64编码的字符串。

猜你喜欢

转载自blog.csdn.net/nndsb/article/details/141900051