【发布时间】:2021-04-27 23:11:26
【问题描述】:
我刚刚发现我的 PKCS#5 不符合标准,因为如果数据长度是块大小的倍数,则不会添加填充,除非所有最后一个块字节都等于块大小(最后一个块就像一个填充块)。我看到标准行为是在数据大小是块长度的倍数时添加块大小的填充块,无论最后一个块内容如何。
我很困惑,因为我的实现只是在工作,并且在数据大小对齐时保存了额外的填充,那么为什么标准中有这个要求?
这是一些关于 aes 块大小(16 字节)的测试代码:
#include <iostream>
#include <array>
#include <vector>
#include <cassert>
template<size_t block_length>
inline size_t calc_existing_padding_size(const uint8_t* buff, size_t size)
{
assert(size && size % block_length == 0);
uint8_t last_byte = buff[size - 1];
if (last_byte > block_length || !last_byte)
return 0;
auto end = buff + size;
auto start = end - last_byte;
while (start != end)
{
if (*start != last_byte)
return 0;
++start;
}
return last_byte;
}
template<size_t block_length>
inline size_t calc_required_padding_size(const uint8_t* buff, size_t size)
{
assert(buff && size);
if (size < block_length)
return block_length - size;
if ((size == block_length) || (size % block_length == 0))
{
if (calc_existing_padding_size<block_length>(buff, size))
return block_length;
return 0;
}
return block_length - (size % block_length);
}
template<size_t block_length>
void add_padding(std::vector<uint8_t>& data)
{
auto padding_size = calc_required_padding_size<block_length>(data.data(), data.size());
data.resize(padding_size + data.size(), static_cast<uint8_t>(padding_size));
}
template<size_t block_length>
void remove_padding(std::vector<uint8_t>& data)
{
auto padding_size = calc_existing_padding_size<block_length>(data.data(), data.size());
data.resize(data.size() - padding_size);
}
struct test_entry
{
std::vector<uint8_t> data;
std::vector<uint8_t> padded_data;
std::vector<uint8_t> unpadded_data;
test_entry(std::vector<uint8_t> test_data, std::vector<uint8_t> after_padding)
: data(std::move(test_data)), padded_data(std::move(after_padding))
{
unpadded_data = data;
}
};
int main()
{
constexpr size_t block_length = 16;
std::vector<test_entry> tests;
auto make_test_entry = [](size_t data_size, uint8_t fill_byte)
{
assert(data_size <= block_length);
std::vector<uint8_t> data(data_size, fill_byte);
std::vector<uint8_t> padded_data = data;
padded_data.resize(block_length, static_cast<uint8_t>(block_length - data_size ));
return test_entry(std::move(data), std::move(padded_data));
};
for (size_t i = 0; i < block_length - 1; ++i)
{
tests.emplace_back(make_test_entry(i + 1, static_cast<uint8_t>(i + 1)));
}
tests.emplace_back(make_test_entry(block_length, '5'));
{
std::vector<uint8_t> data(block_length, static_cast<uint8_t>(block_length));
auto padded_data = data;
padded_data.resize(padded_data.size() + block_length, static_cast<uint8_t>(block_length));
tests.emplace_back(std::move(data), std::move(padded_data));
}
std::cout << "[*] performing " << tests.size() << " tests" << std::endl;
for (size_t i = 0; i < tests.size(); ++i)
{
add_padding<block_length>(tests[i].data);
if (tests[i].data.size() != tests[i].padded_data.size()
|| ! std::equal(tests[i].data.begin(), tests[i].data.end(), tests[i].padded_data.begin()))
{
std::cout << "[!] test [" << i << "] failed in adding padding !" << std::endl;
continue;
}
remove_padding<block_length>(tests[i].data);
if (tests[i].data.size() != tests[i].unpadded_data.size()
|| ! std::equal(tests[i].data.begin(), tests[i].data.end(), tests[i].unpadded_data.begin()))
{
std::cout << "[!] test [" << i << "] failed in removing padding !" << std::endl;
continue;
}
std::cout << "[*] test [" << i << "] passed" << std::endl;
}
}
【问题讨论】:
-
当试图解密代码时不知道是否有填充。
-
不,可以根据结果大小和最后一个块来确定
-
块大小是固定的,在不知道消息细节的情况下被解密。那么最后 5 个字节(比如说)填充是要删除还是要返回的数据?
-
编辑了代码以反映我的实际实现。如果大小对齐并且最后一个块类似于填充块,则附加完整的填充块,否则不附加。所以在加密时,如果明文以 5 字节等于 0x5 结尾,则附加一个 16 字节的填充块,以便被解密器删除
-
PKCS#7 假定不知道明文。让它加密到一定数量的字节,然后偶尔添加一个额外的块是一个棘手的提议 - 毕竟你不能真正依赖密文大小更小。除此之外,很难提前计算密文大小。但是,是的,原则上上面可以工作。不是我们关心,更多的密码学家已经转向 CTR,它根本不填充。大多数经过身份验证的密码在下面使用 CTR 模式。
标签: c++ cryptography