C# 使用国密SM4加密解密

28 篇文章 0 订阅

首先需第三方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编码的字符串。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,以下是基于C# Winform的国密SM4加解密方法的示例代码: ``` using System.Security.Cryptography; // SM4加密 public static byte[] SM4Encrypt(byte[] plainData, byte[] key) { // 创建SM4加密对象 var sm4 = new SM4CryptoServiceProvider(); // 设置加密模式和填充模式 sm4.Mode = CipherMode.ECB; sm4.Padding = PaddingMode.Zeros; // 设置密钥 sm4.Key = key; // 创建加密器 var encryptor = sm4.CreateEncryptor(); // 加密数据 return encryptor.TransformFinalBlock(plainData, 0, plainData.Length); } // SM4解密 public static byte[] SM4Decrypt(byte[] cipherData, byte[] key) { // 创建SM4解密对象 var sm4 = new SM4CryptoServiceProvider(); // 设置加密模式和填充模式 sm4.Mode = CipherMode.ECB; sm4.Padding = PaddingMode.Zeros; // 设置密钥 sm4.Key = key; // 创建解密器 var decryptor = sm4.CreateDecryptor(); // 解密数据 return decryptor.TransformFinalBlock(cipherData, 0, cipherData.Length); } ``` 使用方法: ``` // 要加密的数据 byte[] plainData = Encoding.UTF8.GetBytes("Hello, world!"); // 加密密钥 byte[] key = new byte[16] { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10 }; // 加密 byte[] cipherData = SM4Encrypt(plainData, key); // 解密 byte[] decryptedData = SM4Decrypt(cipherData, key); // 输出解密后的数据 Console.WriteLine(Encoding.UTF8.GetString(decryptedData)); ``` 请注意,以上示例代码仅供参考,您需要根据实际需求进行修改和调整。另外,由于SM4算法是国密算法,因此在实际开发中需要遵循相关法律法规的规定。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

战族狼魂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值