在这里记录一下工作中调查国密算法SM4过程中掌握的心得体会。
密钥分散
对基于 SM4 的分散算法的描述。
密钥分散算法简称 Diversify,是指将一个双长度的密钥 MK,对分散数据进行处理,推导出双长度的密钥 DK。
将分散数据的 8 个字节,作为输入数据的左半部分:
将分散数据的 8 个字节求反,作为输入数据的右半部分;
用 MK 对输入数据进行 SM4 运算。
/// <summary>
/// 基于SM4的分散算法。
/// 将分散数据的 8 个字节,作为输入数据的左半部分;将分散数据的 8 个字节求反,作为输入数据的右半部分; 用CKKey 对输入数据进行 SM4 运算。
/// </summary>
/// <param name="SourceData"></param>
/// <param name="srcIndex"></param>
/// <param name="CKKey"></param>
/// <param name="subKey"></param>
public static void GetSubKey_SM4(byte[] SourceData, int srcIndex, byte[] CKKey, ref byte[] subKey)
{
int num;
byte[] pvInOutputBlock = new byte[0x10];
for (num = 0; num < 8; num++)
{
pvInOutputBlock[num] = SourceData[num + srcIndex];
}
for (num = 0; num < 8; num++)
{
pvInOutputBlock[8 + num] = (byte)~SourceData[num + srcIndex];
}
CombinedSM4Operation(ref pvInOutputBlock, null, CKKey, DES.ZERO_CBC_IV | DES.TRIPLE_DES_ENCRYPTION_EDE2, 1);
Array.Copy(pvInOutputBlock, 0, subKey, 0, 16);
}
加解密
SM4属于对称算法,加密和解密密钥相同。
SM4算法:
SM4算法是指使用长度为16字节的密钥K将16字节明文数据块加密成密文数据块,如下所
示:
Y = SM4 (K) [X]
解密的方式如下:
X = SM4-1 (K)[X]
计算过程:
数据的加密解密计算采用ECB 作为块操作模式按照如下步骤对数据进行加密:
第一步: 用Ld(1 字节)表示明文数据的长度,在明文数据前加上Ld 产生新的数据块。
第二步:将该数据块分成以分组长度 128 位为单位的数据块,表示为块 1、块 2、…、块 n。
第三步:如果最后(或唯一) 的数据块的长度是分组长度,转到第四步:如果不足分组长度,则在其后加入16进制数’80’,如果达到分组长度,则转到第四步:否则在其后加入16进制数’00’直到长度达到分组长度。
第四步:使用加密密钥对每一个数据块进行加密。
第五步: 计算结束后,所有加密后的数据块依照原顺序连接在一起。
按照如下步骤对数据进行解密:
第一步: 将该数据块分成以分组长度128位为单位的数据块,,表示为块1、块2、…、块n。
第二步: 使用解密密钥对每个数据块进行解密。
第三步: 计算结束后,所有解密后的数据块依照原顺序连接在一起。
第四步: 第一个字节为 Ld,从第二字节起,取前 Ld 字节数据作为明文输出。
使用了BouncyCastle.Crypto开源库,NuGet安装即可。
代码中的一些变量自己定义。
/// <summary>
/// SM4加解密算法
/// 数据的加密解密计算采用ECB作为块操作模式。
/// </summary>
/// <param name="pvInOutputBlock">数据块</param>
/// <param name="pvCbcBlock">保留</param>
/// <param name="pbKeyString">密钥</param>
/// <param name="eSm4Mode">加解密模式</param>
/// <param name="cnt">轮数</param>
public static void CombinedSM4Operation(ref byte[] pvInOutputBlock, byte[] pvCbcBlock, byte[] pbKeyString, uint eSm4Mode, uint cnt)
{
// 补0到最小位数
string data = CommonFunc.Instance.ConvertByteToString(pvInOutputBlock);
if ((data.Length % 0x20) != 0)
{
if ((data.Length % 0x20) != 0)
{
data = data.PadRight(0x20 * ((data.Length / 0x20) + (((data.Length % 0x20) == 0) ? 0 : 1)), '0');
}
}
pvInOutputBlock = CommonFunc.Instance.ConvertStringToByte(data);
bool forEncryption = (eSm4Mode == (DES.ZERO_CBC_IV | DES.TRIPLE_DES_ENCRYPTION_EDE2)); // true 表示加密, false表示解密
SM4Engine sm4 = new SM4Engine(); // 创建 SM4 算法实例
sm4.Init(forEncryption, new KeyParameter(pbKeyString)); // 设置加密模式和密钥 默认ECB模式
byte[] encryptedData = new byte[pvInOutputBlock.Length]; // 存储加密后的数据
if (cnt == 0)
{
cnt = 1;
}
// 按照 ECB 算法使用加密密钥对每一个数据块进行加密
for (int i = 0; i < cnt; i ++)
{
if (pvInOutputBlock.Length < (i + 1) * sm4.GetBlockSize())
{ break; }
sm4.ProcessBlock(pvInOutputBlock, i* sm4.GetBlockSize(), encryptedData, i* sm4.GetBlockSize());
}
Array.Copy(encryptedData, pvInOutputBlock, encryptedData.Length);
}
MAC计算
报文认证码的计算采用分组长度为 128 位分组计算,采用 CBC 分组操作模式按照如下步骤计算报文认证码 MAC:
第一步: 终端 GET CHALLENGE 命令从 IC 获得一个 4/8/16 字节随机数,后补 12/8/0字节‘00’作为初始数据。
第二步:将所有输入数据按指定顺序连接成一个数据块。
第三步:将该数据块分成以分组长度 128 位为单位的数据块,表示为块 1、块 2、···、块n。
第四步:在最后的数据块后加入 16 进制数’80’,如果此时达到分组长度,则转到第五否则在其后加入 16 进制数‘00’直到长度达到分组长度。
第五步:按下图所述的算法对这些数据块使用指定密钥进行加密来产生 MAC。
第六步: 返回MAC计算数据,具体怎么取还要看需求。一般取计算结果前4字节长度的MAC。
这是第五步中的算法代码文章来源:https://www.toymoban.com/news/detail-733809.html
/// <summary>
/// SM4 MAC计算方法
/// </summary>
/// <param name="pvSubKeys">密钥</param>
/// <param name="pvCbcBlock">IV</param>
/// <param name="pbInputData">数据块</param>
/// <param name="eNBytes">保留</param>
/// <param name="pbMac32Out">返回值mac</param>
public static void CalcPBOCMac_SM4(byte[] pvSubKeys, byte[] pvCbcBlock, byte[] pbInputData, uint eNBytes, ref byte[] pbMac32Out)
{
// 补0到最小位数
string data = CommonFunc.Instance.ConvertByteToString(pvCbcBlock);
if ((data.Length % 0x20) != 0)
{
if ((data.Length % 0x20) != 0)
{
data = data.PadRight(0x20 * ((data.Length / 0x20) + (((data.Length % 0x20) == 0) ? 0 : 1)), '0');
}
}
pvCbcBlock = CommonFunc.Instance.ConvertStringToByte(data);
byte[] szTempArray = new byte[16];
byte[] szBlockArray = new byte[16];
Array.Clear(szBlockArray, 0, szBlockArray.Length);
Array.Clear(szTempArray, 0, szTempArray.Length);
if (pvCbcBlock == null)
{
pvCbcBlock = szBlockArray;
}
uint num = eNBytes / 16u;
if (num * 16 < eNBytes)
{
num++;
pbInputData[eNBytes] = 128;
}
// 创建SM4引擎
SM4Engine sm4Engine = new SM4Engine();
// 设置为加密模式,并设置密钥
sm4Engine.Init(true, new KeyParameter(pvSubKeys));
byte[] encryptedData = new byte[16]; // 存储加密后的数据
for (uint num2 = 0u; num2 < num; num2++)
{
DataXOR16(ref szTempArray, pbInputData, (int)(num2 * 16), pvCbcBlock, 0);
sm4Engine.ProcessBlock(szTempArray, 0, encryptedData, 0);
Array.Copy(encryptedData, pvCbcBlock, 16);
}
Array.Copy(encryptedData, pbMac32Out, 4);
}
// 异或方法
private static void DataXOR16(ref byte[] xoData, byte[] des, int desIndex, byte[] source, int sourceIndex)
{
for (byte i = 0; i < 16; i = (byte)(i + 1))
{
xoData[i] = (byte)(des[i + desIndex] ^ source[i + sourceIndex]);
}
}
实例测试
TODO 之后更新文章来源地址https://www.toymoban.com/news/detail-733809.html
到了这里,关于【IC卡 国密SM4算法 密钥分散,加解密,MAC计算】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!