引言:
根据相关资料(上半部之哈希/下半部之对称和非对称加密)进行整理,方便以后回顾和查阅......
- Base64
- MD5、SHA1、SHA256、SHA512、HMAC
- AES
- RSA
一、Base64
1. 算法介绍
Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一,大家可以查看RFC2045~RFC2049,上面有MIME的详细规范。Base64编码可用于在HTTP环境下传递较长的标识信息。个人感觉Base64仅仅是一种编码方式(%02X
)而不是加密方式如同UTF-8。
它使用 2 的最大次方来代表仅可打印的ASCII字符。在 Base64 中的变量使用字符 A—Z、a—z 和 0 —9 共 62 个字符 , 用来作为 Base64 编码表中的 64 码 , 最后两个用作为数字的符号在不同的系统中而不同。Base64 编码转换的时候,将三个字节的数据 , 先后放入 24 位的缓冲区中 , 先来的字节占高位。如果数据不足 3 个字节 , 将缓冲区中剩下的位用 0 补足。然后 , 每次取出 6 位,按照其值选择ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
中的字符作为编码后的输出。不断进行 , 直到全部输入数据转换完成。
Base64 要求把每三个 8Bit 的字节按照每 6Bit 一组的长度分割成四组(3 X 8 = 4 X 6 = 24),然后给每组 6Bit 的数据添加两位高位 0,组成四个新的 8Bit 的字节。也就是说, 转换后的字符串理论上将要比原来的长 1/3。然后将新产生的四个8Bit字节根据转换表映射为 ASCII 字符。(最后两个字符的定义在不同的系统中有所不同)。为了保证所输出的编码位可读字符,Base64制定了一个编码表,以便进行统一转换。编码表的大小为2^6=64,这也是Base64名称的由来。
如果原文的字节数不是 3 的倍数,即转换到最后 部分时 bit 数不够 6 的倍数时我们规定,不足的 bit 位 使用全 0 来补足,转换后需要在密文的末尾添加 = 号来标注。如果原文剩余 1 字节(即需要补足 4 位 0), 那么就在密文末尾添加两个 = 号,如果原文剩余 2 字节(即需要补足 2 位 0),则添加一个 = 号。
2. 上代码。。。。
在iOS7之前我们一般用的都是第三方框架,比如nicklockwood写的Base64框架还有Google的GTMBase64,虽然苹果有了自己的实现,但是许多其它的加密框架都用到了它,所以还是要了解一下,另外它还提供任意长度字符插入\r\n
,而苹果只能是64或76长度。
Base64存储方式(重要):
可见字符串形式
为了保证所输出的每一个编码字节都是可读字符,而不是0~63这些数字,Base64制作了一个码表,就像ASCII码表一样,每一个Base64码值都有对应的字符。64个可读字符从0到63非别是A-Z、a-z、0-9、+、/,这也是Base64名字的由来。以16进制形式
即NSData形式保存,Base64编码结果为字符,而这些字符又对应ASCII码表的码值,NSData就是存储ASCII码表的码值。
示例: 苹果原生API->NSData的扩展:NSData (NSDataBase64Encoding)
假设我们对字符串"123"进行Base64编码,"123"对应的16进制是313233
,二进制为00110001 00110010 00110011
,将其变为4*** 6结果即下表中的第一行。然后根据Base64的码表,它们分别对应表中的第二行。那么"123"编码的最终结果即为MTIz,以字符串的形式保存。然后根据MTIz对应ASCII码值,以NSData形式存储,如表中的第三行。
转换为4*6结果 | 00001100 | 00010011 | 00001000 | 00110011 |
---|---|---|---|---|
Base64对应字符 | M | T | I | z |
对应ASCII码值(16进制) | 4d | 54 | 49 | 7a |
上面的过程通过代码实现如下:
//1 待编码的原始字符串
NSString *plainStr = @"123";
// 2 将其转换成NSData保存,那么"123"对应的ASCII码表码值是31、32、33(16进制)
NSData *plainData = [plainStr dataUsingEncoding:NSUTF8StringEncoding];
// 3.1 将其进行Base64编码,且结果以字符串形式保存,对应表中的第二行
NSString *baseStr = [plainData base64EncodedStringWithOptions:0];
// 3.2 将其进行Base64编码,且结果以NSData形式保存
NSData *base64Data = [plainData base64EncodedDataWithOptions:0];
另外对于参数NSDataBase64EncodingOptions选项,有多种取值
- NSDataBase64Encoding64CharacterLineLength:每64个字符插入\r或\n
- NSDataBase64Encoding76CharacterLineLength:每76个字符插入\r或\n,标准中有要求是76个字符要换行,不过具体还是自己定
- NSDataBase64EncodingEndLineWithCarriageReturn:插入字符为\r
- NSDataBase64EncodingEndLineWithLineFeed:插入字符为\n
前两个选项为是否允许插入字符,以及多少个字符长度插入,两个可以选其一或者都不选。后两个选项代表要插入的具体字符。比如我们想76个字符后插入一个\r则可以NSDataBase64Encoding76CharacterLineLength | NSDataBase64EncodingEndLineWithCarriageReturn
。而在上面举的例子中选项为0,则代表不插入字符。
二、MD5、SHA1、SHA256、SHA512、HMAC
实质是抽取特征码,这样一般不会重复!不同的文本它的哈希结果是有可能相同的,但概率很小。(举例:比如想要识别一个人,我们可以通过他的指纹来锁定他,指纹出现相同的概率很低吧!在这里,人就相当于数据,而指纹就相当于对人这个数据进行hash后得到的结果)
对任意一个二进制数据进行哈希,可以得到定长的字符串结果,例如MD5哈希结果是128bit,更多是以32个字符的十六进制格式哈希输出
还有就是不可逆的,既然是不可逆的,那么当然不是用来加密的,而是签名
以MD5为例说明:(SHA实现换汤不换药,更换实现中的数据格式以及加密算法就ok了)
+ (NSString *)md5EncryptStringWithString:(NSString *)str{
const char *plain = str.UTF8String;
unsigned char *digest;
digest = malloc(CC_SHA1_DIGEST_LENGTH);
CC_MD5(plain, (CC_LONG)strlen(plain), digest);
NSString *encode = [self stringFromBytes:digest length:CC_MD5_DIGEST_LENGTH];
free(digest);
return encode;
}
+ (NSString *)md5EncryptStringWithData:(NSData *)data{
unsigned char *digest;
digest = malloc(CC_SHA1_DIGEST_LENGTH);
CC_MD5(data.bytes, (CC_LONG)data.length, digest);
NSString *encode = [self stringFromBytes:digest length:CC_MD5_DIGEST_LENGTH];
free(digest);
return encode;
}
+ (NSData *)md5EncryptDataWithString:(NSString *)str{
const char *plain = str.UTF8String;
unsigned char result[CC_MD5_DIGEST_LENGTH];
CC_MD5(plain, (CC_LONG)strlen(plain), result);
return [[NSData alloc] initWithBytes:result length:CC_MD5_DIGEST_LENGTH];
}
+ (NSData *)md5EncryptDataWithData:(NSData *)data{
unsigned char result[CC_MD5_DIGEST_LENGTH];
CC_MD5(data.bytes, (CC_LONG)data.length, result);
return [NSData dataWithBytes:result length:CC_MD5_DIGEST_LENGTH];
}
补充说明:
- 以NSData输出,是以
dataWithBytes:length:
方法获取(byte和长度)
- 以NSString输出,苹果没有相关的方法提供,见下面实现
+ (NSString *)stringFromBytes:(uint8_t *)bytes length:(int)length {
NSMutableString *strM = [NSMutableString string];
for (int i = 0; i < length; i++) {
//此处%02X中X的大小写决定了输出字母的大小写
[strM appendFormat:@"%02X", bytes[i]];
}
return [strM copy];
}
以HMACMD5为例说明HAMAC:
HMAC是密钥相关的哈希运算消息认证码(Hash-based Message Authentication Code),HMAC运算利用哈希算法,以一个密钥和一个消息为输入,生成一个消息摘要作为输出。个人感觉有秘钥会更安全一点,但是HTTPS才是以后发展王道。。。。。下面实现有字符串输出的依然参考上面stringFromBytes:length:
+ (NSString *)hmacMD5EncryptStringWithString:(NSString *)str andKey:(NSString *)key{
const char *keyData = key.UTF8String;
const char *strData = str.UTF8String;
uint8_t buffer[CC_MD5_DIGEST_LENGTH];
CCHmac(kCCHmacAlgMD5, keyData, strlen(keyData), strData, strlen(strData), buffer);
return [self stringFromBytes:buffer length:CC_MD5_DIGEST_LENGTH];
}
+ (NSString *)hmacMD5EncryptStringWithData:(NSData *)data andKey:(NSString *)key{
const char *keyData = key.UTF8String;
// const char *strData = str.UTF8String
uint8_t buffer[CC_MD5_DIGEST_LENGTH];
CCHmac(kCCHmacAlgMD5, keyData, strlen(keyData), [data bytes], [data length], buffer);
return [self stringFromBytes:buffer length:CC_MD5_DIGEST_LENGTH];
}
+ (NSData *)hmacMD5EncryptDataWithString:(NSString *)str andKey:(NSString *)key{
const char *keyData = key.UTF8String;
const char *strData = str.UTF8String;
unsigned char hash[CC_MD5_DIGEST_LENGTH];
CCHmac(kCCHmacAlgMD5, keyData, strlen(keyData), strData, strlen(strData), hash);
return ( [NSData dataWithBytes: hash length: CC_MD5_DIGEST_LENGTH] );
}
+ (NSData *)hmacMD5EncryptDataWithData:(NSData *)data andKey:(NSString *)key{
const char *keyData = key.UTF8String;
unsigned char hash[CC_MD5_DIGEST_LENGTH];
CCHmac(kCCHmacAlgMD5, keyData, strlen(keyData), data.bytes, data.length, hash);
return ( [NSData dataWithBytes: hash length: CC_MD5_DIGEST_LENGTH] );
}
三、AES
AES加密过程涉及到4种操作:字节替代(SubBytes)、行移位(ShiftRows)、列混淆(MixColumns)和轮密钥加(AddRoundKey)。解密过程分别为对应的逆操作。由于每一步操作都是可逆的,按照相反的顺序进行解密即可恢复明文。加解密中每轮的密钥分别由初始密钥扩展得到。算法中16字节的明文、密文和轮密钥都以一个4x4的矩阵表示。
说明:AES根据秘钥的长度不同分为AES128、AES129、AES256;AES细分又有很多加密模式(ECB、CBC、CFB、OFB),一般开发常用的有ECB和CBC。
- ECB(Electronic Code Book,电子密码本)模式
是一种基础的加密方式,要加密的数据被分割成分组长度相等的块,不足补齐,然后单独的一个个组加密,合在一起输出组成密文。
优点: 1.简单; 2.有利于并行计算; 3.误差不会被扩散;
缺点: 1.不能隐藏明文的模式; 2.可能对明文进行主动攻击;
因此,此模式适于加密小消息。- CBC(Cipher Block Chaining,加密块链)模式
是一种循环模式,也将要加密的数据分割为长度相等的组,不足补齐,前一个分组的密文和当前分组的明文异或操作后再加密,这样做的目的是增强破解难度,会比ECB安全一点。
优点: 不容易主动攻击,安全性好于ECB,适合传输长度长的报文,是SSL、IPSec的标准。
缺点: 1.不利于并行计算; 2.误差传递; 3.需要初始化向量IV
AES加密
/// 默认使用kCCOptionPKCS7Padding填充
#define kPaddingMode kCCOptionPKCS7Padding
/*
默认CBC模式,返回base64编码
*/
- (NSString *)aesEncryptWithHexKey:(NSString *)key hexIv:(NSString *)iv {
NSData *aesKey = [key dataFromHexString];
if (iv == nil) {
// 32长度
iv = @"00000000000000000000000000000000";
}
NSData *aesIv = [iv dataFromHexString];
NSData *resultData = [self aesEncryptWithDataKey:aesKey dataIv:aesIv];
return [resultData base64EncodedStringWithOptions:0];
}
/*
默认CBC模式,返回base64编码
*/
- (NSString *)aesEncryptWithKey:(NSString *)key iv:(NSString *)iv {
NSData *aesKey = [key dataUsingEncoding:NSUTF8StringEncoding];
if (iv == nil) {
// 32长度
iv = @"00000000000000000000000000000000";
}
NSData *aesIv = [iv dataUsingEncoding:NSUTF8StringEncoding];
NSData *resultData = [self aesEncryptWithDataKey:aesKey dataIv:aesIv];
return [resultData base64EncodedStringWithOptions:0];
}
/*
CBC模式,返回NSData
*/
- (NSData *)aesEncryptWithDataKey:(NSData *)key dataIv:(NSData *)iv {
return [self aesEncryptOrDecrypt:kCCEncrypt data:[self dataUsingEncoding:NSUTF8StringEncoding] dataKey:key dataIv:iv mode:kPaddingMode];
}
/*
ECB模式,返回base64编码
*/
- (NSString *)aesECBEncryptWithHexKey:(NSString *)key {
NSData *aesKey = [key dataFromHexString];
NSData *resultData = [self aesECBEncryptWithDataKey:aesKey];
return [resultData base64EncodedStringWithOptions:0];
}
/*
ECB模式,返回base64编码
*/
- (NSString *)aesECBEncryptWithKey:(NSString *)key {
NSData *aesKey = [key dataUsingEncoding:NSUTF8StringEncoding];
NSData *resultData = [self aesECBEncryptWithDataKey:aesKey];
return [resultData base64EncodedStringWithOptions:0];
}
/*
ECB模式,返回NSData
*/
- (NSData *)aesECBEncryptWithDataKey:(NSData *)key {
NSData *aesIv = [@"00000000000000000000000000000000" dataFromHexString];
return [self aesEncryptOrDecrypt:kCCEncrypt data:[self dataUsingEncoding:NSUTF8StringEncoding] dataKey:key dataIv:aesIv mode:kPaddingMode | kCCOptionECBMode];
}
AES解密
/*
默认CBC模式解密,默认string为base64格式
*/
- (NSString *)aesBase64StringDecryptWithHexKey:(NSString *)key hexIv:(NSString *)iv {
NSData *aesKey = [key dataFromHexString];
if (iv == nil) {
// 32长度
iv = @"00000000000000000000000000000000";
}
NSData *aesIv = [iv dataFromHexString];
NSData *data = [[NSData alloc] initWithBase64EncodedString:self options:0];
NSData *resultData = [NSString aesDecryptWithData:data dataKey:aesKey dataIv:aesIv];
return [[NSString alloc] initWithData:resultData encoding:NSUTF8StringEncoding];
}
/*
CBC模式解密,返回NSData
*/
+ (NSData *)aesDecryptWithData:(NSData *)data dataKey:(NSData *)key dataIv:(NSData *)iv {
return [[NSString alloc] aesEncryptOrDecrypt:kCCDecrypt data:data dataKey:key dataIv:iv mode:kPaddingMode];
}
/*
ECB模式解密,返回base64编码
*/
- (NSString *)aesECBDecryptWithHexKey:(NSString *)key {
NSData *aesKey = [key dataFromHexString];
NSData *resultData = [self aesECBEncryptWithDataKey:aesKey];
return [[NSString alloc] initWithData:resultData encoding:NSUTF8StringEncoding];;
}
/*
ECB模式解密,返回NSData
*/
- (NSData *)aesECBDecryptWithDataKey:(NSData *)key {
NSData *aesIv = [@"00000000000000000000000000000000" dataFromHexString];
return [self aesEncryptOrDecrypt:kCCDecrypt data:[self dataUsingEncoding:NSUTF8StringEncoding] dataKey:key dataIv:aesIv mode:kPaddingMode | kCCOptionECBMode];
}
补充说明:
这个运算会根据传入key的长度进行识别,只是加密的轮数不同
- (NSData *)aesEncryptOrDecrypt:(CCOperation)option data:(NSData *)data dataKey:(NSData *)key dataIv:(NSData *)iv mode:(int)mode{
// check length of key and iv
if ([iv length] != 16) {
@throw [NSException exceptionWithName:@"Encrypt"
reason:@"Length of iv is wrong. Length of iv should be 16(128bits)"
userInfo:nil];
}
if ([key length] != 16 && [key length] != 24 && [key length] != 32 ) {
@throw [NSException exceptionWithName:@"Encrypt"
reason:@"Length of key is wrong. Length of iv should be 16, 24 or 32(128, 192 or 256bits)"
userInfo:nil];
}
// setup output buffer
size_t bufferSize = [data length] + kCCBlockSizeAES128;
void *buffer = malloc(bufferSize);
// do encrypt
size_t encryptedSize = 0;
CCCryptorStatus cryptStatus = CCCrypt(option,
kCCAlgorithmAES128,
mode,
[key bytes], // Key
[key length], // kCCKeySizeAES
[iv bytes], // IV
[data bytes],
[data length],
buffer,
bufferSize,
&encryptedSize);
NSData *resultData = nil;
if (cryptStatus == kCCSuccess) {
NSData *resultData = [NSData dataWithBytes:buffer length:encryptedSize];
free(buffer);
return resultData;
} else {
free(buffer);
@throw [NSException exceptionWithName:@"Encrypt"
reason:@"Encrypt Error!"
userInfo:nil];
return resultData;
}
return resultData;
}
四、RSA
使用说明
假设A、B双方均拥有一对公私钥(PUB_A
、PRI_A
、PUB_B
、PRI_B
)。
A向B发送Message
的整个签名和加密的过程如下:
- A先使用HASH对
Message
生成一个固定长度的信息摘要Message_hash_A
- A使用A的私钥
PRI_A
对Message_hash_A
进行签名得到Message_sign
(这里为什么不直接对Message
进行签名,而要对Message_hash_A
进行签名呢?因为Message
的长度可能很长,而Message_hash_A
的长度则是固定的,这样性能更高,格式也固定,况且hash的结果一般不会出现重复的可能) - A接着使用B的公钥PUB_B对信息
Message
和信息Message_sign
进行加密得到Message_RSA
,这时A将Message_RSA
发送给B。
当B接收到A的信息Message_RSA
后,获取Message
的步骤如下:
- B用自己的私钥
PRI_B
解密得到明文:Message
和Message_sign
; - 然后B使用A的公钥
PUB_A
解Message_sign
得到Message_hash_A
;同时,B再对Message
使用与A相同的HASH得到Message_hash_B
; - 如果
Message_hash_A
与Message_hash_B
相同,则说明Message
没有被篡改过。
秘钥生成方法
先cd 一个文件夹 生成的pem文件就在这个文件夹下面,以文本编辑器方式打开。。。。。
- 生成私钥,1024bit,PKCS1Padding格式,Base64编码
命令行:openssl genrsa -out rsa_private_key.pem 1024
结果如下:
-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQDOxoZIsFbFMeR0OWnc/sF5A3Gj0BWsoClQW3BKgvMQ85ZXVCM6
7g6XItl5sSW2EyMaIeQ8tRsM0HI4oCvlOMjSVgdyZmqbUfaZDoDYPW2pDbLqMDr/
o1eKxYpssbAyH6ZDyJeTOEu9yF7XUsIilokzc0D9i+uPc8yp/vLYTPDJEQIDAQAB
AoGAFUMevcy8L2zQ9A6PTzU3Cc2L2u9juyuA9A1i/5Z1jhGuLO6u7Llb8LiZqkTH
/u/61Q4VHRT2YhvxEteNi/WJ2L+1wTZYWbE/NIHBls4dTDt4aiMGUG2y6uBcFPmB
97sjT3ofcOHVZuFc80ktyhVuvx5osB8obZHbjn+3hn/pIF0CQQDx1bollu3XXL08
YJrS1mpB3F/87HXcxDa0dWUoqBRUCPjqC+8SuxaddPK6RFvkb1UyWJNzQ5Mb3OZt
65/sipdDAkEA2uMWf0ukTRhxiEYhZIJDSaERYeaWFU+mc6mC2//Tcvy7hldBe15n
7UQNKWl7DbI3Z7NmuKPa+rWqwASqtBAHGwJAOav7iW1V6Q8fvd9X7MHfczdn2LxX
Wz+bwCti5XA38NZ27fHMoM3nFcPHAu68b1yxl6ESAOHzmihy93HCoLloWwJARodX
j2rTJRhUNMHMLrOedNIWZMJE59cDXk9nX/X9rxZqYi4pZlQUDqqXxxk60j3zhlGT
Lrl1bMUuoLKgQmbLswJAIfv1Vw18YcEexWPBkn5iKufu0Fo7+Z776lDLYP1kNyQZ
eohofAAWYNQvHZ4WTpiIxi2FZ9xIRu+M7smsIs0h2g==
-----END RSA PRIVATE KEY-----
- 根据上面的私钥生成公钥
命令行:openssl rsa -in rsa_private_key.pem -out rsa_public_key.pem -pubout
结果如下:
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOxoZIsFbFMeR0OWnc/sF5A3Gj
0BWsoClQW3BKgvMQ85ZXVCM67g6XItl5sSW2EyMaIeQ8tRsM0HI4oCvlOMjSVgdy
ZmqbUfaZDoDYPW2pDbLqMDr/o1eKxYpssbAyH6ZDyJeTOEu9yF7XUsIilokzc0D9
i+uPc8yp/vLYTPDJEQIDAQAB
-----END PUBLIC KEY-----
- 将私钥生成pkcs8格式,可在iOS工程中直接使用
openssl pkcs8 -topk8 -in rsa_private_key.pem -out pkcs8_rsa_private_key.pem -nocrypt
结果:
-----BEGIN PRIVATE KEY-----
MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAM7GhkiwVsUx5HQ5
adz+wXkDcaPQFaygKVBbcEqC8xDzlldUIzruDpci2XmxJbYTIxoh5Dy1GwzQcjig
K+U4yNJWB3JmaptR9pkOgNg9bakNsuowOv+jV4rFimyxsDIfpkPIl5M4S73IXtdS
wiKWiTNzQP2L649zzKn+8thM8MkRAgMBAAECgYAVQx69zLwvbND0Do9PNTcJzYva
72O7K4D0DWL/lnWOEa4s7q7suVvwuJmqRMf+7/rVDhUdFPZiG/ES142L9YnYv7XB
NlhZsT80gcGWzh1MO3hqIwZQbbLq4FwU+YH3uyNPeh9w4dVm4VzzSS3KFW6/Hmiw
HyhtkduOf7eGf+kgXQJBAPHVuiWW7ddcvTxgmtLWakHcX/zsddzENrR1ZSioFFQI
+OoL7xK7Fp108rpEW+RvVTJYk3NDkxvc5m3rn+yKl0MCQQDa4xZ/S6RNGHGIRiFk
gkNJoRFh5pYVT6ZzqYLb/9Ny/LuGV0F7XmftRA0paXsNsjdns2a4o9r6tarABKq0
EAcbAkA5q/uJbVXpDx+931fswd9zN2fYvFdbP5vAK2LlcDfw1nbt8cygzecVw8cC
7rxvXLGXoRIA4fOaKHL3ccKguWhbAkBGh1ePatMlGFQ0wcwus5500hZkwkTn1wNe
T2df9f2vFmpiLilmVBQOqpfHGTrSPfOGUZMuuXVsxS6gsqBCZsuzAkAh+/VXDXxh
wR7FY8GSfmIq5+7QWjv5nvvqUMtg/WQ3JBl6iGh8ABZg1C8dnhZOmIjGLYVn3EhG
74zuyawizSHa
-----END PRIVATE KEY-----