PKCS之数据块填充算法
PKCS(Public Key Cryptography Standards)公钥加密标准包含了一系列公钥密码学标准,其中包含了数据填充的相关规范。
当涉及到块加密算法(例如AES)时,PKCS#7(扩展了PKCS#5)是一种常用的填充方式。
当数据长度不是加密算法所要求块大小(AES块大小为16字节)的整数倍时,就需要使用填充算法来确保数据能够被正确地加密和解密。
PKCS#7
在PKCS#7中,如果数据的长度不是块大小的整数倍,那么会在数据的末尾添加额外的字节来进行填充。填充字节的数量由最后一个字节值给出,该值表示添加了多少个填充字节(取值在1到块大小之间)。
如果数据已经是块大小的整数倍,那么添加一个额外的块作为补充,其中每个字节的值都是块的大小(对于AES,填充值为16,即0x10
)。
解密后如何判断最后一个字节不是明文,而是填充值?
如果解密后最后一个字节是明文的话,那么可以确定明文的长度刚好为数据块大小的整数倍;然而当明文长度为数据块大小的整数倍时,PKCS#7要求要填充一个额外的块(块中所有字节值为0x10
),这显然是相互矛盾的。
如果明文的最后一个字节值为0x10
且其长度刚好为数据块的整数倍,那么依据PKCS#7还要填充一个值为0x10
的额外的块作为补充,此时解密后的数据末尾至少具有17字节的值为0x10
。
综上所述,可以根据解密后数据的最后一个字节值来判断填充了多少个字节,最后一个字节值不会为0(会用0x10
代替)。
填充原理
- 确定填充字节的数量:首先,计算原始数据长度与加密算法块大小(对于AES,块大小通常为128位,即16字节)之间的差值。这个差值就是需要填充的字节数量。
- 填充数据:在原始数据的末尾添加填充字节。每个填充字节的值都等于需要填充的字节数量。如果数据已经是块大小的整数倍,那么还需要添加一个完整的块,该块的所有字节都设置为块大小的值(对于16字节的块,这些字节的值就是16,即十六进制的
10
)。
数学表示如下:
- 假设原始数据长度为
x
字节。
- 块大小为
b
字节(对于AES,b = 16
)。 - 需要填充的字节数
n = b - (x % b)
。 - 填充后的数据将是原始数据后跟
n
个值为n
的字节。
示例1
假设我们有一个10字节长的数据块,并且我们使用的是16字节的块大小(如AES)。那么,我们需要填充6个字节(因为16 - 10 = 6)。这6个字节的值都是6(因为我们需要填充6个字节)。所以,填充后的数据块将是原始数据后跟6个值为6的字节。
示例2
如果原始数据是 FF FF FF FF
(4字节),并且我们使用的是16字节的块大小,那么需要填充12个字节(因为16 - 4 = 12)。这12个字节的值都是12(因为我们需要填充12个字节)。所以,填充后的数据将是 FF FF FF FF 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C
。
注意事项
- 填充是为了确保数据长度是加密算法块大小的整数倍。
- 每个填充字节的值都等于需要填充的字节数量。
- 如果数据已经是块大小的整数倍,则添加一个完整的块,该块的所有字节都设置为块大小的值。
- PKCS#5是PKCS#7的一个子集,当块大小为8字节时,两者是等效的。对于大于8字节的块大小,PKCS#7允许使用更大的块大小。
实现
下面的示例中实现了PKCS#7填充算法:
// base为基于填充的块大小
int pkcs7_padding(unsigned char* plaintext, int plaintext_len, unsigned char** output, int* output_len, int base)
{
int iRet = -1;
if (plaintext == NULL || plaintext_len < 0)
return iRet;
// 如果数据长度为块大小的整数倍,则额外填充一个块
unsigned char padding_val = base - (plaintext_len % base);
int padding_len = plaintext_len + (base - (plaintext_len % base));
unsigned char* padding = (unsigned char*)malloc(padding_len);
if (padding == NULL)
return iRet;
memset(padding, 0x00, padding_len);
memcpy(padding, plaintext, plaintext_len);
memset(padding + plaintext_len, padding_val, padding_len - plaintext_len);
*output = padding;
*output_len = padding_len;
iRet = 0;
return iRet;
}
其中base
参数用于指定数据块的大小。
在使用完毕之后需要手动调用free
释放output
。
测试代码以及结果如下:
int main()
{
char str1[] = "0123456789abcdef";
unsigned char* output = NULL;
int output_len = 0;
int base = 16;
printf("plaintext: ");
for (int i = 0; i < strlen(str1); i++)
{
printf("%2X ", str1[i]);
}
printf("\n");
if (pkcs7_padding((unsigned char*)str1, strlen(str1), &output, &output_len, base) == 0 && output != NULL &&
output_len > 0)
{
printf("padding_val:");
for (int i = 0; i < output_len; i++)
{
printf("%2X ", output[i]);
}
printf("\n");
free(output);
}
char str2[] = "0123456789";
printf("plaintext: ");
for (int i = 0; i < strlen(str2); i++)
{
printf("%2X ", str2[i]);
}
printf("\n");
if (pkcs7_padding((unsigned char*)str2, strlen(str2), &output, &output_len, base) == 0 && output != NULL &&
output_len > 0)
{
printf("padding_val:");
for (int i = 0; i < output_len; i++)
{
printf("%2X ", output[i]);
}
printf("\n");
free(output);
}
}
// plaintext: 30 31 32 33 34 35 36 37 38 39 61 62 63 64 65 66
// padding_val:30 31 32 33 34 35 36 37 38 39 61 62 63 64 65 66 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
// plaintext: 30 31 32 33 34 35 36 37 38 39
// padding_val:30 31 32 33 34 35 36 37 38 39 6 6 6 6 6 6
可以看见执行结果与PKCS#7加密原理一致。
- 原文作者:生如夏花
- 原文链接:https://blduan.top/post/%E6%95%B0%E5%AD%97%E5%AE%89%E5%85%A8/pkcs%E4%B9%8B%E6%95%B0%E6%8D%AE%E5%9D%97%E5%A1%AB%E5%85%85%E7%AE%97%E6%B3%95/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。