首页 > 解决方案 > fstream 在替代控制字符处停止读取

问题描述

我正在用 C++ 编写一个简单的加密程序来加密基于文本的文件。它使用简单的 XOR 密码算法,但这会在输出文件中产生 ASCII 控制字符。当我尝试使用 读取新加密的文件时std::ifstream,它偶然发现 character #26,它停止并无法读取文件的其余部分。

例如,如果我尝试加密此文本:

这只是一个简单的示例
两行一个句子的文本。

它变成了这个

/[[[[[
[[[[[[U

当我尝试在我的程序中读取该文件时,它无法读取位置 15 的字符,所以我得到了一个半加密文件。

我怎样才能解决这个问题?

这是代码:

#include <iostream>
#include <Windows.h>
#include <string>
#include <fstream>

void Encrypt(char encryptionKey, std::string filename)
{
    std::ifstream sourceFile(filename);
    std::ofstream outputFile(filename.substr(0, filename.find_last_of("\\")) + "\\Encrypted" + filename.substr(filename.find_last_of("\\") + 1), std::ofstream::out | std::ofstream::trunc);
    std::string sourceLine;
    std::string outputLine;
    long numLines = 0;
    if (sourceFile.is_open())
    {
        std::cout << "Opening file: " + filename + " for encryption" << std::endl;
        while (sourceFile.good()) // This iterates over the whole file, once for each line
        {
            sourceLine = ""; //Clearing the line for each new line
            outputLine = ""; //Clearing the line for each new line

            std::getline(sourceFile, sourceLine);
            for (int i = 0; i < sourceLine.length(); i++) // Looping through all characters in each line
            {

                char focusByte = sourceLine[i] ^ encryptionKey;
                std::cout << " focusByte: " << focusByte << std::endl;
                outputLine.push_back(focusByte);
                //std::cout << sourceLine << std::flush;

            }
            numLines++;
            outputFile << outputLine << std::endl;
        }
    }
    sourceFile.close();
    outputFile.close();
}

void Decrypt(unsigned int encryptionKey, std::string filename)
{
    std::ifstream sourceFile(filename);
    std::ofstream outputFile(filename.substr(0, filename.find_last_of("\\")) + "\\Decrypted" + filename.substr(filename.find_last_of("\\") + 1), std::ofstream::out | std::ofstream::trunc);
    std::string sourceLine;
    std::string outputLine;
    long numLines = 0;
    if (sourceFile.is_open())
    {
        std::cout << "Opening file: " + filename + " for decryption" << std::endl;
        while (sourceFile.good()) // This iterates over the whole file, once for each line
        {
            if (sourceFile.fail() == true)
                std::cout << "eof" << std::endl;
            sourceLine = ""; //Clearing the line for each new line
            outputLine = ""; //Clearing the line for each new line

            std::getline(sourceFile, sourceLine);
            for (int i = 0; i < sourceLine.length(); i++) // Looping through all characters in each line
            {
                char focusByte = sourceLine[i] ^ encryptionKey;
                std::cout << " focusByte: " << focusByte << std::endl;
                outputLine.push_back(focusByte);

            }
            numLines++;
            outputFile << outputLine << std::endl;
        }
    }
    sourceFile.close();
    outputFile.close();
}


int main(int argument_count,
    char * argument_list[])
{
    system("color a");
    std::string filename;
    if (argument_count < 2)
    {
        std::cout << "You didn't supply a filename" << std::endl;
    }
    else
    {
        filename = argument_list[1];
        std::cout << "Target file: " << filename << std::endl;
        std::cout << "Press e to encrypt the selected file, Press d to decrypt the file > " << std::flush;
        char choice;
        while (true)
        {
            std::cin >> choice;
            if (choice == 'e')
            {
                Encrypt(123, filename);
                break;
            }
            else if (choice == 'd')
            {
                Decrypt(123, filename);
                break;
            }
            else
            {
                std::cout << "please choose option e or d for encryption respectivly decryption" << std::endl;
            }
        }
    }


    std::cout << "\nPaused, press Enter to continue > " << std::flush;
    system("Pause");
    return EXIT_SUCCESS;
}

标签: c++encryptionfstream

解决方案


Decrypt(), 在第一次调用之后std::getline(),sourceFile.good()是假的并且sourceFile.fail()是真的,这就是你停止从加密文件中读取后续行的原因。

原因是加密文件中有一个编码0x1A字节,并且根据您的平台和 STL 实现,该字符可能会被解释为 EOF 条件,从而启用std::ifstream'eofbit状态,终止进一步阅读。

在我的编译器在 Windows 上的 STL 实现中,当std::ifstream从文件中读取时,它最终会调用一个名为的函数_Fgetc()

template<> inline bool _Fgetc(char& _Byte, _Filet *_File)
    {   // get a char element from a C stream
    int _Meta;
    if ((_Meta = fgetc(_File)) == EOF) // <-- here
        return (false);
    else
        {   // got one, convert to char
        _Byte = (char)_Meta;
        return (true);
        }
    }

当它试图读取一个0x1A字符时,fgetc()返回EOF,当_Fgetc()返回 false 时,std::getline()设置eofbitonstd::ifstream并退出。

检查编译器的 STL 是否有类似的行为。

这种行为是因为您正在以文本模式打开加密文件。您需要改为以binary模式打开加密文件:

std::ifstream sourceFile(..., std::ifstream::binary);

此外,您还应该binary在加密文件上启用模式Encrypt()

std::ofstream outputFile(..., std::ofstream::binary | std::ofstream::trunc);

尝试更多类似的东西:

#include <Windows.h>

#include <iostream>
#include <string>
#include <fstream>
#include <cstdlib>

void Encrypt(char encryptionKey, const std::string &filename)
{
    std::string::size_type pos = filename.find_last_of("\\");
    std::string out_filename = filename.substr(0, pos+1) + "Encrypted" + filename.substr(pos + 1);

    std::ifstream sourceFile(filename.c_str());
    std::ofstream outputFile(out_filename.c_str(), std::ofstream::binary | std::ofstream::trunc);

    if (sourceFile.is_open())
    {
        std::cout << "Opened file: " + filename + " for encryption" << std::endl;

        std::string line;
        long numLines = 0;

        while (std::getline(sourceFile, line)) // This iterates over the whole file, once for each line
        {
            for (std::string::size_type i = 0; i < line.length(); ++i) // Looping through all characters in each line
            {
                char focusByte = line[i] ^ encryptionKey;
                std::cout << " focusByte: " << focusByte << std::endl;
                line[i] = focusByte;
                //std::cout << line << std::flush;
            }

            outputFile << line << std::endl;
            ++numLines;
        }
    }
}

void Decrypt(char encryptionKey, const std::string &filename)
{
    std::string::size_type pos = filename.find_last_of("\\");
    std::string out_filename = filename.substr(0, pos+1) + "Decrypted" + filename.substr(pos + 1);

    std::ifstream sourceFile(filename.c_str(), std::ifstream::binary);
    std::ofstream outputFile(out_filename.c_str(), std::ofstream::trunc);

    if (sourceFile.is_open())
    {
        std::cout << "Opened file: " + filename + " for decryption" << std::endl;

        std::string line;
        long numLines = 0;

        while (std::getline(sourceFile, line)) // This iterates over the whole file, once for each line
        {
            for (std::string::size_type i = 0; i < line.length(); ++i) // Looping through all characters in each line
            {
                char focusByte = line[i] ^ encryptionKey;
                std::cout << " focusByte: " << focusByte << std::endl;
                line[i] = focusByte;
            }

            outputFile << line << std::endl;
            ++numLines;
        }

        std::cout << "eof" << std::endl;
    }
}

int main(int argument_count, char* argument_list[])
{
    std::system("color a");
    std::string filename;

    if (argument_count < 2)
    {
        std::cout << "Enter a file to process: " << std::flush;
        std::getline(std::cin, filename);
    }
    else
    {
        filename = argument_list[1];
    }

    if (filename.empty())
    {
        std::cout << "You didn't supply a filename" << std::endl;
        return EXIT_FAILURE;
    }

    std::cout << "Target file: " << filename << std::endl;
    std::cout << "Press e to encrypt the file" << std::endl;
    std::cout << "Press d to decrypt the file" << std::endl;
    char choice;

    while (true)
    {
        std::cout << "> " << std::flush;
        std::cin >> choice;

        if (choice == 'e')
        {
            Encrypt(123, filename);
            break;
        }
        else if (choice == 'd')
        {
            Decrypt(123, filename);
            break;
        }
        else
        {
            std::cout << "please choose option e or d for encryption or decryption, respectively" << std::endl;
        }
    }

    std::cout << std::endl << "Paused, press Enter to continue" << std::flush;
    std::system("pause");

    return EXIT_SUCCESS;
}

话虽如此,请记住,在使用 XOR 时,某些加密字符最终可能是\r(0x0D) 或\n(0x0A),这会在稍后解密文件时产生干扰,从而std::getline()产生与原始文件不匹配的解密输出文字输入。

由于您应该将加密文件视为二进制文件,因此您根本不应该将文件作为文本读取/写入。为您的加密输出选择不依赖于文本与二进制模式中的换行语义的不同格式。

例如:

#include <Windows.h>

#include <iostream>
#include <string>
#include <fstream>
#include <cstdlib>

void Encrypt(char encryptionKey, const std::string &filename)
{
    std::string::size_type pos = filename.find_last_of("\\");
    std::string out_filename = filename.substr(0, pos+1) + "Encrypted" + filename.substr(pos + 1);

    std::ifstream sourceFile(filename.c_str());
    std::ofstream outputFile(out_filename.c_str(), std::ofstream::binary | std::ofstream::trunc);

    if (sourceFile.is_open())
    {
        std::cout << "Opened file: " + filename + " for encryption" << std::endl;

        std::string line;
        std::string::size_type lineLen;
        long numLines = 0;

        while (std::getline(sourceFile, line)) // This iterates over the whole file, once for each line
        {
            lineLen = line.length();

            for (std::string::size_type i = 0; i < lineLen; ++i) // Looping through all characters in each line
            {
                char focusByte = line[i] ^ encryptionKey;
                std::cout << " focusByte: " << focusByte << std::endl;
                line[i] = focusByte;
                //std::cout << line << std::flush;
            }

            outputFile.write((char*)&lineLen, sizeof(lineLen));
            outputFile.write(line.c_str(), lineLen);

            ++numLines;
        }
    }
}

void Decrypt(char encryptionKey, const std::string &filename)
{
    std::string::size_type pos = filename.find_last_of("\\");
    std::string out_filename = filename.substr(0, pos+1) + "Decrypted" + filename.substr(pos + 1);

    std::ifstream sourceFile(filename.c_str(), std::ifstream::binary);
    std::ofstream outputFile(out_filename.c_str(), std::ofstream::trunc);

    if (sourceFile.is_open())
    {
        std::cout << "Opened file: " + filename + " for decryption" << std::endl;

        std::string line;
        std::string::size_type lineLen;
        long numLines = 0;

        while (sourceFile.read((char*)&lineLen, sizeof(lineLen))) // This iterates over the whole file, once for each line
        {
            line.resize(lineLen);
            if (!sourceFile.read(&line[0], lineLen))
                break;

            for (std::string::size_type i = 0; i < lineLen; ++i) // Looping through all characters in each line
            {
                char focusByte = line[i] ^ encryptionKey;
                std::cout << " focusByte: " << focusByte << std::endl;
                line[i] = focusByte;
            }

            outputFile << line << std::endl;
            ++numLines;
        }

        std::cout << "eof" << std::endl;
    }
}

int main(int argument_count, char* argument_list[])
{
    std::system("color a");
    std::string filename;

    if (argument_count < 2)
    {
        std::cout << "Enter a file to process: " << std::flush;
        std::getline(std::cin, filename);
    }
    else
    {
        filename = argument_list[1];
    }

    if (filename.empty())
    {
        std::cout << "You didn't supply a filename" << std::endl;
        return EXIT_FAILURE;
    }

    std::cout << "Target file: " << filename << std::endl;
    std::cout << "Press e to encrypt the file" << std::endl;
    std::cout << "Press d to decrypt the file" << std::endl;
    char choice;

    while (true)
    {
        std::cout << "> " << std::flush;
        std::cin >> choice;

        if (choice == 'e')
        {
            Encrypt(123, filename);
            break;
        }
        else if (choice == 'd')
        {
            Decrypt(123, filename);
            break;
        }
        else
        {
            std::cout << "please choose option e or d for encryption or decryption, respectively" << std::endl;
        }
    }

    std::cout << std::endl << "Paused, press Enter to continue" << std::flush;
    std::system("pause");

    return EXIT_SUCCESS;
}

推荐阅读