一、问题需求。
我们的软件是使用星际译王的开源软件进行词典处理。可以使用它提供的stardict-tool工具,对用户提供的词典(DRAE.tab),转换成星际译王所需的文件(转换成三个文件,idx,ifo,dict.dz)。只要tab文件按照星际译王的格式要求,即可进行转换(单词与解释在一行,使用/t分割)。
由于用户提供的词典具有版权等问题,所以,需要实现对tab进行加密。
二、处理方法。
使用QCA。
编写一个dictionary_encrypt的工具,以后每次运行这个工具,就可以对tab进行加密,存储成另一个tab文件,供stardict-editor进行转换。
编写一个crypto_util的library,在其他的地方只要使用了这个library,就支持相关的加密解密工作。
三、遇到问题及尝试的改进。
1.编码问题
在tab文件中,有各种西班牙的字母,所以,需要注意设置读取文件的格式是utf-8。其次,读入文件,写文件,相关的操作方法要对应起来,才能保证格式的一致。如使用QFile提供的方法来读文件,则对应地,要使用QFile的方法来写新文件。如果用QTextStream或其他方法来写文件,则在实际操作中发现格式会不一致,导致后面的操作出错。
2.大小问题及模糊检索
对单词进行加密,使用的是aes-128加密方法,会增加文件的大小。(比如,原来是word,加密后会是asdfasfdasdf1221341423等一长串字符。)
对于tab文件,示例形式是:
word-a explanation-a
(开始的要求是只需对explanation-a进行加密。后来客户觉得word-a也需加密。我们认为,对word-a的加密,导致的增加会是翻倍的。因为word-a不仅存在与词典(.dict.dz)中,还存在于词典的索引文件(.idx)中。就是说,加密后的word部分,会在词典里有两份表示。
但这样子,文件太大。
所以,考虑使用折中的方法处理。
使用两次的start-dict。
一是存储word的对:
word-a 1
word-b 2
.....
一是存储explanation的对:
1 explanation-a
2 explanation-b
.....
但是这样子,其实word-a还是存在了两次啊。为什么会减少空间?这不科学!
但是实际就是这样的。。如果是 word-encrypted explanation-encrypted 这样的对,那么总大小是500M。如果分开word-encrypted 1 和1 explanation-encrypted,则两个词典总大小是180M。
应该是与dict.dz文件的存储形式有关。并不是线性的增加。
后来,与客户进行沟通,放弃了对word的加密。
其实对word加密,发现还有问题,就是fuzzy的查询方式不能支持。查阅 有关资料,对于加密的其实也可以建立索引。具体可参考如下:
http://www.cnki.com.cn/Article/CJFDTotal-JSJA2011S1030.htm)
四、代码。
使用QT的QCA进行加密,示例如下。
1.配置QCA:
自行解决...
2.头文件:
#ifndef TEST_AES_CRYPTO_H
#define TEST_AES_CRYPTO_H
#include <QtCore/QtCore>
#include <QtCrypto/QtCrypto>
class TestAESCrypto: public QObject
{
Q_OBJECT
public:
TestAESCrypto(QObject *parent = 0);
~TestAESCrypto();
QString encryptData(const QString & source);
QString decryptData(const QString & source);
void encryptFile(const QString & in_file_name, const QString & out_file_name);
private:
QCA::Initializer init_;
QCA::SymmetricKey key_;
QCA::InitializationVector iv_;
};
#endif // TEST_AES_CRYPTO_H
3.源文件:
#include "test_aes_crypto.h"
#include <QtCrypto/QtCrypto>
const static QString KEY = "aff400e117304202a660442fd2dc9172";
const static QString IV = "156b68e397a54a2f8aa68b2be2f4eb11";
TestAESCrypto::TestAESCrypto(QObject *parent)
: QObject(parent)
{
//initialize QCA
init_ = QCA::Initializer();
key_ = QCA::SymmetricKey(KEY.toAscii());
iv_ = QCA::InitializationVector(IV.toAscii());
}
TestAESCrypto::~TestAESCrypto()
{
}
QString TestAESCrypto::encryptData(const QString &source)
{
//initialize the cipher for aes128 algorithm, using CBC mode,
//with padding enabled (by default), in encoding mode,
//using the given key and initialization vector
QCA::Cipher cipher = QCA::Cipher(QString("aes128"), QCA::Cipher::CBC,
QCA::Cipher::DefaultPadding, QCA::Encode,
key_, iv_);
//check if aes128 is available
if (!QCA::isSupported("aes128-cbc-pkcs7"))
{
qDebug() << "AES128 CBC PKCS7 not supported - "
"please check if qca-ossl plugin"
"installed correctly !";
return "";
}
QCA::SecureArray secureData = source.toUtf8();
//we encrypt the data
QCA::SecureArray encrypted_data = cipher.process(secureData);
//check if encryption succeded
if (!cipher.ok())
{
qDebug() << "Encryption failed !";
return "";
}
return QCA::arrayToHex(encrypted_data.toByteArray());
}
QString TestAESCrypto::decryptData(const QString &source)
{
//initialize the cipher for aes128 algorithm, using CBC mode,
//with padding enabled (by default), in decoding mode,
//using the given key and initialization vector
QCA::Cipher cipher = QCA::Cipher(QString("aes128"), QCA::Cipher::CBC,
QCA::Cipher::DefaultPadding, QCA::Decode,
key_, iv_);
QCA::SecureArray encrypted_data = QCA::SecureArray(QCA::hexToArray(source));
QCA::SecureArray decrypted_data = cipher.process(encrypted_data);
//check if decryption succeded
if (!cipher.ok())
{
qDebug() << "Decryption failed !";
return "";
}
return QString::fromUtf8(decrypted_data.data());
}
//跟具体应用场景有关。我所需的加密文件是以/t为分隔符的字典
void TestAESCrypto::encryptFile(const QString &in_file_name, const QString &out_file_name)
{
QFile in_file(in_file_name);
QFile out_file(out_file_name);
QTextStream in_stream;
QTextStream out_stream;
if(!in_file.open(QIODevice::ReadOnly|QIODevice::Text))
{
qDebug() << " Cannot open file to read";
return;
}
if(!out_file.open(QIODevice::WriteOnly|QIODevice::Truncate))
{
qDebug() << " Cannot open file to write";
return;
}
in_stream.setDevice(&in_file);
in_stream.setCodec("UTF-8");
out_stream.setDevice(&out_file);
out_stream.setCodec("UTF-8");
QString line_string = in_stream.readLine();
QStringList string_list;
int line_count = 1;
//使用map来判断这个单词是否已经出现过
QMap<QString,bool> map;
while(!line_string.isNull())
{
string_list = line_string.split("\t");
if(string_list.size() != 2)
{
qDebug() << "The input file doesn't contain two elements in line "<<line_count;
}else if(string_list.at(0).isEmpty() || string_list.at(0).isNull()){
qDebug() << "The word is empty in line "<<line_count;
}else if(map.contains(string_list.at(0))) {
qDebug() << "The word " <<string_list.at(0)<<" is duplicate in line "<<line_count;
qDebug() << "whole string is "<< line_string;
}else{
map.insert(string_list.at(0), true);
out_stream << string_list.at(0);
out_stream << "\t";
out_stream << encryptData(string_list.at(1));
out_stream << "\n";
}
line_count++;
line_string = in_stream.readLine();
}
in_file.close();
out_file.close();
}
4.用于加密的main.cpp。
#include <QApplication>
#include "test_aes_crypto.h"
int main(int argc, char *argv[])
{
if (argc != 3)
{
qDebug() << "Need to input two file names! " << argc;
return 0;
}
qDebug() << "Encryption begins..";
TestAESCrypto crypto;
crypto.encryptFile(QString(argv[1]), QString(argv[2]));
qDebug() << "Encryption ends.";
return 1;
}
5.在makefile里的示例:
ENABLE_QT()
INCLUDE_DIRECTORIES(.)
# Header files.
FILE(GLOB MOC_HDRS *.h)
QT4_WRAP_CPP(MOC_SRCS ${MOC_HDRS})
# Source files.
FILE(GLOB SRCS *.cpp)
SET(SRCS ${MOC_HDRS} ${SRCS} ${MOC_SRCS})
ADD_LIBRARY(crypto_util ${SRCS})
TARGET_LINK_LIBRARIES(crypto_util qca ${QT_LIBRARIES} ${ADD_LIB})
ADD_EXECUTABLE(dictionary_encrypt main.cpp ${SRCS})
makefile主要的是
ADD_LIBRARY(crypto_util ${SRCS})
在其他的地方可以使用到crypto_util这个library
ADD_EXECUTABLE(dictionary_encrypt main.cpp ${SRCS})
生成了dictionary_encrypt这个bin文件,进行加密
参考