有个这样的需求,需要实现对指定长度的二进制数据进行加密和解密,但是要求明文和密文的长度一样,列如明文读取了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类来实现加解密。

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();

加密前后对比,明文和密文长度一致
对16字节进行加解密,更方便观察,但不能比16字节少,且得是16字节的倍数,否则加解密会出错。

读取16字节进行加密

读取24字节进行加密时就出错了
所以,对于有特殊需求的,需要加解密后长度一致的,可以使用无填充模式,或者手动进行填充,对于正常加解密,最好还是使用填充模式,可避免明文长度不确定导致的加密出错。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
收藏了,感谢分享