Blowfish是一种对称加密算法,由布鲁斯·施奈尔(Bruce Schneier)于1993年设计,旨在替代当时存在的加密算法。它使用相同的密钥进行加密和解密,支持8字节数据块的加密,并允许使用可变长度的密钥,从32位(4字节)到448位(56字节)不等。Blowfish算法专为32位指令集处理器设计,相较于DES算法,其执行速度显著提升。自其诞生以来,Blowfish已经经过了广泛的分析。该算法未申请专利,无需许可,且免费供所有人使用。
Blowfish算法由两部分组成:密钥扩展部分和数据加密部分。密钥扩展部分将至少4字节、最多56字节的可变密钥转换成多个子密钥数组,总共4168字节。Blowfish算法包含16轮运算,每一轮都包括一个依赖于密钥的排列和一个依赖于密钥和数据的替换。所有操作都是32位字的异或(XOR)和加法运算。每轮中唯一的额外操作是四次索引数组数据查找。Blowfish使用大量的子密钥,这些密钥必须在任何数据加密或解密之前预先计算。
关于Blowfish算法的更详细文章可以在以下网址找到:
本文中介绍的C++实现通过了Eric Young提供的测试向量进行测试,测试结果完全一致,除了一处可能由于输入文件中的打字错误导致的例外。
Blowfish算法的C++实现提供了一个名为CBlowfish的类,其公共用户接口如下:
class CBlowFish {
public:
// 构造函数 - 使用给定的密钥初始化P和S盒子
CBlowFish(unsigned char* ucKey, size_t n, const SBlock& roChain = SBlock(0UL,0UL));
// 重置链块
void ResetChain();
// 在地加密/解密缓冲区
void Encrypt(unsigned char* buf, size_t n, int iMode=ECB);
void Decrypt(unsigned char* buf, size_t n, int iMode=ECB);
// 从输入缓冲区加密/解密到输出缓冲区
void Encrypt(const unsigned char* in, unsigned char* out, size_t n, int iMode=ECB);
void Decrypt(const unsigned char* in, unsigned char* out, size_t n, int iMode=ECB);
};
在构造函数中,使用用户指定大小的密钥材料生成子密钥数组,同时初始化链块。ResetChain()函数用于在开始新的加密或解密操作之前重置链块。Encrypt()函数的第一个变体用于对指定大小的数据块进行原地加密,应用指定的操作模式,数据块大小应该是8的倍数。该函数可以在以下模式下操作:ECB、CBC或CFB。在ECB模式下,不使用链式操作。如果使用相同的密钥两次加密相同的数据块,得到的密文数据块是相同的。在CBC模式下,通过首先将明文数据块与前一个密文数据块进行XOR操作,然后加密结果值来获得密文数据块。在CFB模式下,通过加密前一个密文数据块,然后将结果值与明文进行XOR操作来获得密文数据块。操作模式通过iMode参数指定,默认值为ECB。Encrypt()函数的第二个变体将加密结果输出到输出缓冲区。Decrypt()函数是上述Encrypt()函数的逆操作。
CBlowfish类的使用非常简单。在第一个代码片段示例中,使用8字节大小的密钥对8字节数据块进行加密。初始链块是一个空块。数据块"aaaabbbb"被加密,然后被解密回来。
try {
char szHex[17];
// 初始化
CBlowFish oBlowFish((unsigned char*)"abcdefgh", 8);
char szDataIn[] = "aaaabbbb";
char szDataOut[17] = "\0\0\0\0\0\0\0\0";
// 加密
oBlowFish.Encrypt((unsigned char*)szDataIn, (unsigned char*)szDataOut, 8);
CharStr2HexStr((unsigned char*)szDataIn, szHex, 8);
cout << szHex << endl;
CharStr2HexStr((unsigned char*)szDataOut, szHex, 8);
cout << szHex << endl;
memset(szDataIn, 0, 8);
// 解密
oBlowFish.Decrypt((unsigned char*)szDataOut, (unsigned char*)szDataIn, 8);
CharStr2HexStr((unsigned char*)szDataIn, szHex, 8);
cout << szHex << endl;
}
catch (exception& roException) {
cout << roException.what() << endl;
}
try {
CBlowFish oBlowFish((unsigned char*)"1234567890123456", 16);
char szDataIn1[49] = "ababababccccccccababababccccccccababababcccccccc";
char szDataIn[49];
char szDataOut[49];
memset(szDataIn, 0, 49);
memset(szDataOut, 0, 49);
// 测试 ECB
strcpy(szDataIn, szDataIn1);
memset(szDataOut, 0, 49);
oBlowFish.Encrypt((unsigned char*)szDataIn, (unsigned char*)szDataOut, 48, CBlowFish::ECB);
memset(szDataIn, 0, 49);
oBlowFish.Decrypt((unsigned char*)szDataOut, (unsigned char*)szDataIn, 48, CBlowFish::ECB);
// 测试 CBC
oBlowFish.ResetChain();
strcpy(szDataIn, szDataIn1);
memset(szDataOut, 0, 49);
oBlowFish.Encrypt((unsigned char*)szDataIn, (unsigned char*)szDataOut, 48, CBlowFish::CBC);
memset(szDataIn, 0, 49);
oBlowFish.ResetChain();
oBlowFish.Decrypt((unsigned char*)szDataOut, (unsigned char*)szDataIn, 48, CBlowFish::CBC);
// 测试 CFB
oBlowFish.ResetChain();
strcpy(szDataIn, szDataIn1);
memset(szDataOut, 0, 49);
oBlowFish.Encrypt((unsigned char*)szDataIn, (unsigned char*)szDataOut, 48, CBlowFish::CFB);
memset(szDataIn, 0, 49);
oBlowFish.ResetChain();
oBlowFish.Decrypt((unsigned char*)szDataOut, (unsigned char*)szDataIn, 48, CBlowFish::CFB);
cout << endl;
}
catch (exception& roException) {
cout << "Exception: " << roException.what() << endl;
}