【问题标题】:Why PKCS#7 always requires padding even if the data length is multiple of the block size为什么即使数据长度是块大小的倍数,PKCS#7 也总是需要填充
【发布时间】: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


【解决方案1】:

这是必要的,因此解密算法可以确定 确定最后一个块的最后一个字节是否是填充字节 指示添加的填充字节数或明文的一部分 信息。考虑一个纯文本消息,它是 B 的整数倍 明文最后一个字节为 01 的字节。没有额外的 信息,解密算法将无法确定 最后一个字节是明文字节还是填充字节。然而,由 在 01 明文字节之后添加 B 个字节,每个 B 值, 解密算法总是可以将最后一个字节视为填充字节,并且 从末尾剥离适当数量的填充字节 密文;所述的字节数基于值被剥离 最后一个字节。 https://en.wikipedia.org/wiki/Padding_(cryptography)

如果您知道消息将始终是块大小的倍数,您可以指定不应用填充的密码,或使用 cmets 中提到的不需要填充的密码模式。

【讨论】:

    猜你喜欢
    • 2012-10-07
    • 2015-10-15
    • 2017-06-22
    • 2023-04-01
    • 2011-02-04
    • 2021-10-27
    • 1970-01-01
    • 2018-04-20
    • 1970-01-01
    相关资源
    最近更新 更多