谁能解释为什么我的加密++解密文件短16个字节?

时间:2010-06-11 15:36:30

标签: c++ encryption crypto++

为了将AES加密文本作为std::istream提供给解析器组件,我尝试创建一个包含vanilla crypto ++加密/解密的std::streambuf实现。

main()函数调用以下函数来比较我的包装器和vanilla实现:

  • EncryptFile() - 使用我的streambuf实现加密文件
  • DecryptFile() - 使用我的streambuf实现解密文件
  • EncryptFileVanilla() - 使用vanilla crypto ++加密文件
  • DecryptFileVanilla() - 使用vanilla crypto ++解密文件

问题在于EncryptFile()EncryptFileVanilla()创建的加密文件完全相同。由DecryptFile()创建的解密文件不正确,比DecryptFileVanilla()创建的文件短16个字节。可能并非巧合的是,块大小也是16。

我认为这个问题必须在CryptStreamBuffer::GetNextChar(),但我一直在盯着它和加密++文档几个小时。

有人可以帮忙解释吗?

关于我的std::streambuf实施方式有多么糟糕或幼稚的任何其他评论也欢迎; - )

谢谢,

汤姆

// Runtime Includes
#include <iostream>

// Crypto++ Includes
#include "aes.h"
#include "modes.h"      // xxx_Mode< >
#include "filters.h"    // StringSource and
                        // StreamTransformation
#include "files.h"

using namespace std;

class CryptStreamBuffer: public std::streambuf {

public:

    CryptStreamBuffer(istream& encryptedInput, CryptoPP::StreamTransformation& c);

    CryptStreamBuffer(ostream& encryptedOutput, CryptoPP::StreamTransformation& c);

    ~CryptStreamBuffer();

protected:
    virtual int_type overflow(int_type ch = traits_type::eof());

    virtual int_type uflow();

    virtual int_type underflow();

    virtual int_type pbackfail(int_type ch);

    virtual int sync();

private:
    int GetNextChar();

    int m_NextChar; // Buffered character

    CryptoPP::StreamTransformationFilter* m_StreamTransformationFilter;

    CryptoPP::FileSource* m_Source;

    CryptoPP::FileSink* m_Sink;

}; // class CryptStreamBuffer

CryptStreamBuffer::CryptStreamBuffer(istream& encryptedInput, CryptoPP::StreamTransformation& c) :
    m_NextChar(traits_type::eof()),
    m_StreamTransformationFilter(0),
    m_Source(0),
    m_Sink(0) {

    m_StreamTransformationFilter = new CryptoPP::StreamTransformationFilter(c, 0, CryptoPP::BlockPaddingSchemeDef::PKCS_PADDING);
    m_Source = new CryptoPP::FileSource(encryptedInput, false, m_StreamTransformationFilter);
}

CryptStreamBuffer::CryptStreamBuffer(ostream& encryptedOutput, CryptoPP::StreamTransformation& c) :
    m_NextChar(traits_type::eof()),
    m_StreamTransformationFilter(0),
    m_Source(0),
    m_Sink(0) {

    m_Sink = new CryptoPP::FileSink(encryptedOutput);
    m_StreamTransformationFilter = new CryptoPP::StreamTransformationFilter(c, m_Sink, CryptoPP::BlockPaddingSchemeDef::PKCS_PADDING);
}

CryptStreamBuffer::~CryptStreamBuffer() {

    if (m_Sink) {
        delete m_StreamTransformationFilter;
        // m_StreamTransformationFilter owns and deletes m_Sink.
    }
    if (m_Source) {
        delete m_Source;
        // m_Source owns and deletes m_StreamTransformationFilter.
    }
}

CryptStreamBuffer::int_type CryptStreamBuffer::overflow(int_type ch) {

    return m_StreamTransformationFilter->Put((byte)ch);
}

CryptStreamBuffer::int_type CryptStreamBuffer::uflow() {

    int_type result = GetNextChar();

    // Reset the buffered character
    m_NextChar = traits_type::eof();

    return result;
}

CryptStreamBuffer::int_type CryptStreamBuffer::underflow() {

    return GetNextChar();
}

CryptStreamBuffer::int_type CryptStreamBuffer::pbackfail(int_type ch) {

    return traits_type::eof();
}

int CryptStreamBuffer::sync() {

    // TODO: Not sure sync is the correct place to be doing this.
    //       Should it be in the destructor?
    if (m_Sink) {
        m_StreamTransformationFilter->MessageEnd();
        // m_StreamTransformationFilter->Flush(true);
    }

    return 0;
}

int CryptStreamBuffer::GetNextChar() {

    // If we have a buffered character do nothing
    if (m_NextChar != traits_type::eof()) {
        return m_NextChar;
    }

    // If there are no more bytes currently available then pump the source
    if (m_StreamTransformationFilter->MaxRetrievable() == 0) {
        m_Source->Pump(1024);
    }

    // Retrieve the next byte
    byte nextByte;
    size_t noBytes = m_StreamTransformationFilter->Get(nextByte);
    if (0 == noBytes) {
        return traits_type::eof();
    }

    // Buffer up the next character
    m_NextChar = nextByte;

    return m_NextChar;
}

void InitKey(byte key[]) {

    key[0] = -62;
    key[1] = 102;
    key[2] = 78;
    key[3] = 75;
    key[4] = -96;
    key[5] = 125;
    key[6] = 66;
    key[7] = 125;
    key[8] = -95;
    key[9] = -66;
    key[10] = 114;
    key[11] = 22;
    key[12] = 48;
    key[13] = 111;
    key[14] = -51;
    key[15] = 112;
}

/** Decrypt using my CryptStreamBuffer */
void DecryptFile(const char* sourceFileName, const char* destFileName) {

    ifstream ifs(sourceFileName, ios::in | ios::binary);
    ofstream ofs(destFileName, ios::out | ios::binary);

    byte key[CryptoPP::AES::DEFAULT_KEYLENGTH];
    InitKey(key);

    CryptoPP::ECB_Mode<CryptoPP::AES>::Decryption decryptor(key, sizeof(key));

    if (ifs) {
        if (ofs) {
            CryptStreamBuffer cryptBuf(ifs, decryptor);
            std::istream decrypt(&cryptBuf);

            int c;
            while (EOF != (c = decrypt.get())) {
                ofs << (char)c;
            }
            ofs.flush();
        }
        else {
            std::cerr << "Failed to open file '" << destFileName << "'." << endl;
        }
    }
    else {
        std::cerr << "Failed to open file '" << sourceFileName << "'." << endl;
    }  
}

/** Encrypt using my CryptStreamBuffer */
void EncryptFile(const char* sourceFileName, const char* destFileName) {

    ifstream ifs(sourceFileName, ios::in | ios::binary);
    ofstream ofs(destFileName, ios::out | ios::binary);

    byte key[CryptoPP::AES::DEFAULT_KEYLENGTH];
    InitKey(key);

    CryptoPP::ECB_Mode<CryptoPP::AES>::Encryption encryptor(key, sizeof(key));

    if (ifs) {
        if (ofs) {
            CryptStreamBuffer cryptBuf(ofs, encryptor);
            std::ostream encrypt(&cryptBuf);

            int c;
            while (EOF != (c = ifs.get())) {
                encrypt << (char)c;
            }
            encrypt.flush();
        }
        else {
            std::cerr << "Failed to open file '" << destFileName << "'." << endl;
        }
    }
    else {
        std::cerr << "Failed to open file '" << sourceFileName << "'." << endl;
    }  
}

/** Decrypt using vanilla crypto++ */
void DecryptFileVanilla(const char* sourceFileName, const char* destFileName) {

    byte key[CryptoPP::AES::DEFAULT_KEYLENGTH];
    InitKey(key);

    CryptoPP::ECB_Mode<CryptoPP::AES>::Decryption decryptor(key, sizeof(key));

    CryptoPP::FileSource(sourceFileName, true,
      new CryptoPP::StreamTransformationFilter(decryptor,
        new CryptoPP::FileSink(destFileName), CryptoPP::BlockPaddingSchemeDef::PKCS_PADDING
      ) // StreamTransformationFilter
    ); // FileSource
}

/** Encrypt using vanilla crypto++ */
void EncryptFileVanilla(const char* sourceFileName, const char* destFileName) {

    byte key[CryptoPP::AES::DEFAULT_KEYLENGTH];
    InitKey(key);

    CryptoPP::ECB_Mode<CryptoPP::AES>::Encryption encryptor(key, sizeof(key));

    CryptoPP::FileSource(sourceFileName, true,
      new CryptoPP::StreamTransformationFilter(encryptor,
        new CryptoPP::FileSink(destFileName), CryptoPP::BlockPaddingSchemeDef::PKCS_PADDING
      ) // StreamTransformationFilter
    ); // FileSource
}

int main(int argc, char* argv[])
{
    EncryptFile(argv[1], "encrypted.out");
    DecryptFile("encrypted.out", "decrypted.out");
    EncryptFileVanilla(argv[1], "encrypted_vanilla.out");
    DecryptFileVanilla("encrypted_vanilla.out", "decrypted_vanilla.out");
    return 0;
}

2 个答案:

答案 0 :(得分:5)

使用crypto ++的调试版本后,结果发现缺少的是对StreamTransformationFilter的调用,告知它不会再有来自Source的内容,它应该包含最后几个字节的处理,包括填充。

CryptStreamBuffer::GetNextChar()

替换:

// If there are no more bytes currently available then pump the source
if (m_StreamTransformationFilter->MaxRetrievable() == 0) {
    m_Source->Pump(1024);
}

使用:

// If there are no more bytes currently available from the filter then
// pump the source.
if (m_StreamTransformationFilter->MaxRetrievable() == 0) {
    if (0 == m_Source->Pump(1024)) {
        // This seems to be required to ensure the final bytes are readable
        // from the filter.
        m_StreamTransformationFilter->ChannelMessageEnd(CryptoPP::DEFAULT_CHANNEL);
    }
}

我没有声称这是最好的解决方案,只是我通过反复试验发现出现才能工作。

答案 1 :(得分:3)

如果输入缓冲区不是16字节块的多重性,则需要用虚拟字节填充最后一个块。如果最后一个块小于16个字节,则由crypto ++删除并且不加密。解密时,需要截断虚拟字节。 你指的那种'另一种方式',已经为你做了添加和截断。 那么什么应该是虚拟字节,知道它们中有多少,因此应该被截断?我使用以下模式:用虚拟计数值填充每个字节。

示例:您需要添加8个字节吗?将它们设置为0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08。你需要添加3个字节?将它们设置为0x03,0x03,0x03等。

解密时,获取输出缓冲区的最后一个字节的值。假设它是N.检查,如果最后N个字节的值等于N.截断,如果为真。

<强>更新

CryptStreamBuffer::CryptStreamBuffer(istream& encryptedInput, CryptoPP::StreamTransformation& c) :
    m_NextChar(traits_type::eof()),
    m_StreamTransformationFilter(0),
    m_Source(0),
    m_Sink(0) {

    m_StreamTransformationFilter = new CryptoPP::StreamTransformationFilter(c, 0, CryptoPP::BlockPaddingSchemeDef::ZEROS_PADDING);
    m_Source = new CryptoPP::FileSource(encryptedInput, false, m_StreamTransformationFilter);
}

CryptStreamBuffer::CryptStreamBuffer(ostream& encryptedOutput, CryptoPP::StreamTransformation& c) :
    m_NextChar(traits_type::eof()),
    m_StreamTransformationFilter(0),
    m_Source(0),
    m_Sink(0) {

    m_Sink = new CryptoPP::FileSink(encryptedOutput);
    m_StreamTransformationFilter = new CryptoPP::StreamTransformationFilter(c, m_Sink, CryptoPP::BlockPaddingSchemeDef::ZEROS_PADDING);
}

设置ZEROS_PADDING使您的代码正常工作(在文本文件上测试)。但是为什么它不适用于DEFAULT_PADDING - 我还没找到原因。