C#实现国产SM4加解密-无填充模式

有个这样的需求,需要实现对指定长度的二进制数据进行加密和解密,但是要求明文和密文的长度一样,列如明文读取了256个字节,那么加密后的密文也需要是256个字节,经过查询资料发现RC4和SM4都可以实现此需求,RC4安全性不足容易被破解,SM4是国产密码安全性好,但是SM4在加密和解密后都比明文多了16个字节,后来发现是在加解密时使用了PKCS7填充造成的,下面就RC4和SM4分别来说一下。

RC4实现

RC4实现时不需要引用第三方包,可以实现对字符串或字节数组进行加密解密,新建一个RC4类文件。

 public static class RC4
 {
     public static string Encrypt(string key, string data)
     {
         Encoding unicode = Encoding.Unicode;

         return Convert.ToBase64String(Encrypt(unicode.GetBytes(key), unicode.GetBytes(data)));
     }

     public static string Decrypt(string key, string data)
     {
         Encoding unicode = Encoding.Unicode;

         return unicode.GetString(Encrypt(unicode.GetBytes(key), Convert.FromBase64String(data)));
     }

     public static byte[] Encrypt(byte[] key, byte[] data)
     {
         return EncryptOutput(key, data).ToArray();
     }

     public static byte[] Decrypt(byte[] key, byte[] data)
     {
         return EncryptOutput(key, data).ToArray();
     }

     private static byte[] EncryptInitalize(byte[] key)
     {
         byte[] s = Enumerable.Range(0, 256).Select(i => (byte)i).ToArray();

         for (int i = 0, j = 0; i < 256; i++)
         {
             j = (j + key[i % key.Length] + s[i]) & 255;

             Swap(s, i, j);
         }

         return s;
     }

     private static IEnumerable<byte> EncryptOutput(byte[] key, IEnumerable<byte> data)
     {
         byte[] s = EncryptInitalize(key);

         int i = 0;
         int j = 0;

         return data.Select((b) =>
         {
             i = (i + 1) & 255;
             j = (j + s[i]) & 255;

             Swap(s, i, j);

             return (byte)(b ^ s[(s[i] + s[j]) & 255]);
         });
     }

     private static void Swap(byte[] s, int i, int j)
     {
         byte c = s[i];

         s[i] = s[j];
         s[j] = c;
     }
 }

SM4实现

SM4实现需要引用BouncyCastle.NetCore第三方库,目前的版本是2.2.1,网上说需要引用1.8.0版本,但那个版本不支持无填充模式,需要自己手写,新版本自带的无填充模式,更方便一些,需要注意的是使用无填充模式时明文的字节数需要是16字节的整数倍,不然会出错的,新建一个SM4类来实现加解密。

C#实现国产SM4加解密-无填充模式

SM4引用

public static  class SM4
{
    /// <summary>
    /// SM4加密
    /// </summary>
    /// <param></param>
    /// <param></param>
    /// <param></param>
    /// <returns></returns>
    public static byte[] EncryptSM4(byte[] plaintext, byte[] key, byte[] iv)
    {
        byte[] input = plaintext;
        //无填充模式
        IBufferedCipher cipher = CipherUtilities.GetCipher("SM4/CBC/NoPadding");
       //PKCS7填充模式
       // IBufferedCipher cipher = CipherUtilities.GetCipher("SM4/CBC/PKCS7");
        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;
    }
    /// <summary>
    /// SM4解密
    /// </summary>
    /// <param></param>
    /// <param></param>
    /// <param></param>
    /// <returns></returns>
    public static byte[] DecryptSM4(byte[] encrypted, byte[] key, byte[] iv)
    {
       //无填充模式
        IBufferedCipher cipher = CipherUtilities.GetCipher("SM4/CBC/NoPadding");
      //PKCS7填充模式
      //IBufferedCipher cipher = CipherUtilities.GetCipher("SM4/CBC/PKCS7");
        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;
    }
    /// <summary>
    /// 字符串转换为bytes数组
    /// </summary>
    /// <param></param>
    /// <returns></returns>
    public static byte[] StrToBytes(string str) 
    {
        return Encoding.UTF8.GetBytes(str);
    }
    /// <summary>
    /// 零填充模式
    /// </summary>
    /// <param></param>
    /// <param></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;
    }


}

在加解密时,密钥和IV都得是16字节的长度,不然也会出错,接下来进行测试

SM4加解密测试

先设置好SM4的key和iv,然后读取文件中的256个字节,并对字节进行加密和和解密,以查看其差异。

string key ="AAdddddddddddddd";
string iv = "aaaaaaaabbbbbbbb";
byte[] keys=SM4.StrToBytes(key);
byte[] ivs= SM4.StrToBytes(iv);
string path = "D:\11.jpg";
byte[] data=db.ReadFile(path);


T1.Text = BitConverter.ToString(data);
L1.Text=data.Length.ToString();

byte[] Edata = SM4.EncryptSM4(data, keys, ivs);
T2.Text=BitConverter.ToString(Edata);
L2.Text = Edata.Length.ToString();

byte[] Ddata = SM4.DecryptSM4(Edata, keys, ivs);
T3.Text=BitConverter.ToString(Ddata);
L3.Text= Ddata.Length.ToString();

C#实现国产SM4加解密-无填充模式

加密前后对比,明文和密文长度一致

对16字节进行加解密,更方便观察,但不能比16字节少,且得是16字节的倍数,否则加解密会出错。

C#实现国产SM4加解密-无填充模式

读取16字节进行加密

C#实现国产SM4加解密-无填充模式

读取24字节进行加密时就出错了

所以,对于有特殊需求的,需要加解密后长度一致的,可以使用无填充模式,或者手动进行填充,对于正常加解密,最好还是使用填充模式,可避免明文长度不确定导致的加密出错。

© 版权声明

相关文章

2 条评论

  • SeaWind___
    SeaWind___ 投稿者

    收藏了,感谢分享

    回复