轻装上阵,不调用jar包,用C#写SM4加密算法【卸载IKVM 】

前言

记得之前写了一个文章,是关于java和c#加密不一致导致需要使用ikvm的方式来进行数据加密,主要是ikvm把打包后的jar包打成dll包,然后Nuget引入ikvm,从而实现算法的统一,这几天闲来无事,网上找了一下加密库【BouncyCastle.dll】进行加密,目的是想统一加密。因为ikvm相对重了点,引入一堆dll包。

官方网址

c#入口: https://www.bouncycastle.org/csharp/

java入口: https://www.bouncycastle.org/java.html

在这里插入图片描述
(如图所示)在1.8.4中 发现它是支持SM4 加密的,如果你想要使用SM4加密算法,最低版本需要是1.8.4。
在这里插入图片描述

Nuget安装(如图所示)

注意事项

这个是针对java和c#的加解密一致性

1.算法要保持一致 均是SM4
2.算法模式要保持一致 (如CBC和ECB 当然还有填充模式)
举个例子:我这边就使用 SM4/CBC/PKCS5Padding
3.编码一致性
如果JAVA 加解密用的UTF8 ,C#加解密用的是GBK 这样肯定不行了
俺们都是Chinese,所以果断选择UTF8了

最后就是代码部分了

java

需要引入bcprov-jdk15on-1.59.jar 和 httpcore-4.4.3.jar

package com.ken.utils;
import java.security.*;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;


public class SM4Util {

    static {
        Security.addProvider(new BouncyCastleProvider());
    }
    
	//默认是UTF-8编码
    //private static final String ENCODING = "UTF-8";
    public static final String ALGORITHM_NAME = "SM4";
    // 加密算法/分组加密模式/分组填充方式
    // PKCS5Padding-以8个字节为一组进行分组加密
    // 定义分组加密模式使用:PKCS5Padding
    public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS5Padding";
    // 128 32位16进制  本身SM4默认就是这一种
   // public static final int DEFAULT_KEY_SIZE = 128;

  // 这边我默认了密钥
    public static final String SM4_KEY = "86C63180C2806ED1F47B859DE501215B";

    /**
     * 生成ECB暗号
     * @explain ECB模式(电子密码本模式:Electronic codebook)
     * @param algorithmName
     *            算法名称
     * @param mode
     *            模式
     * @param key
     * @return
     * @throws Exception
     */
    private static Cipher generateEcbCipher(String algorithmName, int mode, byte[] key) throws Exception {
        Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
        Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
        cipher.init(mode, sm4Key);
        return cipher;
    }


    /** 方式一:系统生成密钥
     * 自动生成密钥
     * @explain
     * @return
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     */
    public static byte[] generateKey() throws Exception {
        return generateKey(DEFAULT_KEY_SIZE);
    }

    /**
     * @explain
     * @param keySize
     * @return
     * @throws Exception
     */
    public static byte[] generateKey(int keySize) throws Exception {
        KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
        kg.init(keySize, new SecureRandom());
        return kg.generateKey().getEncoded();
    }


    /**
     * sm4加密
     * @explain 加密模式:ECB
     *          密文长度不固定,会随着被加密字符串长度的变化而变化
     * @param hexKey
     *            16进制密钥(忽略大小写)
     * @param paramStr
     *            待加密字符串
     * @return 返回16进制的加密字符串
     * @throws Exception
     */
    public static String encryptEcb(String hexKey, String paramStr) throws Exception {
        String cipherText = "";
        // 16进制字符串-->byte[]
        byte[] keyData = ByteUtils.fromHexString(hexKey);
        // String-->byte[]
        byte[] srcData = paramStr.getBytes(ENCODING);
        // 加密后的数组
        byte[] cipherArray = encrypt_Ecb_Padding(keyData, srcData);
        // byte[]-->hexString
        cipherText = ByteUtils.toHexString(cipherArray);
        return cipherText;
    }

    /**
     * 加密模式之Ecb 方法二:自己提供16进制的密钥
     * @explain
     * @param key
     * @param data
     * @return
     * @throws Exception
     */
    public static byte[] encrypt_Ecb_Padding(byte[] key, byte[] data) throws Exception {
        Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.ENCRYPT_MODE, key);
        return cipher.doFinal(data);
    }


    /**
     * sm4解密
     * @explain 解密模式:采用ECB
     * @param hexKey
     *            16进制密钥
     * @param cipherText
     *            16进制的加密字符串(忽略大小写)
     * @return 解密后的字符串
     * @throws Exception
     */
    public static String decryptEcb(String hexKey, String cipherText) throws Exception {
        // 用于接收解密后的字符串
        String decryptStr = "";
        // hexString-->byte[]
        byte[] keyData = ByteUtils.fromHexString(hexKey);
        // hexString-->byte[]
        byte[] cipherData = ByteUtils.fromHexString(cipherText);
        // 解密
        byte[] srcData = decrypt_Ecb_Padding(keyData, cipherData);
        // byte[]-->String
        decryptStr = new String(srcData, ENCODING);
        return decryptStr;
    }

    /**
     * 解密
     * @explain
     * @param key
     * @param cipherText
     * @return
     * @throws Exception
     */
    public static byte[] decrypt_Ecb_Padding(byte[] key, byte[] cipherText) throws Exception {
        Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.DECRYPT_MODE, key);
        return cipher.doFinal(cipherText);
    }

    /**
     * 校验加密前后的字符串是否为同一数据
     * @explain
     * @param hexKey
     *            16进制密钥(忽略大小写)
     * @param cipherText
     *            16进制加密后的字符串
     * @param paramStr
     *            加密前的字符串
     * @return 是否为同一数据
     * @throws Exception
     */
    public static boolean verifyEcb(String hexKey, String cipherText, String paramStr) throws Exception {
        // 用于接收校验结果
        boolean flag = false;
        // hexString-->byte[]
        byte[] keyData = ByteUtils.fromHexString(hexKey);
        // 将16进制字符串转换成数组
        byte[] cipherData = ByteUtils.fromHexString(cipherText);
        // 解密
        byte[] decryptData = decrypt_Ecb_Padding(keyData, cipherData);
        // 将原字符串转换成byte[]
        byte[] srcData = paramStr.getBytes(ENCODING);
        // 判断2个数组是否一致
        flag = Arrays.equals(decryptData, srcData);
        return flag;
    }


 

    /**
     * 字符串转化成为16进制字符串
     * @param s
     * @return
     */
    public static String strTo16(String s) {
        String str = "";
        for (int i = 0; i < s.length(); i++) {
            int ch = (int) s.charAt(i);
            String s4 = Integer.toHexString(ch);
            str = str + s4;
        }
        return str;
    }



    public static String byteArrayToHexStr(byte[] byteArray) {
        StringBuilder stringBuilder = new StringBuilder("");
        if (byteArray == null || byteArray.length <= 0) {
            return null;
        }
        for (int i = 0; i < byteArray.length; i++) {
            int v = byteArray[i] & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                stringBuilder.append(0);
            }
            stringBuilder.append(hv);
        }
        return stringBuilder.toString();
    }
	
}

C# (Csharp)

using Org.BouncyCastle.Crypto.IO;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities.Encoders;
using System;
using System.IO;
using System.Text;

namespace Web.Security.Util
{
	public static class SM4Util
	{

		/// <summary>
		/// 默认编码
		/// </summary>
		private static Encoding DefaultEncoding = Encoding.UTF8;
			//"GB2312";

		public const string ALGORITHM_NAME = "SM4";

		/// <summary>
		/// ECB模式 [pkcs5padding填充方式]
		/// </summary>
		public const string ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS5Padding";

		/// <summary>
		/// CBC模式 [pkcs5padding填充方式]
		/// </summary>
		public const string ALGORITHM_NAME_CBC_PADDING = "SM4/CBC/PKCS5Padding";

		//这个不需要  SM4默认就是128位的密钥
		//public const int DEFAULT_KEY_SIZE = 128;


		/// <summary>
		/// 解密
		/// </summary>
		/// <param name="key">密钥</param>
		/// <param name="passInput"></param>
		/// <param name="encoding">编码</param>
		/// <param name="algorithmMode">默认是ECB模式 [pkcs5padding填充方式]</param>
		/// <returns></returns>
		public static string DecryptEcb(string key, string passInput, Encoding encoding, string algorithmMode= ALGORITHM_NAME_ECB_PADDING)
		{
			encoding = encoding??Encoding.UTF8;
			byte[] keyBytes = Hex.Decode(key);
			byte[] input = Hex.Decode(passInput);
			return encoding.GetString(DecryptEcb(keyBytes, input, algorithmMode));
		}

		/// <summary>
		/// 解密
		/// </summary>
		/// <param name="key">密钥</param>
		/// <param name="passInput"></param>
		/// <param name="algorithmMode">默认是ECB模式 [pkcs5padding填充方式]</param>
		/// <returns></returns>
		public static string DecryptEcb(string key, string passInput, string algorithmMode = ALGORITHM_NAME_ECB_PADDING)
		{
			return DecryptEcb(key, passInput, DefaultEncoding, algorithmMode);
		}

		/// <summary>
		/// 解密
		/// </summary>
		/// <param name="keyBytes">密钥</param>
		/// <param name="passInput"></param>
		/// <param name="algorithmMode">加密方式</param>
		/// <returns></returns>
		/// <exception cref="Exception"></exception>
		public static byte[] DecryptEcb(byte[] keyBytes, byte[] passInput, string algorithmMode)
		{
			KeyParameter key = ParameterUtilities.CreateKeyParameter(ALGORITHM_NAME, keyBytes);
			IBufferedCipher inCipher = CipherUtilities.GetCipher(algorithmMode);
			//forEncryption位false表示解密
			inCipher.Init(false, key);

			MemoryStream bIn = new MemoryStream(passInput, false);
			CipherStream cIn = new CipherStream(bIn, inCipher, null);
			byte[] bytes = new byte[passInput.Length];
			byte[] totalBytes;
			try
			{
				#region 此代码一次性读取解密 可行
				/*BinaryReader dIn = new BinaryReader(cIn);
				byte[] extra = dIn.ReadBytes(passInput.Length);
				Array.Copy(extra, 0, bytes, 0, extra.Length);*/
				#endregion

				#region 官方demo 是先处理一半  再处理剩下的 最后把剩下的复制到bytes剩余部分
				BinaryReader dIn = new BinaryReader(cIn);
				for (int i = 0; i != passInput.Length / 2; i++)
				{
					bytes[i] = dIn.ReadByte();
				}

				int remaining = bytes.Length - passInput.Length / 2;
				byte[] extra = dIn.ReadBytes(remaining);

				//把为了加密补位的部分去掉
				if (extra.Length < remaining)
				{
					int len = passInput.Length/2 + extra.Length;
					totalBytes = new byte[len];
					Array.Copy(bytes, 0, totalBytes, 0, passInput.Length / 2);
					extra.CopyTo(totalBytes, passInput.Length / 2);
					return totalBytes;
				}
				else
				{
					extra.CopyTo(bytes, passInput.Length / 2);
				}
				//throw new EndOfStreamException();
				#endregion
			}
			catch (Exception e)
			{
				throw new Exception("SM4 failed encryption - " + e, e);
			}

			return bytes;
		}

		/// <summary>
		/// 加密
		/// </summary>
		/// <param name="key">密钥</param>
		/// <param name="text"></param>
		/// <param name="encoding">编码</param>
		/// <param name="algorithmMode">默认是ECB模式 [pkcs5padding填充方式]</param>
		/// <returns></returns>
		public static string EncryptEcb(string key, string text, Encoding encoding,string algorithmMode = ALGORITHM_NAME_ECB_PADDING)
		{
			encoding = encoding??Encoding.UTF8;
			byte[] keyBytes = Hex.Decode(key);
			byte[] input = encoding.GetBytes(text);
			return Hex.ToHexString(EncryptEcb(keyBytes, input, algorithmMode));
		}

		/// <summary>
		/// 加密
		/// </summary>
		/// <param name="key">密钥</param>
		/// <param name="text"></param>
		/// <param name="algorithmMode"></param>
		/// <returns></returns>
		public static string EncryptEcb(string key, string text, string algorithmMode = ALGORITHM_NAME_ECB_PADDING)
		{
			return EncryptEcb(key,text, DefaultEncoding, algorithmMode);
		}

		

		/// <summary>
		/// 加密
		/// </summary>
		/// <param name="keyBytes">密钥</param>
		/// <param name="input"></param>
		/// <returns></returns>
		/// <exception cref="Exception"></exception>
		public static byte[] EncryptEcb(byte[] keyBytes, byte[] input,string algorithmMode)
		{
			KeyParameter key = ParameterUtilities.CreateKeyParameter(ALGORITHM_NAME, keyBytes);
			IBufferedCipher outCipher = CipherUtilities.GetCipher(algorithmMode);
			//forEncryption位true表示加密
			outCipher.Init(true, key);
			MemoryStream bOut = new MemoryStream();
			CipherStream cOut = new CipherStream(bOut, null, outCipher);

			try
			{
				//处理前一半
				for (int i = 0; i != input.Length / 2; i++)
				{
					cOut.WriteByte(input[i]);
				}
				//处理后一半
				cOut.Write(input, input.Length / 2, input.Length - input.Length / 2);
				cOut.Close();
			}
			catch (IOException e)
			{
				throw new Exception("SM4 failed encryption - " + e, e);
			}
			byte[] bytes = bOut.ToArray();
			return bytes;
		}		
	}

}

密钥生成

java可以使用UUID生成32位的16进制字符串

C#可以使用 GUID生成32位的16进制字符串

密钥格式(示例): 7A5B5AE03F764358AEAEF0D1B4B2ADAE


调用方式

string SM4_KEY =“7A5B5AE03F764358AEAEF0D1B4B2ADAE”;


java

SM4Util.encryptEcb(SM4_KEY, “待加密文本”);

SM4Util.decryptEcb(SM4_KEY, “加密后的文本”);

C#

SM4Util.EncryptEcb(SM4_KEY, “待加密文本”);
SM4Util.DecryptEcb(SM4_KEY, “加密后的文本”)

ps: java版本的,如果数据量超过5000,不建议全部加密,会比较耗时。c# 2万条依旧扛得住。

这个我试过虽然加密的结果两者(java和C#)是不一样的,但解密的结果都是一样的,如果你对此不放心 也可以把java版的改成使用统一版本的BouncyCastle。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/69903.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

时序预测 | MATLAB实现CNN-BiGRU-Attention时间序列预测

时序预测 | MATLAB实现CNN-BiGRU-Attention时间序列预测 目录 时序预测 | MATLAB实现CNN-BiGRU-Attention时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 MATLAB实现CNN-BiGRU-Attention时间序列预测&#xff0c;CNN-BiGRU-Attention结合注意力机制时…

uni-app实现图片上传功能

效果 代码 <uni-forms-item name"ViolationImg" label"三违照片 :"><uni-file-picker ref"image" limit"1" title"" fileMediatype"image" :listStyles"listStyles" :value"filePathsL…

UML-类图和对象图

目录 类图概述&#xff1a; 1.类: 2.属性: 3.类的表示&#xff1a; 4.五种方法: 类图的关系&#xff1a; 1.关联 2.聚合 3.组合 4.依赖 5.泛化 6.实现 对象图概述&#xff1a; 1. 对象图包含元素: 2. 什么是对象 3.对象的状态可以改变: 4.对象的行为 5.对象标…

ad+硬件每日学习十个知识点(30)23.8.10 (SDIO端口扩展器TXS02612RTWR,模数转换器ADC121C027)

文章目录 1.cpu->SDIO端口扩展器->SD卡槽->SD卡(当然也可以反向读取)2.SDIO端口扩展器介绍3.SDIO端口扩展器TXS02612RTWR4.SD卡槽5.什么是模数转换器&#xff1f;6.I2C模数转换器ADC121C0277.模数转换方案 1.cpu->SDIO端口扩展器->SD卡槽->SD卡(当然也可以反…

【JPCS出版】第五届能源、电力与电网国际学术会议(ICEPG 2023)

第五届能源、电力与电网国际学术会议&#xff08;ICEPG 2023&#xff09; 2023 5th International Conference on Energy, Power and Grid 最近几年&#xff0c;不少代表委员把目光投向能源电力领域&#xff0c;对促进新能源发电产业健康发展、电力绿色低碳发展&#xff0c;提…

Kubernetes(K8s)从入门到精通系列之十:使用 kubeadm 创建一个高可用 etcd 集群

Kubernetes K8s从入门到精通系列之十&#xff1a;使用 kubeadm 创建一个高可用 etcd 集群 一、etcd高可用拓扑选项1.堆叠&#xff08;Stacked&#xff09;etcd 拓扑2.外部 etcd 拓扑 二、准备工作三、建立集群1.将 kubelet 配置为 etcd 的服务管理器。2.为 kubeadm 创建配置文件…

【前端 | CSS】滚动到底部加载,滚动监听、懒加载

背景 在日常开发过程中&#xff0c;我们会遇到图片懒加载的功能&#xff0c;基本原理是&#xff0c;滚动条滚动到底部后再次获取数据进行渲染。 那怎么判断滚动条是否滚动到底部呢&#xff1f;滚动条滚动到底部触发时间的时机和方法又该怎样定义&#xff1f; 针对以上问题我…

QPainter - 八卦时钟

QPainter - 八卦时钟 上一篇我们在画时钟的时候&#xff0c;已经把基本的钟表指针和刻度都绘制过了 想要完成八卦时钟&#xff0c;就要绘制这个里面的八卦了。 先上个图&#xff1a; 有人和我说八卦不能转 再来一张图&#xff1a; 背景的绘制 我们需要删除之前所绘制的白色…

FFmpeg常见命令行(五):FFmpeg滤镜使用

前言 在Android音视频开发中&#xff0c;网上知识点过于零碎&#xff0c;自学起来难度非常大&#xff0c;不过音视频大牛Jhuster提出了《Android 音视频从入门到提高 - 任务列表》&#xff0c;结合我自己的工作学习经历&#xff0c;我准备写一个音视频系列blog。本文是音视频系…

2023年中国日志审计市场竞争格局、市场规模、下游应用领域及行业发展趋势[图]

日志是行为或状态详细描述的载体&#xff0c;其时效性与信息丰富程度在网络安全事件分析、事件回溯和取证过程中起到重要作用。在法律层&#xff0c;日志也是重要的电子证据&#xff0c;日志记录、监控、审计手段等&#xff0c;可以帮助有效地减少信息破坏、信息泄露的问题&…

【Python机器学习】实验10 支持向量机

文章目录 支持向量机实例1 线性可分的支持向量机1.1 数据读取1.2 准备训练数据1.3 实例化线性支持向量机1.4 可视化分析 实例2 核支持向量机2.1 读取数据集2.2 定义高斯核函数2.3 创建非线性的支持向量机2.4 可视化样本类别 实例3 如何选择最优的C和gamma3.1 读取数据3.2 利用数…

断路器分合闸速度试验

试验目的 高压断路器的分、 合闸速度是断路器的重要特性参数, 反映出断路器的操动机构 与传动机构在分、 合闸过程中的运动特征。 断路器分、 合闸速度超出或者低于规定值 均会影响断路器的运行状态和使用寿命。 断路器合闸速度不足, 将会引起触头合闸振颤, 预击穿时间过长。 断…

Java课题笔记~ Request请求

1.请求消息格式 客户端发送一个HTTP请求到服务器的请求消息包括以下格式&#xff1a; 请求行&#xff08;request line&#xff09;、请求头部&#xff08;header&#xff09;、空行和请求数据四个部分组成。下图给出了请求报文的一般格式。 GET请求&#xff1a; POST请求&am…

机器学习笔记:李宏毅ChatGPT:生成式学习的两种策略

1 策略1 “各个击破”——autoregressive model “各个击破”——一个一个生成出来 2 策略2 &#xff1a; “一次到位”——non-autoregressve model 一步到位&#xff0c;全部生成出来 2.1 non-autoregressive model 如何确定长度&#xff1f; 两种策略 策略1&#xff1a;始…

(十五)大数据实战——hive的安装部署

前言 Hive是由Facebook开源&#xff0c;基于Hadoop的一个数据仓库工具&#xff0c;可以将结构化的数据文件映射为一张表&#xff0c;并提供类SQL查询功能。本节内容我们主要介绍一下hive的安装与部署的相关内容。 正文 上传hive安装包到hadoop101服务器/opt/software目录 解…

无涯教程-Perl - link函数

描述 此函数创建一个新文件名NEWFILE,链接到文件OLDFILE。该函数创建一个硬链接&#xff1b;如果需要符号链接,请使用符号链接功能。 语法 以下是此函数的简单语法- link OLDFILE,NEWFILE返回值 如果失败,此函数返回0,如果成功,则返回1。 例 以下是显示其基本用法的示例…

IPC之三:使用 System V 消息队列进行进程间通信的实例

IPC 是 Linux 编程中一个重要的概念&#xff0c;IPC 有多种方式&#xff0c;本文主要介绍消息队列(Message Queues)&#xff0c;消息队列可以完成同一台计算机上的进程之间的通信&#xff0c;相比较管道&#xff0c;消息队列要复杂一些&#xff0c;但使用起来更加灵活和方便&am…

Android界面设计与用户体验

Android界面设计与用户体验 1. 引言 在如今竞争激烈的移动应用市场&#xff0c;提供优秀的用户体验成为了应用开发的关键要素。无论应用功能多么强大&#xff0c;如果用户界面设计不合理&#xff0c;用户体验不佳&#xff0c;很可能会导致用户流失。因此&#xff0c;在Androi…

BI技巧丨利用Index计算半累计

在实际的业务场景中&#xff0c;特别是财务模块和库存管理模块&#xff0c;经常需要我们针对每个月的期初期末进行相关指标计算&#xff0c;这也是我们之前曾经提到的Calculate基础应用——半累计计算。 现在我们也可以通过微软新推出的Index开窗函数来解决这一问题。 INDEX函…

kafka:java client使用总结塈seek() VS commitSync()的区别(三)

最近一段日子接触了kafka这个消息系统&#xff0c;主要为了我的开源中间件项目simplemq增加kafka支持&#xff08;基于kafka-client【java】&#xff09;&#xff0c;如今总算完成&#xff0c;本文是对这个过程中对kafka消息系统的使用总结 线程安全 关于线程安全&#xff0c…