在AES-128 CBC模式下加密流末尾的加密++额外块

问题描述 投票:2回答:1

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

#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错误:

terminate called after throwing an instance of 'CryptoPP::InvalidCiphertext'
  what():  StreamTransformationFilter: invalid PKCS #7 block padding found
c++ crypto++
1个回答
1
投票

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

最后16个字节是填充。填充由q​​azxswpoi过滤器添加;请参阅手册中的StreamTransformationFilter。虽然不是很明显,StreamTransformationFilter Class ReferenceDEFAULT_PADDINGPKCS_PADDINGECB_Mode。对于像CBC_ModeNO_PADDING这样的其他模式,它是OFB_Mode

您只需要为加密和解密过滤器指定CTR_Mode。但是,您必须确保明文和密文是块大小的倍数,AES为16。

您可以通过切换到NO_PADDING等其他模式来回避blocksize限制。但是,您必须非常小心密钥或IV重用,由于您使用的密码派生方案,这可能很难。

所以代替:

CTR_Mode

使用:

CBC_Mode<AES>::Encryption encryptor;
...
StreamTransformationFilter stfenc(encryptor, new Redirector(outSink));

另请参阅Crypto ++ wiki上的CBC_Mode<AES>::Encryption encryptor; ... StreamTransformationFilter stfenc(encryptor, new Redirector(outSink), NO_PADDING); 。您可能也对wiki上的CBC_Mode感兴趣。


为此,您还可以:

Authenticated Encryption

#ifndef CRYPTOPP_NO_GLOBAL_BYTE namespace CryptoPP { using byte = unsigned char; } #endif 是在CRYPTOPP_NO_GLOBAL_BYTE之后定义的。如果未定义C++17 std::byte fixes,则CRYPTOPP_NO_GLOBAL_BYTE位于全局命名空间(Crypto ++ 5.6.5及更早版本)。如果定义了byte,则CRYPTOPP_NO_GLOBAL_BYTE位于byte命名空间(Crypto ++ 6.0及更高版本)。


为了这:

CryptoPP

你也可以这样做:

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

为了这:

FileSink outSink("./test.enc");

您可以考虑使用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"info参数。你只需要调用它两次,一次用于键,一次用于iv。

DeriveKey

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然后使用它。否则HKDF使用默认盐。

另请参阅Crypto ++ wiki上的salt


最后,关于这个:

您可以通过切换到CTR_Mode等其他模式来回避块大小限制。但是,您必须非常小心密钥或IV重用,由于您使用的密码派生方案,这可能很难。

您可能还会考虑使用HKDF等集成加密方案。它是Elliptic Curve Integrated Encryption Scheme,这是一个强大的安全概念。您需要的一切都打包到加密方案中。

在ECIES下,每个用户都获得一个公共/私人密钥对。然后,使用大的随机密钥作为AES密钥,iv和mac密钥的种子。明文经过加密和验证。最后,种子在用户的公钥下加密。密码仍在使用,但它用于解密私钥。

© www.soinside.com 2019 - 2024. All rights reserved.