首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >在AES-128 CBC模式下加密流结束时的Crypto++额外块

在AES-128 CBC模式下加密流结束时的Crypto++额外块
EN

Stack Overflow用户
提问于 2019-02-20 17:11:50
回答 1查看 862关注 0票数 2

我试图在CBC模式下使用AES-128加密320字节的二进制数据,并将密码存储到文件中。输出文件应该是320字节,但我有336字节。这是我的代码:

代码语言:javascript
运行
复制
#include <iostream>
#include <fstream>
#include <crypto++/aes.h>
#include <crypto++/modes.h>
#include <crypto++/base64.h>
#include <crypto++/sha.h>
#include <cryptopp/osrng.h>
#include <crypto++/filters.h>
#include <crypto++/files.h>

namespace CryptoPP
{
    using byte = unsigned char;
}

void myAESTest()
{
    std::string password = "testPassWord";

    // hash the password string
    // -------------------------------
    CryptoPP::byte key[CryptoPP::AES::DEFAULT_KEYLENGTH], iv[CryptoPP::AES::BLOCKSIZE];
    CryptoPP::byte passHash[CryptoPP::SHA256::DIGESTSIZE];
    CryptoPP::SHA256().CalculateDigest(passHash, (CryptoPP::byte*) password.data(), password.size());
    std::memcpy(key, passHash, CryptoPP::AES::DEFAULT_KEYLENGTH);
    std::memcpy(iv, passHash+CryptoPP::AES::DEFAULT_KEYLENGTH, CryptoPP::AES::BLOCKSIZE);

    // encrypt
    // ---------------------------------
    int chunkSize = 20*CryptoPP::AES::BLOCKSIZE;

    CryptoPP::CBC_Mode<CryptoPP::AES>::Encryption encryptor;
    encryptor.SetKeyWithIV(key, sizeof(key), iv);

    std::ofstream testOut("./test.enc", std::ios::binary);
    CryptoPP::FileSink outSink(testOut);

    CryptoPP::byte message[chunkSize];

    CryptoPP::StreamTransformationFilter stfenc(encryptor, new CryptoPP::Redirector(outSink));

    for(int i = 0; i < chunkSize; i ++)
    {
        message[i] = (CryptoPP::byte)i;
    }

    stfenc.Put(message, chunkSize);
    stfenc.MessageEnd();
    testOut.close();

    // decrypt
    // ------------------------------------
    // Because of some unknown reason increase chuksize by 1 block
    // chunkSize+=16;

    CryptoPP::byte cipher[chunkSize], decrypted[chunkSize];

    CryptoPP::CBC_Mode<CryptoPP::AES>::Decryption decryptor;
    decryptor.SetKeyWithIV(key, sizeof(key), iv);

    std::ifstream inFile("./test.enc", std::ios::binary);
    inFile.read((char *)cipher, chunkSize);

    CryptoPP::ArraySink decSink(decrypted, chunkSize);
    CryptoPP::StreamTransformationFilter stfdec(decryptor, new CryptoPP::Redirector(decSink));

    stfdec.Put(cipher, chunkSize);
    stfdec.MessageEnd();
    inFile.close();

    for(int i = 0; i < chunkSize; i++)
    {
        std::cout << (int)decrypted[i] << ' ';
    }
    std::cout << std::endl;
}

int main(int argc, char* argv[]) 
{
    myAESTest();
    return 0;
}

我无法理解最后16个字节是如何生成的。如果我选择忽略解密中的最后16个字节,CryptoPP将引发CryptoPP::InvalidCiphertext错误:

代码语言:javascript
运行
复制
terminate called after throwing an instance of 'CryptoPP::InvalidCiphertext'
  what():  StreamTransformationFilter: invalid PKCS #7 block padding found
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2019-02-20 18:53:30

我无法理解最后16个字节是如何生成的。如果我选择忽略解密中的最后16个字节,Crypto++将引发InvalidCiphertext错误。

最后16个字节是填充。填充是由StreamTransformationFilter过滤器添加的;参见手册中的StreamTransformationFilter类引用。虽然并不明显,但DEFAULT_PADDINGECB_ModeCBC_ModePKCS_PADDING。它是NO_PADDING,用于其他模式,如OFB_ModeCTR_Mode

您只需要为加密和解密过滤器指定NO_PADDING。但是,您必须确保明文和密文是块的倍数,对于AES来说是16。

您可以通过切换到另一种模式(如CTR_Mode )来避开块大小限制。但是,您必须非常小心密钥或IV的重用,这可能是困难的,因为您正在使用的密码派生方案。

因此,与其:

代码语言:javascript
运行
复制
CBC_Mode<AES>::Encryption encryptor;
...
StreamTransformationFilter stfenc(encryptor, new Redirector(outSink));

使用:

代码语言:javascript
运行
复制
CBC_Mode<AES>::Encryption encryptor;
...
StreamTransformationFilter stfenc(encryptor, new Redirector(outSink), NO_PADDING);

还请参阅模式上的Crypto++ wiki。您也可能对wiki上的认证加密感兴趣。

为此,您还可以:

代码语言:javascript
运行
复制
#ifndef CRYPTOPP_NO_GLOBAL_BYTE
namespace CryptoPP
{
    using byte = unsigned char;
}
#endif

CRYPTOPP_NO_GLOBAL_BYTE是在修复之后定义的。如果未定义CRYPTOPP_NO_GLOBAL_BYTE,则byte位于全局命名空间中(Crypto++ 5.6.5及更早版本)。如果定义了CRYPTOPP_NO_GLOBAL_BYTE,则byte位于CryptoPP命名空间(Crypto++ 6.0及更高版本)中。

为此:

代码语言:javascript
运行
复制
std::ofstream testOut("./test.enc", std::ios::binary);
FileSink outSink(testOut);

你也可以:

代码语言:javascript
运行
复制
FileSink outSink("./test.enc");

为此:

代码语言:javascript
运行
复制
SHA256().CalculateDigest(passHash, (byte*) password.data(), password.size());
std::memcpy(key, passHash, AES::DEFAULT_KEYLENGTH);
std::memcpy(iv, passHash+AES::DEFAULT_KEYLENGTH, AES::BLOCKSIZE);

您可以考虑使用HKDF作为派生函数。使用一个密码,但两个不同的标签,以确保独立的派生。一个标签可能是字符串"AES key derivation version 1",另一个标签可能是"AES iv derivation version 1"

标签将用作DeriveKeyDeriveKey参数。你只需要叫它两次,一次是为了钥匙,一次是为了iv。

代码语言:javascript
运行
复制
unsigned int DeriveKey (byte *derived, size_t derivedLen,
    const byte *secret, size_t secretLen,
    const byte *salt, size_t saltLen,
    const byte *info, size_t infoLen) const

secret是密码。如果您有salt,那么使用它。否则,HKDF将使用默认的盐类。

还请参阅香港发展基金上的Crypto++ wiki。

最后,关于这一点:

您可以通过切换到另一种模式(如CTR_Mode )来避开块大小限制。但是,您必须非常小心密钥或IV的重用,这可能是困难的,因为您正在使用的密码派生方案。

您还可以考虑使用集成加密方案,如椭圆曲线综合加密方案。它是IND-CCA2,它是一个强有力的安全概念。您需要的一切都被打包到加密方案中。

在ECIES下,每个用户都会得到一个公共/私有键盘。然后,使用一个大的随机秘密作为AES密钥、iv密钥和mac密钥的种子。明文经过加密和身份验证。最后,根据用户的公钥对种子进行加密。仍然使用密码,但用于解密私钥。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/54791882

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档