文件打包学习3

放肆的年华 提交于 2019-12-05 14:53:40

之前存在的弱点

  1. 文件虽然打包成一个文件,但是对于打包后的文件内容依然是明文的,不安全

常用加密算法

  • AES
    密码学中的高级加密标准(Advanced Encryption Standard,AES),又称高级加密标准Rijndael加密法,是美国联邦政府采用
    的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。

  • XOR加密
    同一值去异或两次就可以恢复出原来的值,因此它是最简单的可逆的操作,也是最简单的加密算法

  • 自定义算法

NewPackage.h

#ifndef NEWPACKAGE_H_
#define NEWPACKAGE_H_

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

#include <vector>
#include <string>

class CFileBuffer
{
public:
    CFileBuffer():m_pBuffer(nullptr),m_nSize(0)
    {
    }
    ~CFileBuffer()
    {
        Destroy();
    }
    //创建一块内存
    bool Create(size_t Size)
    {
        Destroy();
        m_pBuffer = malloc(Size);
        if(!m_pBuffer)
        {
            return false;
        }
        m_nSize = Size;
        return true;
    }
    //销毁一块内存
    void Destroy()
    {
        if(m_pBuffer)
        {
            free(m_pBuffer);
            m_pBuffer = nullptr;
        }
        m_nSize = 0;
    }
    //获取申请内存地址
    void* GetBuffer()const
    {
        return m_pBuffer;
    }    
    //获取申请内存大小
    size_t GetSize()const
    {
        return m_nSize;
    }
    //将内存写入到磁盘文件
    bool Save(const char* szFileName)
    {

        //检查m_pBuffer是否为空,如果为空,表示当前指针没有指向任何数据,不需要写入
        if(!m_pBuffer)
            return false;

        //以二进制写的方式打开文件,准备写入文件
        FILE* fp = fopen(szFileName, "wb");
        if(!fp)
        {
            fprintf(stderr, "fopen %s error : %s \n",szFileName, strerror(errno));
        }

        //开始写入文件
        if(m_nSize != fwrite(m_pBuffer, 1, m_nSize, fp))
        {
            fclose(fp);
            return false;
        }

        fclose(fp);
        return true;
    }

private:
    void* m_pBuffer;    
    size_t m_nSize;
};

struct NewPackageItem
{
    size_t FileNameLen;         //文件名的长度
    size_t FileSize;            //文件的大小
    size_t OffsetPackage;       //文件在包裹的偏移位置
    char FileName[0];           //可变文件名
};

//我们定义一个标识,表示这个是我们的包裹格式
static const char   MY_PACKAGE_FLAG[] = "NewPackage";
static const size_t MY_ENCRYPT_METHOD_XOR = 0;  //xor加密

struct PackageInfo
{
    size_t              EncryptMethod;  //加密算法
};

class CNewPackage
{
public:
    CNewPackage();
    ~CNewPackage();
    //重置一个包裹
    void ResetPackage( );
    //读取一个包裹
    bool OpenPackage( const char*szPackageName );
    //添加一个文件,忽略大小写
    bool AddFile( const char*szFileName );
    //删除一个文件,忽略大小写
    bool RemoveFile( const char*szFileName );
    //重复性检查,忽略大小写
    bool HasFile( const char*szFileName );
    //保存包裹到指定文件名
    bool SavePackage( const char*szPackageName );
    //导出文件数据到FileBuffer里面
    bool ExportPackageItem( const char*szFileName, CFileBuffer & FileBuffer );
    
private:
    FILE*                           m_fpPackage;        //包裹文件指针
    std::vector<std::string>        m_AddFiles;         //待添加到包裹的文件
    std::vector<NewPackageItem*>    m_PackageItems;     //已经打包到包裹的文件信息
};

#endif // !NEWPACKAGE_H_

NewPackage.cpp

#include "NewPackage.h"

/*
┌------------┬-------------┬-------------┬------┬--------┬-------┬-------┐
├  size_t n  │     Item    │     Item    │. . . │ FILE1  │ FILE1 │ . . . │
└------------┴-------------┴-------------┴------┴--------┴-------┴-------┘
┌-----------┬-----------┬--------┬-----------┬-----------┬------┬--------┬-------┬-------┐
├  size_t n │PackageItem│FileName│PackageItem│ FileName  │. . . │ FILE1  │ FILE1 │ . . . │
└-----------┴-----------┴--------┴-----------┴-----------┴------┴--------┴-------┴-------┘
*/

struct FileItemForWrite
{
    FILE *fp;                     //要打包的文件的文件指针
    size_t FileOffset;            //在文件中的偏移位置
    size_t FileSize;              //要读取的文件的大小
    std::string FileName;         //要读取的文件的文件名
    NewPackageItem *pPackageItem; //要读取的文件基本信息
};

CNewPackage::CNewPackage()
{
    m_fpPackage = nullptr;
}

CNewPackage::~CNewPackage()
{
    ResetPackage();
}

void CNewPackage::ResetPackage()
{

    for (size_t i = 0; i < m_PackageItems.size(); i++)
    {
        NewPackageItem *pItem = m_PackageItems[i];
        free(pItem);
    }
    //clear是为了清除vector内部的index,size;如果不清除,这两个值会一直增加
    m_PackageItems.clear();

    //关闭包裹文件指针
    if (m_fpPackage)
    {
        fclose(m_fpPackage);
        m_fpPackage = nullptr;
    }

    //清理待添加的文件容器
    m_AddFiles.clear();
}

bool CNewPackage::AddFile(const char *szFileName)
{
    //如果有同名的,做替换处理
    RemoveFile(szFileName);

    m_AddFiles.push_back(szFileName);

    return true;
}

bool CNewPackage::RemoveFile(const char *szFileName)
{
    //从待添加的文件查找
    for (size_t i = 0; i < m_AddFiles.size(); i++)
    {
        if (strcasecmp(m_AddFiles[i].c_str(), szFileName) == 0)
        {
            m_AddFiles.erase(m_AddFiles.begin() + i);
            return true;
        }
    }

    //从包裹文件中查找
    for (size_t i = 0; i < m_PackageItems.size(); i++)
    {
        NewPackageItem *pItem = m_PackageItems[i];
        if (strcasecmp(pItem->FileName, szFileName))
        {
            m_PackageItems.erase(m_PackageItems.begin() + i);
            return true;
        }
    }
}

bool CNewPackage::HasFile(const char *szFileName)
{
    //从待添加的文件查找
    for (size_t i = 0; i < m_AddFiles.size(); i++)
    {
        if (strcasecmp(m_AddFiles[i].c_str(), szFileName) == 0)
        {
            return true;
        }
    }

    //从包裹文件中查找
    for (size_t i = 0; i < m_PackageItems.size(); i++)
    {
        NewPackageItem *pItem = m_PackageItems[i];
        if (strcasecmp(pItem->FileName, szFileName))
        {
            return true;
        }
    }
}

//用xor的方式写入
inline size_t NewPackageXORWrite( const void*pBuffer, size_t Size, FILE*fp )
{
    unsigned char*pByteBuffer = (unsigned char*)malloc( Size );
    if( !pByteBuffer )
    {
        return 0;
    }
    unsigned char*pSrcBuffer = (unsigned char*)pBuffer;
    for( size_t i=0; i<Size; i++ )
    {
        pByteBuffer[i] = ~pSrcBuffer[i];
    }
    size_t WriteSize = fwrite( pByteBuffer, 1, Size, fp );
    free( pByteBuffer );
    return WriteSize;
}

//用xor的方式读取
inline size_t NewPackageXORRead( void*pBuffer, size_t Size, FILE*fp )
{
    size_t ReadSize = fread( pBuffer, 1, Size, fp );
    unsigned char*pByteBuffer = (unsigned char*)pBuffer;
    for( size_t i=0; i<ReadSize; i++ )
    {
        pByteBuffer[i] = ~pByteBuffer[i];
    }
    return ReadSize;
}

bool CNewPackage::SavePackage(const char *szPackageName)
{
    FILE *fpPackage = fopen(szPackageName, "wb");
    if (!fpPackage)
    {
        fprintf(stderr, " fopen %s error %s \n", szPackageName, strerror(errno));
        return false;
    }

    //写入文件头标识
    if( sizeof( MY_PACKAGE_FLAG ) != NewPackageXORWrite( MY_PACKAGE_FLAG, sizeof( MY_PACKAGE_FLAG ), fpPackage ) )
    {
        fprintf(stderr, " 写入文件头信息失败 error %s \n", strerror(errno));
        return false;
    }

    PackageInfo Info;
    Info.EncryptMethod = MY_ENCRYPT_METHOD_XOR;
    if( sizeof( Info ) != NewPackageXORWrite( &Info, sizeof( Info ), fpPackage ) )
    {
        fprintf(stderr, " 写入文件信息 error %s \n", strerror(errno));
        return false;
    }

    //将包裹中原有的文件 和 待添加的文件 全部放入这个WriteItems中
    std::vector<FileItemForWrite> WriteItems;

    //预留一个sizeof(size_t)的大小,TotalHeadIndexSize是n个PackageItem所占的字节数
    size_t TotalHeadIndexSize = sizeof(size_t);

    //处理待添加的文件
    for (size_t i = 0; i < m_AddFiles.size(); i++)
    {
        FILE *fpRead = fopen(m_AddFiles[i].c_str(), "rb");
        if (!fpRead)
        {
            fprintf(stderr, "fopen %s error %s \n", m_AddFiles[i].c_str(), strerror(errno));
            return false;
        }
        fseek(fpRead, 0, SEEK_END);

        //填充FileItemForWrite信息
        FileItemForWrite Item;
        Item.fp = fpRead;
        Item.FileOffset = 0;
        Item.FileSize = ftell(fpRead);
        Item.FileName = m_AddFiles[i];
        size_t FileNameLen = Item.FileName.length() + 1;

        //申请一块内存存放文件的头部信息
        NewPackageItem *pNewItem = (NewPackageItem *)malloc(sizeof(NewPackageItem) + FileNameLen);
        if (!pNewItem)
        {
            continue;
        }
        Item.pPackageItem = pNewItem;

        //填充文件的头部信息
        pNewItem->FileNameLen = FileNameLen;
        pNewItem->FileSize = Item.FileSize;
        strcpy(pNewItem->FileName, m_AddFiles[i].c_str());

        //更新TotalHeadIndexSize的大小
        TotalHeadIndexSize += sizeof(NewPackageItem) + FileNameLen;

        //将待写入的文件添加到容器中
        WriteItems.push_back(Item);
    }

    //处理原来包裹中已经存在的文件
    for (size_t i = 0; i < m_PackageItems.size(); i++)
    {
        NewPackageItem *pItem = m_PackageItems[i];

        FileItemForWrite Item;
        Item.fp = m_fpPackage;
        Item.FileOffset = pItem->OffsetPackage;
        Item.FileSize = pItem->FileSize;
        Item.FileName = pItem->FileName;

        size_t FileNameLen = Item.FileName.length();
        NewPackageItem *pNewItem = (NewPackageItem *)malloc(sizeof(NewPackageItem) + FileNameLen);
        if (!pNewItem)
        {
            continue;
        }
        Item.pPackageItem = pNewItem;

        pNewItem->FileNameLen = FileNameLen;
        pNewItem->FileSize = Item.FileSize;
        strcpy(pNewItem->FileName, Item.FileName.c_str());

        TotalHeadIndexSize += sizeof(NewPackageItem) + pNewItem->FileNameLen;

        WriteItems.push_back(Item);
    }

    //有多少个PackageItem
    size_t TotalPackageItems = WriteItems.size();

    //开始写入文件
    if( sizeof( TotalPackageItems ) != NewPackageXORWrite( &TotalPackageItems, sizeof( TotalPackageItems ), fpPackage ) )
    {
        fprintf(stderr, "fwrite %lu error %s \n ", TotalPackageItems, strerror(errno));
        return false;
    }

    //记录当前的文件指针位置
    size_t IndexOffset = ftell(fpPackage);
    //预留TotalHeadIndexSize的空间供索引写入
    fseek(fpPackage, TotalHeadIndexSize, SEEK_SET);

    //先写入文件
    bool bError = false;
    size_t CurOffset = TotalHeadIndexSize;
    for (size_t i = 0; i < WriteItems.size(); i++)
    {
        FileItemForWrite &Item = WriteItems[i];
        if (bError == false)
        {
            //填充偏移信息
            Item.pPackageItem->OffsetPackage = CurOffset;
            //更新偏移信息
            CurOffset += Item.FileSize;

            //将要写入的文件的文件指针移动到文件头
            fseek(Item.fp, Item.FileOffset, SEEK_SET);

            char szBuffer[65536];
            size_t LeftSize = Item.FileSize;
            while (1)
            {
                //实际要读取的大小
                size_t ReadSize = LeftSize;
                //如果大于缓冲区,我们就只读取缓冲区大小的内容,剩余的下次读取
                if (ReadSize > sizeof(szBuffer))
                {
                    ReadSize = sizeof(szBuffer);
                }
                //读取文件
                size_t nReadBytes = fread(szBuffer, 1, ReadSize, Item.fp);
                //ReadSize必须要等于nReadBytes
                if (ReadSize != nReadBytes)
                {
                    fprintf(stderr, "读取 %s 失败 %s\n",Item.FileName.c_str(), strerror(errno));
                    bError = true;
                    break;
                }
                //写入包裹
                if( nReadBytes != NewPackageXORWrite( szBuffer, nReadBytes, fpPackage ) )
                {
                    fprintf(stderr, "读取 %s 失败 %s\n",szPackageName, strerror(errno));
                    bError = true;
                    break;
                }
                LeftSize -= nReadBytes;
                //如果剩余大小为0,就读取完成了
                if (LeftSize == 0)
                {
                    break;
                }
            }
        }

        //关闭需要打包的文件
        if (Item.fp != m_fpPackage)
        {
            fclose(Item.fp);
            Item.fp = NULL;
        }
    }

    //如果没错误,写入索引
    if (!bError)
    {
        fseek(fpPackage, IndexOffset, SEEK_SET);
        for (size_t i = 0; i < WriteItems.size(); i++)
        {
            FileItemForWrite &Item = WriteItems[i];
            NewPackageItem *pItem = Item.pPackageItem;

            if (!bError)
            {
                //写入的长度应该是 sizeof(PackageItem) + 文件名的长度
                size_t WriteSize = sizeof(NewPackageItem) + pItem->FileNameLen;
                if( WriteSize != NewPackageXORWrite( pItem, WriteSize, fpPackage ) )
                {
                    fprintf(stderr, "写入 %s 失败 %s\n",szPackageName,strerror(errno));
                    bError = true;
                }
            }
            free(pItem);
        }
    }

    fclose(fpPackage);

    return !bError;
}

bool CNewPackage::OpenPackage(const char *szPackageName)
{
    ResetPackage();

    m_fpPackage = fopen(szPackageName, "rb");
    if (!m_fpPackage)
    {
        fprintf(stderr, "打开 %s 失败 %s\n",szPackageName, strerror(errno));
        return false;
    }

    //读入文件头标识
    char FileFlag[ sizeof( MY_PACKAGE_FLAG ) ];
    if( sizeof( MY_PACKAGE_FLAG ) != NewPackageXORRead( FileFlag, sizeof( FileFlag ), m_fpPackage ) )
    {
        fprintf(stderr, "读取文件头标识失败 %s\n", strerror(errno));
        return false;
    }

    //判断一下我们读取到的文件头是否是我们定义的,做个最简单的格式判断
    if( memcmp( FileFlag, MY_PACKAGE_FLAG, sizeof( FileFlag ) ) != 0 )
    {
        fprintf( stderr, "包裹%s格式错误\n", szPackageName );
        return false;
    }
    PackageInfo Info;
    if( sizeof( Info ) != NewPackageXORRead( &Info, sizeof( Info ), m_fpPackage ) )
    {
        fprintf( stderr, "读取%s失败 %s\n", szPackageName, strerror(errno));
        return false;
    }
    if( Info.EncryptMethod != MY_ENCRYPT_METHOD_XOR )
    {
        fprintf( stderr, "无法识别%s的加密格式\n", szPackageName );
        return false;
    }


    //先读取包裹中Item的个数
    size_t PackageItemCount = 0;
    if( sizeof( PackageItemCount ) != NewPackageXORRead( &PackageItemCount, sizeof( PackageItemCount ), m_fpPackage ) )
    {
        fprintf(stderr, "读取%s失败 %s\n", szPackageName, strerror(errno));
        return false;
    }

    //根据读到的Item的个数遍历,把所有包裹中的文件放入m_PackageItems中
    for (size_t i = 0; i < PackageItemCount; i++)
    {
        //先读取文件名的长度,因为文件名是变长的,方便后面申请内存
        size_t FileNameLen = 0;
        if( sizeof( FileNameLen ) != NewPackageXORRead( &FileNameLen, sizeof( FileNameLen ), m_fpPackage ) )
        {
            fprintf(stderr, "读取%s失败 %s\n", szPackageName, strerror(errno));
            return false;
        }

        char *pBuffer = (char *)malloc(sizeof(NewPackageItem) + FileNameLen);
        if (!pBuffer)
        {
            fprintf(stderr, "读取%s失败 %s\n", szPackageName, strerror(errno));
            return false;
        }
        NewPackageItem *pItem = (NewPackageItem *)pBuffer;
        pItem->FileNameLen = FileNameLen;

        //我们已经读取FileNameLen了,所以剩余读取的部分减掉FileNameLen
        size_t ReadSize = sizeof(NewPackageItem) + FileNameLen - sizeof(FileNameLen);
        if( ReadSize != NewPackageXORRead( pBuffer + sizeof( FileNameLen ), ReadSize, m_fpPackage ) )
        {
            fprintf(stderr, "读取%s失败 %s\n", szPackageName, strerror(errno));
            return false;
        }
        m_PackageItems.push_back(pItem);
    }
    return true;
}

bool CNewPackage::ExportPackageItem(const char *szFileName, CFileBuffer &FileBuffer)
{
    //先从待添加的文件查找
    for (size_t i = 0; i < m_AddFiles.size(); i++)
    {
        if (strcasecmp(m_AddFiles[i].c_str(), szFileName) == 0)
        {
            FILE *fp = fopen(szFileName, "rb");
            if (!fp)
            {
                return false;
            }
            fseek(fp, 0, SEEK_END);
            size_t Size = ftell(fp);
            fseek(fp, 0, SEEK_SET);
            //申请内存
            if (!FileBuffer.Create(Size))
            {
                fclose(fp);
                return false;
            }
            //把文件读入内存中
            if (Size != fread(FileBuffer.GetBuffer(), 1, Size, fp))
            {
                fclose(fp);
                return false;
            }
            return true;
        }
    }
    //再从包裹文件中查找
    for (size_t i = 0; i < m_PackageItems.size(); i++)
    {
        NewPackageItem *pItem = m_PackageItems[i];
        if (strcasecmp(pItem->FileName, szFileName) == 0)
        {
            if (!FileBuffer.Create(pItem->FileSize))
            {
                return false;
            }
            fseek(m_fpPackage, pItem->OffsetPackage, SEEK_SET);
            if( pItem->FileSize != NewPackageXORRead( FileBuffer.GetBuffer(), pItem->FileSize, m_fpPackage ) )
            {
                return false;
            }
            return true;
        }
    }
    return false;
}

main.cpp

#include "NewPackage.h"


int main(void)
{
    CNewPackage pkg;
    pkg.AddFile( "test1.txt" );
    pkg.AddFile( "test2.txt" );
    pkg.AddFile( "test3.txt" );
    pkg.RemoveFile( "test2.txt" );
    pkg.SavePackage( "test.pkg" );
    pkg.OpenPackage( "test.pkg" );
    CFileBuffer bf;
    pkg.ExportPackageItem( "test3.txt", bf );
    bf.Save( "testout.txt" );

    return 0;
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!