C#基于SM4加密算法的文件快速加解密实现

C#基于SM4加密算法的文件快速加解密实现

编码文章call10242025-02-01 3:32:3011A+A-

思路

针对文件加密特别是文件批量加密存在一个问题,就是如果对文件整体进行加密的话,文件越大则加密速度越慢,并且加密后的文件体积也会变大,基本上就是分段读取-加密-保存-删除原文件这种操作,解密也是一样的,时间开销比较大,不符合大家的需求。

那么就换一种思路,只读取文件的一部分,比如256个字节,然后对字节进行加密,再把加密后字节写回原来的位置,这样就无需对文件整体进行读写,自然速度也快,但是在二进制读取的方式下,从底层无法实现字节的查找替换,所以就要求加密前是256个字节的明文,那么加密后也一定得要是256个字节的密文才行。

能符合这种要求的加密算法有经典密码算法、RC4加密、SM4加密等,经典算法基本上没有安全性可言,RC4安全性较差,而SM4是国产加密算法,更好一些,SM4分为填充模式和无填充模式,这里我们只能选择无填充模式。

由于选择的是固定长度的加密,并且选择的是无填充模式,所以对于比读取明文长度更短的文件无法加密,比如读取256字节,而一个记事本文件100个字节,那么就会出现错误。

SM4加密解密实现

需要注意的是在引用BouncyCastle.Crypto一定要选择2.2.0版本的,1.8.0版本的需要自己实现无填充模式。

/// 
/// SM4加密
/// 
/// 
/// 
/// 
/// 
public static byte[] EncryptSM4(byte[] plaintext, byte[] key, byte[] iv)
{
    byte[] input = plaintext;

    IBufferedCipher cipher = CipherUtilities.GetCipher("SM4/CBC/NoPadding");
    cipher.Init(true, new KeyParameter(key));
    
    byte[] output = new byte[cipher.GetOutputSize(input.Length)];
    int len = cipher.ProcessBytes(input, 0, input.Length, output, 0);
    cipher.DoFinal(output, 0);
    return output;
}
/// 
/// SM4解密
/// 
/// 
/// 
/// 
/// 
public static byte[] DecryptSM4(byte[] encrypted, byte[] key, byte[] iv)
{
    IBufferedCipher cipher = CipherUtilities.GetCipher("SM4/CBC/NoPadding");
    cipher.Init(false, new KeyParameter(key));

    byte[] output = new byte[cipher.GetOutputSize(encrypted.Length)];
    int len = cipher.ProcessBytes(encrypted, 0, encrypted.Length, output, 0);
    cipher.DoFinal(output, len);
    return output;
}

密钥和IV的生成,都是16个字符长度,通过生成指定长度的随机字符串来做密钥

/// 
/// 获取一个指定长度的不重复字符串
/// 
/// 指定的长度
/// 默认为true
/// 自定义字符串,当useLow为true时,它不起作用
/// 
public static string GetKey(int length, bool useLow, string custom)
{
    byte[] b = new byte[4];
    new System.Security.Cryptography.RNGCryptoServiceProvider().GetBytes(b);
    Random r = new Random(BitConverter.ToInt32(b, 0));
    string s = null, str = custom;

    if (useLow == true) { str += "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPRSTUVWXYZ"; }


    for (int i = 0; i < length; i++)
    {
        s += str.Substring(r.Next(0, str.Length - 1), 1);
    }
    return s;
}

文件读取和写入实现

从第10个字节开始,读取256个字节,前10个字节里包含有文件的类型、长度、版本等信息。在读取之前最好先判断下文件是否存在、长度信息、是否被其他进程占用等。

读取和写入的长度一定要一致,不然文件的内容在写入时会被覆盖掉,且无法恢复。

 /// 
 /// 读取文件中的256个字节
 /// 
 /// 
 /// 
 public static byte[] ReadFile(string path)
 {
     long startPosition = 10; // 从文件的第10个字节开始读取
     long length =256; // 需要读取的字节数
     string res = "";
     byte[] bytes;
     // 打开文件流
     using (FileStream fileStream = new FileStream(path, FileMode.Open))
     {
         // 设置文件流的起始位置
         fileStream.Seek(startPosition, SeekOrigin.Begin);

         // 创建二进制读取器
         using (BinaryReader binaryReader = new BinaryReader(fileStream))
         {
             // 读取指定长度的二进制数据
             bytes = binaryReader.ReadBytes((int)length);
         }
     }
     return bytes;
 }
 /// 
 /// 向文件中写入256个字节
 /// 
 /// 
 /// 
 public static void WriteFile(string path, byte[] data)
 {
     long startPosition = 10; // 从文件的第10个字节开始写入
     using (FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Write))
     {
         // 将文件指针移动到指定位置
         fileStream.Seek(startPosition, SeekOrigin.Begin);

         // 将数据写入文件
         fileStream.Write(data, 0, data.Length);
     }
 }

加密、解密单个文件

在加密、解密文件时可以配合数据库一起使用,加密和解密使用的密钥要相同,如果要批量对文件进行加密、解密,那么就使用线程池来实现,避免用户界面无响应。

/// 
/// 使用普通方式加密文件中的一部分
/// 
/// 文件路径
/// 是否多线程
public void LockFile(string path,Boolean isThread) 
{
    byte[] key = SM4.StrToBytes(db.Key);
    byte[] iv = SM4.StrToBytes(db.Iv);

    //读取256字节数据
    byte[] data = db.ReadFile(path);
    byte[] Edata = SM4.EncryptSM4(data, key, iv);
    db.WriteFile(path, Edata);

    //更新数据库
    string sql = "update 文件列表 set 状态='已加密' where 文件='" + path + "'";
    db.ExecSQL(sql);
}

/// 
/// 使用普通方式解密文件,解密后的数据写入原文件
/// 
/// 
public void unLockFile(string path,Boolean isThread) 
{
    byte[] key = SM4.StrToBytes(db.Key);
    byte[] iv = SM4.StrToBytes(db.Iv);
    byte[] data = db.ReadFile(path);
    byte[] Edata = SM4.DecryptSM4(data, key, iv);
    db.WriteFile(path, Edata);

    //更新数据库
    string sql = "update 文件列表 set 状态='已解密' where 文件='" + path + "'";
    db.ExecSQL(sql);
}

运行界面


在文件全部都处于未加密状态时,可以重置SM4密钥,每次重置时都随机生成一对全新的密钥。

在加密文本文件时,不管是否加密都可以打开,只是加密后打开的话文本内容部分是乱码了。

点击这里复制本文地址 以上内容由文彬编程网整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!
qrcode

文彬编程网 © All Rights Reserved.  蜀ICP备2024111239号-4