1. 如何循环左移?
1.1 先来区分一下概念:
- 物理左移SHL:丢弃最高位,0补最低位
- 循环左移ROTL:将丢弃的最高位补到低位
1.2 代码示例:32位无符号数左移和循环左移
#define SHL(x,n) (((x) & 0xFFFFFFFF) << n )
#define ROTL(x,n) (SHL((x),n) | ((x) >> (32 - n)))
- a = 01111011 循环左移两位
- b = a >> (8 - 2) = 00000001 记录即将被丢弃的高位
- a = a << 2 物理左移
- a = a | b = 01111011 | 00000001 = 11101101
1.3 解释代码(代码只针对无符号数)
- 宏调用,在编译前把记号传递给程序,根据需要也可以改写成函数
- SHL:左移,x为进行左移的数据,n为要左移的位数
- ROTL:循环左移,x为进行左移的数据,n为要左移的位数,32为数据的位数
为什么要 & 0xFFFFFFFF 呢?
(这里扩展一下“&”与运算的用途)
总结:两位同时为1,结果才为1,否则结果为0。
用途:
- 清零(与全0按位与)
- 取一个数的指定位(取几位用几位1)
- 判断奇偶(看末位)
2. 如何大数转换呢?
2.1 先来看一下应用场景:
在加密算法中总需要将明文或者密钥进行分组
- 例如,一般见到的input是这样的:
-
uint8_t input[16] = { 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c, }
- 需要将数据合成这样:
-
uint8_t input_2[4] = { 0x2b7e1516, 0x28aed2a6,0xabf71588, 0x09cf4f3c, }
- 有时候又需要将上边的过程逆过来
2.2.1 代码示例1.0
uint8_t Num_Array[MAXSIZE];
void Group_out(uint16_t Num)
{
int i, j, x = sizeof(Num_Array) - 1;
while(Num > 0)
{
i = 0;
j = 0;
i = Num % 16;
Num = Num / 16;
j = Num % 16;
Num = Num % 16;
Num_Array[x] = i + j * 16;
x--;
}
}
- 想法源自手算16进制的按权相加
- 看着麻烦,于是有了2.0
2.2.2 代码示例2.0
#define Group_out(n,b,i) \
{ \
(b)[(i) ] = (unsigned char) ( (n) >> 24 ); \
(b)[(i) + 1] = (unsigned char) ( (n) >> 16 ); \
(b)[(i) + 2] = (unsigned char) ( (n) >> 8 ); \
(b)[(i) + 3] = (unsigned char) ( (n) ); \
}
#define Group_in(n,b,i) \
{ \
(n) = ( (unsigned long) (b)[(i) ] << 24 ) \
| ( (unsigned long) (b)[(i) + 1] << 16 ) \
| ( (unsigned long) (b)[(i) + 2] << 8 ) \
| ( (unsigned long) (b)[(i) + 3] ); \
}
2.3 解释代码2.0
- n 为一串数字,例如:0x12345678
- b 为将一串数字分解后的数组,例如:0x12,0x34,0x56,0x78
- i 为数组序号,因为需要分很多的数呀
原理:
发挥想象,通俗一点讲,0x12,0x34,0x56,0x78 <——> 0x12345678,无非就是将每个数放到合适的位置。
深究原理,其实与按权相加一样的道理:
- 左移,对于正数来说,左移相当于乘以2(但效率比乘法高);
- 右移,对于正数来说,右1移相当于除以2(但效率比除法高);
3. 加密数据填充(填充0x80、0x00、数据长度)
3.1 填充方式说明(SM3算法为例)
消息m的长度为l比特。首先将“1”添加到消息末尾,再添加k个“0”,k是满足 l + 1 + k ≡ 448mod512 的最小的非负整数。然后再添加一个64位比特串,该比特串是长度l的二进制表示。填充后的消息m’的比特长度为512的倍数。
3.2.1 代码示例1.0
初接触时,按照填充描述,创建了一个巨长的数组,将每一比特填充了出来。代码巨长,时间复杂度和空间复杂度也巨大,现在看来真是一坨代码。这里不做展示了。
3.2.2 代码示例(部分)2.0
const uint8_t padding[64] =
{
0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
}
high = ( len[0] >> 29 ) | ( len[1] << 3 ); // 数据长度(字节)高位
low = ( len[0] << 3 ); // 数据长度(字节)低位
Group_out( high, msglen, 0 ); // 数据长度填入msglen数组中
Group_out( low, msglen, 4 );
last = len[0] & 0x3F; // 取低六位数值
padn = ( last < 56 ) ? ( 56 - last ) : ( 120 - last ); // 见下面讲解
// 填充好的数据 = 原数据 + padding(截取padn) + msglen
3.3 解释代码
先深入理解一下填充方式:
- 首先,目的是将长度为l的消息填充为512比特的整数倍,那么用数学表达式的方法来看 (消息长度 + 填充的长度) mod 512 ≡ 0;
- 接着,看一下填充的要求,“1”占了一个比特位,64比特的长度占了64个比特位,剩下的就是需要自己求的占k个比特位的“0”。由此就能知道 l + 1 + k ≡ 448mod512 的含义了,即 (消息长度 + 填充的长度 - 64) mod 512 ≡ 448
-
- 当消息长度无限趋近于0,那 k + 1(填充的长度 - 64 )取最大值为448(64字节),所以设置padding初始数组为
const uint8_t padding[64] =
{
0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
}
-
- 还是由上边的数学表达式推出:k + 1 = 512*n + 448 -l (n = 0,1,2…)
-
- 改成字节就是(padn为k+1的长度,last为消息长度,单位字节): padn = 64*n + 56 - last (n = 0,1,2…)
-
- 可以由一个三目运算符表示出来:padn = ( last < 56 ) ? ( 56 - last ) : ( 120 - last )
-
- 这样的话,在初始数组上截取我们需要的长度就可以了。
- 最后,原数据 + padding(截取padn字节) + msglen 就得到了填充好的数据m’