基于QT开发PC端文件加密工具

基于QT开发PC端文件加密工具

1、客户需求

某案子。该方案,通过外挂TF卡实现,本地教育资源的调取使用。按客户需求,把TF卡内的内容进行加密,并在软件中写好解密,确保资源文件只能在教育机中才能正常播放,以防被拷贝复制。

2、涉及的加密文件类型

目前仅支持的加密文件类型:文本(.txt)、图片(.png/.jpg)、视频(.mp4)、音乐(.mp3)

3、开发环境

QT5.12(C++)、Qt程序打包发布方法(使用官方提供的windeployqt工具)

4、设计思路

 

如图所示,为加密工具的操作界面。操作方法:1、选择待加密文件所在的文件夹;2、选择指定加密文件的新文件夹;3、列出所有待加密的文件;4、一键加密。

软件设计思路:

1、选择待加密文件所在的文件夹

首先是利用QT的槽函数机制,浏览控件都有对应的槽函数。

void MainWindow::on_SelectSourceFilepushButton_3_clicked()
{
    //获取应用的当前路径,并通过QString类定义的变量保存该路径
    QString curPath = QCoreApplication::applicationDirPath();
	//设置选择弹框的标题
    QString dlaTitle = "选择待加密文件";
    QString selectedDir = QFileDialog::getExistingDirectory(this, dlaTitle, curPath, QFileDialog::ShowDirsOnly);
    //通过全局变量保存待加密文件的文件路径
    SourceFilePath = selectedDir;

    if(!selectedDir.isEmpty()){
        //通过comboBox控件显示选择的指定文件夹路径
        ui->comboBox->addItem(selectedDir);
        //初始化进度条为0
        ui->progressBar->setValue(0);
    }
}

分析:类MainWindow为主界面,成员函数on_SelectSourceFilepushButton_3_clicked,亦为浏览控件的槽函数。

2、选择指定加密文件的新文件夹

void MainWindow::on_SelectTargetFile_pushButton_clicked()
{
    //获取应用的当前路径,并通过QString类定义的变量保存该路径
    QString curPath = QCoreApplication::applicationDirPath();
    QString dlaTitle = "选择目标文件";
   	//选择指定文件夹,并保存该文件夹的路径
    QString selectedDir = QFileDialog::getExistingDirectory(this, dlaTitle, curPath, QFileDialog::ShowDirsOnly);
    //通过全局变量来保存指定文件夹的文件路径
    TargetFilePath = selectedDir;

    if(!selectedDir.isEmpty()){
        //通过comboBox_2控件显示选择的指定文件夹路径
        ui->comboBox_2->addItem(selectedDir);
    }
}

分析:类MainWindow为主界面,成员函数on_SelectTargetFile_pushButton_clicked,亦为浏览控件的槽函数。

3、列出所有待加密的文件

void MainWindow::on_ListAllFileButton_clicked()
{
    QString curPath = SourceFilePath;		//获取待加密文件的文件路径
    QStringList fileList;	//用字符串列表的类定义对象fileList
    QString filter;
    ListWidget TmpListWight;	//列表窗口的类定义对象TmpListWight

    ui->listWidget_2->clear();	//清除主界面上的列表窗口
    totalfiles = 0;				//初始化用于记录总待加密文件数的变量
    file_info_list = TmpListWight.GetFileList(curPath);	//根据文件路径,直接获取所有文件的路径信息

    //循环遍历 file_info_list对象中所有的路径,并获取文件信息fileinfo
    foreach(QFileInfo fileinfo, file_info_list)
    {
        filter = fileinfo.suffix();    //获取文件后缀名
        //过滤文件
        if( (filter != "jpg") && (filter != "jpeg")
        	&&(filter != "png") && filter != "mp3" 
        	&& filter != "txt" && filter != "mp4") {
            continue;
        }
		//将每个文件路径添加到列表中
        fileList.append(fileinfo.absoluteFilePath());
        //在listWidget_2显示
        ui->listWidget_2->addItem(fileinfo.absoluteFilePath());
    }
}

4、一键加密

void MainWindow::on_EncryptionButton_clicked()
{
    //创建加密文件的线程
    newThread();
}
void MainWindow::newThread()
{
    ThreadEncryptionFile *NewThreadFile = new ThreadEncryptionFile;

    QObject::connect(NewThreadFile,SIGNAL(UpdateProgressBar(double)),this,SLOT(processbar(double)));
	//启动线程,开始执行工作线程的任务
    NewThreadFile->start();
}

分析:成员变量默认是界面的子类了,不会再生成一个线程。new出来的话就是在堆中,默认是一个新进程。这里注意用new来创建新线程。connect()函数有且只能在QObject类里面和QObject派生类里面使用,自己新建的类里面(基类不是QObject类和其QObject派生类)使用connect()函数是无效的,编译时会报错。新建工程比如widget,mainwindow,dialog都是QObject的派生类,所以可以直接使用connect()函数,实现信号与槽机制。NewThreadFile->start()启动线程。

重点说下自定义的加密类:

//继承 线程的加密类
class ThreadEncryptionFile :public QThread
{
    Q_OBJECT
public:
    bool ReadFile(const QString& SourceFileName, const QString& TargetFileName);
    bool CopyDirectoryFiles(const QString &fromDir,const QString &toDir);
    bool getFileCount( const QString& sPath );
    void Encryption_copy(const QString& SourceFileName, const QString& TargetFileName);
    void EncryptionTextFile(const QString& SourceFileName, const QString& TargetFileName);
    void EncryptionMusicFile(const QString& SourceFileName, const QString& TargetFileName);
    void EncryptionPhotoFile(const QString& SourceFileName, const QString& TargetFileName);
    void EncryptionMovieFile(const QString& SourceFileName, const QString& TargetFileName);
    QByteArray FileToByteArray(const QString& SourceFileName);
    U8 word_encrypt(U8 val, U8 Secretkey);
//信号 用来通知更新 进度条
signals:
    void UpdateProgressBar(double );
protected:
    void run();
};
分析:QThread是线程类,是实现多线程操作的核心类,一般从QThread继承定义自己的线程类,本设计就是用的这种方式。在从QThread继承一个自定义类,并重定义虚函数run(),在run()函数里实现线程需要完成的任务,该线程可以通过调用start()开始执行工作线程的任务。

再来看看主界面的类:

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private slots:
    void on_EncryptionButton_clicked();
    void on_ListAllFileButton_clicked();
    void on_SelectTargetFile_pushButton_clicked();
    void on_SelectSourceFilepushButton_3_clicked();
    void processbar(double index);
    void newThread();
private:
    Ui::MainWindow *ui;
    QProgressBar *progressBar;
public:
    QFileInfoList file_info_list;
};
分析:主窗口的类,主要是各个控件的槽函数。以及关联进度条,和文件信息列表类。
重点->加密线程中的执行内容:
void ThreadEncryptionFile::run()
{
    getFileCount(SourceFilePath);	//获取待加密文件总数
    CopyDirectoryFiles(SourceFilePath, TargetFilePath);	    //拷贝目录文件,同时完成加密工作
}
获取待加密文件总数的方法如下:
bool ThreadEncryptionFile::getFileCount(const QString &sPath)
{
    QDir dir( sPath );	//目录类实例化对象dir
    QFileInfoList fileInfoList = dir.entryInfoList();	//文件信息列表类实例化对象fileInfoList,并初始化

    foreach ( QFileInfo fileInfo, fileInfoList ) {
        if ( fileInfo.fileName() == "." || fileInfo.fileName() == ".." ) {
            continue;
        }
        totalfiles++;	//统计待加密文件总数
        qDebug() << "totalfile:" << totalfiles;

        if (fileInfo.isDir()) {
            if (!getFileCount(fileInfo.filePath()))
                return false;
        }	 //当为目录时,递归的进行
    }
    return true;
}
拷贝目录文件,同时完成加密工作如下:
bool ThreadEncryptionFile::CopyDirectoryFiles(const QString &fromDir,const QString &toDir)
{
    QDir sourceDir(fromDir);	//待加密文件所在文件夹
    QDir targetDir(toDir);		//指定文件夹

    if(!targetDir.exists()) {
        if(!targetDir.mkdir(targetDir.absolutePath())) {
            return false;
        }
    }	//指定文件夹不存在,则创建新文件夹

    QFileInfoList list = sourceDir.entryInfoList();
    for(int i = 0; i < list.size(); i++) {
        QFileInfo fileInfo = list.at(i);
        if(fileInfo.fileName()=="." || fileInfo.fileName()=="..") {
            continue;
        }

        //如果是目录文件,则创建目录
        if(fileInfo.isDir()) {
            if(!CopyDirectoryFiles(fileInfo.filePath(),targetDir.filePath(fileInfo.fileName()))) {
                return false;
            }
        } else {
            if(targetDir.exists(fileInfo.fileName())) {
                //有相同的,直接删除
                targetDir.remove(fileInfo.fileName());
            }
            //拷贝完文件后,加密文件
            Encryption_copy(fileInfo.filePath(), targetDir.filePath(fileInfo.fileName()));
        }
        //统计完成文件数量
        FlishFile++;
        //通过信号通知界面进度条刷新进度显示
        emit UpdateProgressBar(FlishFile);
        qDebug() << "FlishFile:" << FlishFile;
    }
    return true;
}
   重点中的重点:拷贝完文件&加密文件!!! -> Encryption_copy
void ThreadEncryptionFile::Encryption_copy(const QString& SourceFileName, const QString& TargetFileName)
{
    QFileInfo TmpFileInfo(SourceFileName);
    ThreadEncryptionFile EncryFile;
    //通过文件信息,判断待加密的文件的类型
    if(!TmpFileInfo.isFile()){
        return;
    }

    QString TmpSuffix = TmpFileInfo.suffix();

    if(TmpSuffix == "txt"){
        //待加密文件为文本文件
        EncryFile.EncryptionTextFile(SourceFileName, TargetFileName);
    } else if(TmpSuffix == "jpg" || TmpSuffix == "png"){
        //待加密文件为图片文件
        EncryFile.EncryptionPhotoFile(SourceFileName, TargetFileName);
    } else if(TmpSuffix == "mp3") {
        //待加密文件为音频文件
        EncryFile.EncryptionMusicFile(SourceFileName, TargetFileName);
    } else if(TmpSuffix == "mp4") {
        //待加密文件为视频文件
        EncryFile.EncryptionMovieFile(SourceFileName, TargetFileName);
    } else {
        return;
    }
}

后面分别描述不同文件类型的加密方式:

文本文件的加密:

/* text [1]所有数据异或^0xD8 [2]增加*** */
void ThreadEncryptionFile::EncryptionTextFile(const QString& SourceFileName, const QString& TargetFileName)
{
    //先处理目标文件
    QFile TargetFile(TargetFileName);

    if(TargetFile.exists()) {
        //文件存在
    } else {
        //文件不存在,直接创建新文件
        TargetFile.open(QIODevice::ReadWrite);
        TargetFile.close();
    }

    //加密 copy原文件
    QFile SourceFile(SourceFileName);
    QByteArray btData;

    btData = FileToByteArray(SourceFileName);

    //所有数据异或^0x88
    for(int i=0; i<btData.size(); i++){
        btData[i] = word_encrypt(btData[i], g_u8SecretKey);
    }

    //增加***
    btData.prepend("***");

    if(TargetFile.open(QIODevice::ReadWrite)) {
        TargetFile.write(btData);
        TargetFile.close();
    } else {
        qDebug() << "read file faile";
    }
}

图片文件的加密:

void ThreadEncryptionFile::EncryptionPhotoFile(const QString& SourceFileName, const QString& TargetFileName)
{
    //先处理目标文件
    QFile TargetFile(TargetFileName);

    if(TargetFile.exists()) {
        //文件存在
    } else {
        //文件不存在
        TargetFile.open(QIODevice::ReadWrite);
        TargetFile.close();
    }

    QByteArray btData;
    QFile SourceFile(SourceFileName);
    QFileInfo SourceFileInfo(SourceFileName);

    //通过文件信息,判断待加密的文件的类型
    if(!SourceFileInfo.isFile()){
        return;
    }

    QString TmpSuffix = SourceFileInfo.suffix();
    if(TmpSuffix == "jpg"){
        //增加TOP-TECH
        btData = FileToByteArray(SourceFileName);
        btData.prepend("***");

        //.jpg[1]遍历前640个字节[3]取反640个字节里面的0xFFD8->0xD8FF(1个就取反一个,有两个标签的话两个都要取反)
        for(int i=0; i<640; i++){
            if((btData[i]&0xff) == 0xff
                   && (btData[i+1]&0xff) == 0xd8){
                //qDebug() << "0xFFDB->0xD8FF i=" << i;
                btData[i] = 0xd8;
                btData[i+1] = 0xff;
            }
        }

        //open target file
        if(TargetFile.open(QIODevice::ReadWrite)) {
            TargetFile.write(btData);
            TargetFile.close();
        } else {
            qDebug() << "read file faile";
        }
    } else if (TmpSuffix == "png") {
        //.png[1]增加***
        btData = FileToByteArray(SourceFileName);
        btData.prepend("***");
        //open target file
        if(TargetFile.open(QIODevice::ReadWrite)) {
            TargetFile.write(btData);
            TargetFile.close();
        } else {
            qDebug() << "read file faile";
        }
    }
}

音频文件的加密:

/* music [1]所有数据异或^0x88 [2]增加*** */
void ThreadEncryptionFile::EncryptionMusicFile(const QString& SourceFileName, const QString& TargetFileName)
{
    //先处理目标文件
    QFile TargetFile(TargetFileName);

    if(TargetFile.exists()) {
        //文件存在
    } else {
        //文件不存在
        TargetFile.open(QIODevice::ReadWrite);
        TargetFile.close();
    }

    QByteArray btData;
    QFile SourceFile(SourceFileName);

    btData = FileToByteArray(SourceFileName);

    //所有数据异或^0x88
    for(int i=0; i<btData.size(); i++){
        btData[i] = word_encrypt(btData[i], g_u8SecretKey);
    }

    //增加***
    btData.prepend("***");

    if(TargetFile.open(QIODevice::ReadWrite)) {
        TargetFile.write(btData);
        TargetFile.close();
    } else {
        qDebug() << "read file faile";
    }
}

视频文件的加密:(核心中的重点!!! -> 大文件分段处理)

void ThreadEncryptionFile::EncryptionMovieFile(const QString& SourceFileName, const QString& TargetFileName)
{
    //先处理目标文件
    QFile TargetFile(TargetFileName);

    if(TargetFile.exists()) {
        //文件存在
    } else {
        //文件不存在
        TargetFile.open(QIODevice::ReadWrite);
        TargetFile.close();
    }
    
    ReadFile(SourceFileName, TargetFileName);
}
bool ThreadEncryptionFile::ReadFile(const QString& SourceFileName, const QString& TargetFileName)
{
    QFile SourceFile(SourceFileName);
    QFile TargetFile(TargetFileName);
    QByteArray btData;
    qDebug() << "ThreadEncryptionFile::ReadFile";

    //打开待加密文件
    if(!SourceFile.open(QIODevice::ReadWrite)) {
       qDebug() << "open file faile!";
       return false;
    }
	//打开新建文件
    if(!TargetFile.open(QIODevice::ReadWrite | QIODevice::Append)) {
       qDebug() << "open file faile!";
       return false;
    }

    //文件总大小
    int TotalSize = SourceFile.size();
    qDebug() << "TmpFile.size:" << SourceFile.size();
    //将文件分段,每次读取的大小
    int Count = TotalSize / 1024;

    //先把头加密内容写到新文件中
    btData.prepend("***");
    TargetFile.write(btData);

    if (Count <= 0) {
        btData = SourceFile.read(TotalSize);
        TargetFile.write(btData);
    } else {
        for (int i=0; i<Count; i++) {
            btData = SourceFile.read(1024);
            TargetFile.write(btData);	//读一个段,写一段
            TotalSize -= 1024;
        }

        if (TotalSize != 0) {
            btData = SourceFile.read(TotalSize);
            TargetFile.write(btData);
        }
    }

    TargetFile.write(btData);
    //将加密后的文件写入原文件,得到加密后的文件

    TargetFile.close();
    SourceFile.close();
    return true;
}

最后的重点 -> Qt程序打包发布方法!!!

step 1: 设置工程为Release版本

step 2: 正常编译,生成build-encryption_tool-Desktop_Qt_5_12_9_MinGW_32_bit-Release文件夹

step 3:找到encryption_tool.exe文件,copy到自己新建的demo文件夹中(随便文件夹即可)

step 4: 利用官方提供的windeployqt工具,生成可发布软件(即生成dll,这样除了在自己电脑上可以使用,也可以在别人的电脑上使用)

  • 3
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lugas Luo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值