这篇文章是对Base64和3DES算法以及他们如何在iphone平台上实现的一点总结。本文吸收了很多前人的资料和成果,在修正了其中的一些错误的基础上添加了自己的理解。在此向前人出色的工作表示感谢。本文主要参考资料如下:
http://baike.baidu.com/view/469071.htm
http://hi.baidu.com/pj19830204/blog/item/96464d2462d32e3fc9955912.html
http://baike.baidu.com/view/350958.htm
http://www.cocoachina.com/bbs/simple/?t47498.html
Base64加密算法
Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一,可用于在HTTP环境下传递较长的标识信息。它的优点是算法效率高,编码出来的结果比较简短,同时也具有不可读性。
Base64要求把每三个8Bit的字节按照每6Bit一组的长度分割成四组(3*8 = 4*6 = 24),然后给每组6Bit的数据添加两位高位0,组成四个新的8Bit的字节。也就是说,转换后的字符串理论上将要比原来的长1/3。然后将新产生的四个8Bit字节根据转换表映射为ASCII字符,转换表如下所示(最后两个字符的定义在不同的系统中有所不同):
索引 | 对应字符 | 索引 | 对应字符 | 索引 | 对应字符 | 索引 | 对应字符 |
0 | A | 16 | Q | 32 | g | 48 | w |
1 | B | 17 | R | 33 | h | 49 | x |
2 | C | 18 | S | 34 | i | 50 | y |
3 | D | 19 | T | 35 | j | 51 | z |
4 | E | 20 | U | 36 | k | 52 | 0 |
5 | F | 21 | V | 37 | l | 53 | 1 |
6 | G | 22 | W | 38 | m | 54 | 2 |
7 | H | 23 | X | 39 | n | 55 | 3 |
8 | I | 24 | Y | 40 | o | 56 | 4 |
9 | J | 25 | Z | 41 | p | 57 | 5 |
10 | K | 26 | a | 42 | q | 58 | 6 |
11 | L | 27 | b | 43 | r | 59 | 7 |
12 | M | 28 | c | 44 | s | 60 | 8 |
13 | N | 29 | d | 45 | t | 61 | 9 |
14 | O | 30 | e | 46 | u | 62 | + |
15 | P | 31 | f | 47 | v | 63 | / |
那么如果原文的字节数不是3的倍数,即转换到最后部分时bit数不够6的倍数该怎么办呢?我们规定,当遇到这种情况,不足的bit位使用全0来补足,转换后需要在密文的末尾添加=号来标注。如果原文剩余1字节(即需要补足4位0),那么就在密文末尾添加两个=号,如果原文剩余2字节(即需要补足2位0),则添加一个=号,这就是为什么有些编码后的结果会以=号结束的原因。
下面举一个例子
编码“Man”
「M」的ASCII碼 = 77 = 01001101
「a」的 = 97 = 01100001
「n」的 = 110 = 01101110
将这三个字节拼合,得出一个24位的资料:
010011010110000101101110 现在六个一组的分开并补高位0,得到4个新的字节为:
00010011 00010110 00000101 00101110
对应的十进制值为:
19 22 5 46
对应的ASCII字符为
T W F u
所以“Man”经过Base64加密后的结果为“TWFu”。
解码同理,把 TWFu的二进制位分割,去掉高位0,重组后得到三个8位值,最后得出原码。
下面是一个原码字节数不是三的倍数的例子:
加密“M”
「M」的ASCII碼 = 77 = 01001101
位数不够6的倍数,补0后变为
010011010000
六个一组分开是
010011 010000,结果是TQ
在密文末尾加两个“=”,结果就是“TQ==”。
3DES加密算法
要理解3DES,就必须先搞懂什么是DES。DES是美国一种由来已久的加密标准,它的工作原理是将数据按照8个字节一段进行加密或解密,从而得到一段8个字节的密文或者明文。之后按照顺序将计算所得的数据连在一起即可。这里需要注意的是,由于DES加密解密时要求数据长度必须为8个字节的倍数,因此当数据长度不足时必须先进行数据填充,这里使用的填充算法根据系统的不同可能会略有不同。
DES算法有两种工作模式,ECB(电子密本方式)和CBC(密文分组链接方式),下面具体解释一下这两种工作模式的不同。
DES ECB其实非常简单,就是将数据按照8个字节一段分别进行DES加密或解密(不足8个字节的按照需求先进行数据填充),最后按照顺序将加密或解密后的结果连在一起即可,各段数据之间互不影响。
DES CBC稍微复杂一些,它在每一段加密或解密的过程中都要与前一段的结果做一次异或操作。同时CBC模式定义了一个特殊的8字节key(称为初始化向量),用以和第一段的结果做异或时用。这种机制使得加密的各段数据之间有了联系。
加密步骤如下:
1)首先将数据按照8个字节一组进行分组得到D1D2......Dn(若数据长度不是8字节的整数倍,先进行数据填充)
2)第一组数据D1与初始化向量I异或后的结果进行DES加密得到第一组密文C1
3)第二组数据D2与第一组的加密结果C1异或以后的结果进行DES加密,得到第二组密文C2
4)之后的数据以此类推,得到Cn
5)按顺序连为C1C2C3......Cn即为加密结果。
解密是加密的逆过程,步骤如下:
1)首先将数据按照8个字节一组进行分组得到C1C2C3......Cn
2)将第一组数据进行解密后与初始化向量I进行异或得到第一组明文D1(注意:一定是先解密再异或)
3)将第二组数据C2进行解密后与第一组密文数据进行异或得到第二组数据D2
4)之后依此类推,得到Dn
5)按顺序连为D1D2D3......Dn即为解密结果。
这里注意一点,解密的结果并不一定是我们原来的加密数据,可能还含有你补得位,一定要把补位去掉才是你的原来的数据。
OK,最后我们来说说3DES。3DES又称Triple DES,顾名思义就是三次DES算法。比起最初的DES,3DES更为安全。它是以DES为基本模块,通过组合分组方法设计出的分组加密算法。设Ek()和Dk()代表DES算法的加密和解密过程,k代表DES算法使用的密钥,P代表明文,C代表密文,则3DES加密解密的过程可表示为:
C=Ek3(Dk2(Ek1(P)))
P=Dk1(Ek2(Dk3(C)))
这里可以k1=k3,但不能k1=k2=k3(如果相等的话就成了DES算法了)
3DES with 2 diffrent keys(k1=k3),可以是3DES-CBC,也可以是3DES-ECB,3DES-CBC整个算法的流程和DES-CBC一样,但是在原来的加密或者解密处增加了异或运算的步骤,使用的密钥是16字节长度的密钥,将密钥分成左8字节和右8字节的两部分,即k1=k3=左8字节,k2=右8字节,然后进行加密运算和解密运算。
3DES with 3 different keys,和3DES-CBC的流程完全一样,只是使用的密钥是24字节的,它将密钥分为3段8字节的密钥k1,k2,k3,在3DES加密时依次使用k1、k2、k3,在3DES解密时依次使用k3、k2、k1。
iPhone平台的Base64及3DES实现
实际应用中我们经常需要对一条消息先进行Base64编码,再进行3DES加密,然后才能在网络上传输,因此Base64和3DES的加密解密过程可封装为同一个接口。iPhone的SDK中并没有提供现成的base64及3DES的接口,需要自己写一个函数来实现。以下是我的代码,Base64部分使用了开源的GTMBase64库,3DES部分参考了网上的代码并根据需要稍作修改,最终形态如下:
#import "CommonCryptor.h"
#import "GTMBase64.h"
+(NSString *) doCipher:(NSString *)plainText operation:(CCOperation)encryptOrDecrypt
{
const void * vplainText;
size_t plainTextBufferSize;
if (encryptOrDecrypt == kCCDecrypt)
{
NSData * EncryptData = [GTMBase64 decodeData:[plainText
dataUsingEncoding:NSUTF8StringEncoding]];
plainTextBufferSize = [EncryptData length];
vplainText = [EncryptData bytes];
}
else
{
NSData * tempData = [plainText dataUsingEncoding:NSUTF8StringEncoding];
plainTextBufferSize = [data length];
vplainText = [tempData bytes];
}
CCCryptorStatus ccStatus;
uint8_t * bufferPtr = NULL;
size_t bufferPtrSize = 0;
size_t movedBytes = 0;
// uint8_t ivkCCBlockSize3DES;
bufferPtrSize = (plainTextBufferSize + kCCBlockSize3DES)
& ~(kCCBlockSize3DES - 1);
bufferPtr = malloc(bufferPtrSize * sizeof(uint8_t));
memset((void *)bufferPtr, 0x0, bufferPtrSize);
NSString * key = @"123456789012345678901234";
NSString * initVec = @"init Vec";
const void * vkey = (const void *)[key UTF8String];
const void * vinitVec = (const void *)[initVec UTF8String];
uint8_t iv[kCCBlockSize3DES];
memset((void *) iv, 0x0, (size_t) sizeof(iv));
ccStatus = CCCrypt(encryptOrDecrypt,
kCCAlgorithm3DES,
kCCOptionPKCS7Padding,
vkey, //"123456789012345678901234", //key
kCCKeySize3DES,
vinitVec, //"init Vec", //iv,
vplainText, //plainText,
plainTextBufferSize,
(void *)bufferPtr,
bufferPtrSize,
&movedBytes);
//if (ccStatus == kCCSuccess) NSLog(@"SUCCESS");
if (ccStatus == kCC ParamError) return @"PARAM ERROR";
else if (ccStatus == kCCBufferTooSmall) return @"BUFFER TOO SMALL";
else if (ccStatus == kCCMemoryFailure) return @"MEMORY FAILURE";
else if (ccStatus == kCCAlignmentError) return @"ALIGNMENT";
else if (ccStatus == kCCDecodeError) return @"DECODE ERROR";
else if (ccStatus == kCCUnimplemented) return @"UNIMPLEMENTED";
NSString * result;
if (encryptOrDecrypt == kCCDecrypt)
{
result = [[[NSString alloc] initWithData:[NSData
dataWithBytes:(const void *)bufferPtr
length:(NSUInteger)movedBytes]
encoding:NSUTF8StringEncoding]
autorelease];
}
else
{
NSData * myData = [NSData dataWithBytes:(const void *)bufferPtr
length:(NSUInteger)movedBytes];
result = [GTMBase64 stringByEncodingData:myData];
}
return result;
}