Blowfish是1993年布鲁斯·施奈尔(Bruce Schneier)开发的对称密钥区块加密算法,区块长为64位,密钥为1至448位的可变长度。与DES等算法相比,其处理速度较快。由于其无须受权便可使用,做为一种自由受权的加密方式在SSH、文件加密软件等被普遍地使用。php
关于此算法的发明者:html
布鲁斯·施奈尔 (Bruce Schneier,1963年1月15日-)是一位美国的密码学学者、信息安全专家与做家。他撰写了数本信息安全与密码学相关的书籍,而且创办了BT公司并担任其首席技术官(CTO)。算法
分组密码(Block cypher,又称分块密码),是一种对称密钥密码。它的特色是将明文分红多个等长的组,并用相同的密码算法和密钥对每组分别进行加密和解密。其中典型的如DES和AES做为美国政府核定的标准加密算法,应用领域从电子邮件加密到银行交易转账,很是普遍。
blowfish属于分组密码中的一种。安全
要了解blowfish加密算法,也许咱们应该先了解一下Feistel 密码,由于blowfish加密算法也是feistel密码算法中的一种:
假设F是轮函数,而后K0到Kn做为0到n轮的sub-keys,
基本的操做以下:dom
把明文分为相等的两块:L0和R0,
对于每一轮i=0,1,2,…,n 进行以下运算:
Li+1 = Ri
Ri+1 = Li ^ F(Ri,Ki)
最后获得密文(Rn+1,Ln+1)
解密(Rn+1,Ln+1)是经过以下步骤来完成的:
令i=n,n-1,…,0
Ri = Li+1
Li = Ri+1 ^ F(Li+1,Ki)
最后又获得原来的(L0,R0)了函数
这么说可能很差理解,看下图(来自wikipedia)就清楚了:
简单地说:
就是一种分组密码,通常分为64位一组,一组分左右部分,进行通常为16轮的迭代运算,每次迭代完后交换左右位置,能够本身进行设计的有:
分组大小
密钥长度
轮次数
子密钥生成
轮函数加密
而后应该了解一下块密码的工做模式:
from wikipedia:url
密码学中,块密码的工做模式容许使用同一个块密码密钥对多于一块的数据进行加密,并保证其安全性。[1][2] 块密码自身只能加密长度等于密码块长度的单块数据,若要加密变长数据,则数据必须先被划分为一些单独的密码块。一般而言,最后一块数据也须要使用合适填充方式将数据扩展到符合密码块大小的长度。一种工做模式描述了加密每一数据块的过程,并经常使用基于一个一般称为初始化向量的附加输入值以进行随机化,以保证安全[1]。spa
工做模式主要用来进行加密和认证。[1][3] 对加密模式的研究曾经包含数据的完整性保护,即在某些数据被修改后的状况下密码的偏差传播特性。后来的研究则将完整性保护做为另外一个彻底不一样的,与加密无关的密码学目标。部分现代的工做模式用有效的方法将加密和认证结合起来,称为认证加密模式。[2]设计
虽然工做模式一般应用于对称加密[2],它亦能够应用于公钥加密,例如在原理上对RSA进行处理,但在实用中,公钥密码学一般不用于加密较长的信息,而是使用混合加密方案
通常来讲经常使用的加密模式有这么几种:
电子密码本(ECB)
密码块连接(CBC):在CBC模式中,每一个平文块先与前一个密文块进行异或后,再进行加密。在这种方法中,每一个密文块都依赖于它前面的全部平文块。同时,为了保证每条消息的惟一性,在第一个块中须要使用初始化向量。
填充密码块连接(PCBC)
密文反馈(CFB)
输出反馈(OFB)
计数器模式(CTR)
初始化向量(IV,Initialization Vector)是许多工做模式中用于随机化加密的一块数据,所以能够由相同的明文,相同的密钥产生不一样的密文,而无需从新产生密钥,避免了一般至关复杂的这一过程。
初始化向量与密钥相比有不一样的安全性需求,所以IV一般无须保密,然而在大多数状况中,不该当在使用同一密钥的状况下两次使用同一个IV。对于CBC和CFB,重用IV会致使泄露平文首个块的某些信息,亦包括两个不一样消息中相同的前缀。对于OFB和CTR而言,重用IV会致使彻底失去安全性。另外,在CBC模式中,IV在加密时必须是没法预测的;特别的,在许多实现中使用的产生IV的方法,例如SSL2.0使用的,即采用上一个消息的最后一块密文做为下一个消息的IV,是不安全的[12]。
块密码只能对肯定长度的数据块进行处理,而消息的长度一般是可变的。所以部分模式(即ECB和CBC)须要最后一块在加密前进行填充。有数种填充方法,其中最简单的一种是在平文的最后填充空字符以使其长度为块长度的整数倍,但必须保证能够恢复平文的原始长度;例如,若平文是C语言风格的字符串,则只有串尾会有空字符。稍微复杂一点的方法则是原始的DES使用的方法,即在数据后添加一个1位,再添加足够的0位直到知足块长度的要求;若消息长度恰好符合块长度,则添加一个填充块。最复杂的则是针对CBC的方法,例如密文窃取,残块终结等,不会产生额外的密文,但会增长一些复杂度。布鲁斯·施奈尔和尼尔斯·弗格森提出了两种简单的可能性:添加一个值为128的字节(十六进制的80),再以0字节填满最后一个块;或向最后一个块填充n个值均为n的字节[13]。
CFB,OFB和CTR模式不须要对长度不为密码块大小整数倍的消息进行特别的处理。由于这些模式是经过对块密码的输出与平文进行异或工做的。最后一个平文块(多是不完整的)与密钥流块的前几个字节异或后,产生了与该平文块大小相同的密文块。流密码的这个特性使得它们能够应用在须要密文和平文数据长度严格相等的场合,也能够应用在以流形式传输数据而不便于进行填充的场合。
另外在mcrypt的man文档里也找到了加密模式的介绍:
modes of encryption:
ECB: The Electronic CodeBook mode. It is the simplest mode to use with a block cipher. Encrypts each block independently.CBC: The Cipher Block Chaining mode. It is better than ECB since the plaintext is XOR’ed with the previous ciphertext. A random block is placed as the first block so the same block or messages always encrypt to something different. (This is the default mode)
CFB: The Cipher-Feedback Mode (in 8bit). This is a self-synchronizing stream cipher implemented from a block cipher.
OFB: The Output-Feedback Mode (in 8bit). This is a synchronous stream cipher implemented from a block cipher. It is intended for use in noisy lines, because corrupted ciphertext blocks do not corrupt the plaintext blocks that follow. Insecure (because used in 8bit mode) so I recommend against using it. Added just for completeness.
nOFB: The Output-Feedback Mode (in nbit). n Is the size of the block of the algorithm. This is a synchronous stream cipher implemented from a block cipher. It is intended for use in noisy lines, because corrupted ciphertext blocks do not corrupt the plaintext blocks that follow.
via http://mcrypt.hellug.gr/mcrypt/mcrypt.1.html
BTW,The author of mcrypt and libmcrypt is Nikos Mavroyanopoulos.
现代加密算法中,对称密钥算法一般被分为stream ciphers和block ciphers。分块密码处理固定长度的字符串位数(bit)。这个比特字符串的长度必须和分块大小同样长。输入(明文)和输出(密文)都是一样长度的。输出不能比输入短——这是遵循Pigeonhole principle(鸽巢原理,又名狄利克雷抽屉原理、鸽笼原理)和密码必须是可逆的事实——然而,输出比输入更长又是不可取的。
鸽巢原理:
其中一种简单的表述法为:
如有n个笼子和n+1只鸽子,全部的鸽子都被关在鸽笼里,那么至少有一个笼子有至少2只鸽子。
另外一种为:
如有n个笼子和kn+1只鸽子,全部的鸽子都被关在鸽笼里,那么至少有一个笼子有至少k+1只鸽子。
拉姆齐定理是此原理的推广。
通俗点说:
10只鸽子放进9个鸽笼,那么必定有一个鸽笼放进了至少两只鸽子。
S-box (Substitution-box,替换盒)
在分块密码中,S-box一般被用来掩盖密钥和密文之间的关系。
In general, an S-Box takes some number of input bits, m, and transforms them into some number of output bits, n: an m×n S-Box can be implemented as a lookup table with 2m words of n bits each. Fixed tables are normally used, as in the Data Encryption Standard (DES), but in some ciphers the tables are generated dynamically from the key; e.g. the Blowfish and the Twofish encryption algorithms. Bruce Schneier describes IDEA’s modular multiplication step as a key-dependent S-Box.
IDEA(International Data Encryption Algorithm)
算法说明:
BlowFish 使用了两个box,除了著名的S-box,还有一个p-box
pbox有18个unsigned long元素
sbox有4×256个unsigned long元素
BlowFish算法中,有一个核心加密函数,该函数输入64位信息,运算后, 以64位密文的形式输出。 用BlowFish算法加密信息,须要两个过程:
这里我看的源码是Paul Kocher的C语言版本。
源码下载地址:http://www.schneier.com/blowfish-download.html
1.密钥预处理
2.信息加密
结构体定义:
typedef struct { unsigned long P[16 + 2]; unsigned long S[4][256]; } BLOWFISH_CTX;
1.密钥预处理
BlowFish算法的源密钥——pbox和sbox是固定的。此pbox和sbox的值来自PI的十六进制数字值,使用这些数的缘由是这些数看不出有什么明显的规律。(PI)
这里摘取1-500位:
3.243F6A8885 A308D31319 8A2E037073 44A4093822 299F31D008 2EFA98EC4E 6C89452821 E638D01377 BE5466CF34 E90C6CC0AC 29B7C97C50 DD3F84D5B5 B547091792 16D5D98979 FB1BD1310B A698DFB5AC 2FFD72DBD0 1ADFB7B8E1 AFED6A267E 96BA7C9045 F12C7F9924 A19947B391 6CF70801F2 E2858EFC16 636920D871 574E69A458 FEA3F4933D 7E0D95748F 728EB65871 8BCD588215 4AEE7B54A4 1DC25A59B5 9C30D5392A F26013C5D1 B023286085 F0CA417918 B8DB38EF8E 79DCB0603A 180E6C9E0E 8BB01E8A3E D71577C1BD 314B2778AF 2FDA55605C 60E65525F3 AA55AB9457 48986263E8 144055CA39 6A2AAB10B6 B4CC5C3411 41E8CEA154
void Blowfish_Init(BLOWFISH_CTX *ctx, unsigned char *key, int keyLen) { int i, j, k; unsigned long data, datal, datar; for (i = 0; i < 4; i++) { for (j = 0; j < 256; j++) ctx->S[i][j] = ORIG_S[i][j]; } j = 0; for (i = 0; i < N + 2; ++i) { data = 0x00000000; for (k = 0; k < 4; ++k) { data = (data << 8) | key[j]; j = j + 1; if (j >= keyLen) j = 0; } ctx->P[i] = ORIG_P[i] ^ data; } datal = 0x00000000; datar = 0x00000000; for (i = 0; i < N + 2; i += 2) { Blowfish_Encrypt(ctx, &datal, &datar); ctx->P[i] = datal; ctx->P[i + 1] = datar; } for (i = 0; i < 4; ++i) { for (j = 0; j < 256; j += 2) { Blowfish_Encrypt(ctx, &datal, &datar); ctx->S[i][j] = datal; ctx->S[i][j + 1] = datar; } } }
咱们要加密一个信息,
须要本身选择一个key,用这个key对pbox和sbox进行变换,获得下一步信息加密
所要用的pbox和sbox。
具体的变化算法以下:
用原sbox: ORIG_S 填充 sbox
而后,每次取key与data进行运算,运算后的结果送给pbox
运算过程是这样的:
进行N+2次运算(N=16),
令 32位无符号data为0,因为Key是unsigned char类型的,每次对data左移8位(一个字节)以后与
相应的key相或(即相加),当key长度小于4时,循环使用Key。
接下来,用bf_encypt加密一个全0的64位信息(分为datal和datar,各32位)
用输出的结果datal和datar分别替换pbox[0]和pbox[1]
而后,继续加密datal和datar,用输出结果替换pbox[2]和pbox[3]
……
这样循环18次,把pbox所有替换完成。
接下来是对sbox的替换了。
此次总共是循环4×256次,每次循环的过程与上面的同样。
不过这里用的datal和datar就是上面运算事后的datal和datar.
2.信息加密
接下来看这个加密函数,上面的初始化过程已经用到它了。
void Blowfish_Encrypt(BLOWFISH_CTX *ctx, unsigned long *xl, unsigned long *xr){ unsigned long Xl; unsigned long Xr; unsigned long temp; short i; Xl = *xl; Xr = *xr; for (i = 0; i < N; ++i) { Xl = Xl ^ ctx->P[i]; Xr = F(ctx, Xl) ^ Xr; temp = Xl; Xl = Xr; Xr = temp; } temp = Xl; Xl = Xr; Xr = temp; Xr = Xr ^ ctx->P[N]; Xl = Xl ^ ctx->P[N + 1]; *xl = Xl; *xr = Xr; }
与上面说的Feistel 密码的运算过程是同样的:
把64位的明文分为l和r
进行16轮运算:
令i=0,1,2,3,…,N-1
Xl = Xl ^ ctx->P[i];
Xr = F(ctx, Xl) ^ Xr;
若i<N,交换Xl和Xr的值继续作上述运算。
16轮运算完了以后,再作一次Xl与Xr的交换,由于第16次运算完了以后,实际上
没有再作运算了,可是Xl与Xr仍是交换了,所以,得换回来。
而后,Xr = Xr ^ ctx->P[N];
Xl = Xl ^ ctx->P[N + 1];
如今的Xl和Xr就是加密后的数据了。
加密过程图解:
这加密过程当中用到了F变换:
static unsigned long F(BLOWFISH_CTX *ctx, unsigned long x) { unsigned short a, b, c, d; unsigned long y; d = (unsigned short)(x & 0xFF); x >>= 8; c = (unsigned short)(x & 0xFF); x >>= 8; b = (unsigned short)(x & 0xFF); x >>= 8; a = (unsigned short)(x & 0xFF); y = ctx->S[0][a] + ctx->S[1][b]; y = y ^ ctx->S[2][c]; y = y + ctx->S[3][d]; return y; }
这个变换是这样的:
它接收一个32位的数据,而后从高位到低位分为四段,每段8位,依次给
a,b,c,d
取sbox[0][a]+sbox[1][b],相加后的结果再与sbox[2][c]作异或运算,
得出的结果再加上sbox[3][d]
因为sbox的元素都是32位的,所以,F变换后的输出也是32位的。
轮函数图解:
解密函数:
能够看到解密函数与加密函数很是的类似,只是把p0,p1,…,p17 逆序使用:
void Blowfish_Decrypt(BLOWFISH_CTX *ctx, unsigned long *xl, unsigned long *xr){ unsigned long Xl; unsigned long Xr; unsigned long temp; short i; Xl = *xl; Xr = *xr; for (i = N + 1; i > 1; --i) { Xl = Xl ^ ctx->P[i]; Xr = F(ctx, Xl) ^ Xr; /* Exchange Xl and Xr */ temp = Xl; Xl = Xr; Xr = temp; } /* Exchange Xl and Xr */ temp = Xl; Xl = Xr; Xr = temp; Xr = Xr ^ ctx->P[1]; Xl = Xl ^ ctx->P[0]; *xl = Xl; *xr = Xr; }
关于BlowFish 密码,我的认为能够把它理解为一种复杂的异或密码,异或运算的规律:
A ^ 0 = A A ^ A = 0 (A ^ B) ^ C = A ^ (B ^ C) (B ^ A) ^ A = B ^ 0 = B
按这种逻辑,文本序列的每一个字符能够经过与给定的密钥进行按位异或运算来加密。若是要解密,只须要将加密後的结果与密钥再次进行按位异或运算便可。