目录
一、MD5算法
1、MD5算法是什么?
2、MD5算法的优点
3、MD5算法的不足
4、MD5加密的应用场景
5、MD5算法的代码
二、SHA-1算法
1、SHA-1算法和MD5算法联系与区别
2、SHA-1算法的代码
一、MD5算法
1、MD5算法是什么?
MD5的英文全称是Message Digest Algorithm MD5,译为消息摘要算法第五版,是众多哈希算法中的一种(哈希算法是一种可以将任意长度的输入转化为固定长度输出的算法)。
因此MD5算法是一种哈希算法,严格来说不能称之为一种加密算法,但是它可以达到加密的效果,因此下文中会出现“MD5加密”这样的称呼。
2、MD5算法的优点
(1)容易计算及不可逆性:
-
现在主流的编程语言基本都支持MD5算法的实现,所以非常容易计算出一个数据的MD5值。
-
而且MD5算法是不可逆的,也就是说我们无法通过常规的方式从MD5值倒推出它的原文。
(2)压缩性:
- 任意长度的数据,其MD5值都是一个32位长度的十六进制字符串,区分大小写(所以要和安卓、服务端商量好是用大写还是小写)。
(3)抗修改性:
- 对原数据做一丁点的改动,MD5值就会有巨大的变动。逆反过来理解一下这个特性,比如说两个原数据的MD5值非常相似,但是你不能想当然的认为它们俩对应的原数据也相似。这个特性表明的是,如果你想要很轻易的由MD5倒猜出原文数据是不可能的,因此这个特性在某种程度上表明了MD5算法是安全的。
(4)抗碰撞性:
抗碰撞性分为两种:
-
第一种,知道了原数据及其MD5值,想要碰撞出这个MD5值,从而猜测出原数据,是非常困难的。这种碰撞的难度表明的是,强制破解MD5算法是非常困难的,更别说上面的轻易倒推了。
-
第二种,我们知道MD5值总是一个32位的十六进制字符串,换算成二进制就是一个128位的字符串,因此所有的MD5值一共有2的128次方种可能性,那么用自然界无穷的数据去对应这个有限的MD5值集合,会不会出现不同的数据有相同的MD5值呢?这样看来,理论上是会的,但是实际中想要找到两个不同的数据有着相同的MD5值是非常困难的。这种碰撞的难度表明的是,我们可以放心的去使用MD5算法,不必担心不同的数据拥有相同的MD5值。
为什么说以上两种碰撞是困难的呢?因为要寻找这样一对碰撞是需要耗费非常非常长的时间的,依照现在计算机的计算能力,碰撞被认为在实际中是不可能发生的。因此,这个特性也在某种程度上表明了MD5算法的安全性。
3、MD5算法的不足
上面说到了MD5算法抗碰撞性的特点,因此在实际应用中MD5被认为是非常安全的,但是在2004年,咱们中国山东大学的王小云教授以及她的同事在美国加州举办的密码学会议上宣布破解了MD5算法,其实也不是真正的破解,而是非常明显的加快了寻找一对碰撞的速度,利用她们的技术,可以在几个小时内就找到一对碰撞,极端情况下,甚至在2008年计算机的计算能力上,几秒钟就可以找到一对碰撞。
因此MD5算法的安全性就引起人们的担忧,所以在对安全性要求较高的场合,不建议直接使用MD5算法。
4、MD5算法的应用场景
(1)登录、注册、修改密码等简单加密操作
①这个过程为什么要加密?为什么要使用MD5加密?
假设一个用户的手机号为18666666666,密码为123456,那么如果不对密码加密的话,数据库中存储的内容将大概是下面这样:
username | pwd |
---|---|
18666666666 | 123456 |
这样一旦数据库泄漏了,用户的所有信息都是明文摆在黑客面前的,后果非常严重。因此需要对密码进行加密存储,那为什么要选择MD5加密呢?
因为MD5加密有一定的安全保证,而且实现起来非常简单,我们当然可以采用其它的加密方式(如SHA家族算法、AES、RSA等),甚至是多种加密方式的组合,但是对安全性要求没那么高的场景,我们就采用了MD5加密。
②直接使用MD5加密的不可靠之处(涉及到彩虹表这个关键词)
接着①,如果我们采用了MD5加密,对用户的密码123456进行加密,那么数据库中存储的内容将大概是下面这样:
username | pwd |
---|---|
18666666666 | e10adc3949ba59abbe56e057f20f883e |
这样,即便数据库被泄露了,摆在黑客面前的是一堆密文,而且MD5加密是不可逆的,所以黑客也无法通过解密来得到用户的明文密码,这样看起来已经很不错了。真得安全吗?不是。
因为黑客们创建了彩虹表这个东西,就使得直接使用MD5加密并不是那么安全。
那什么是彩虹表呢?简单的理解,彩虹表就是一个庞大的数据库,这个数据库里收集了着咱们所有人常用的密码,以及这些密码对应的MD5值、SHA-X值等哈希值(当然还有别的各种哈希算法的哈希值,不止MD5和SHA-X),有了这个彩虹表,黑客就可以通过比对哈希值的方式(当然这个比对方式也在不断进步和加快)来根据密文密码得到明文密码。因此如果你的密码很不幸的刚好被搜集在这个表里面,就可能被破解掉,这也是为什么很多场合我们输密码的时候,总是提示我们要字母数字下划线大小写乱七八糟各种要求,目的就是尽量使得明文密码的复杂度增加一些,尽量使得明文密码及其哈希值不在彩虹表里。现在主流彩虹表的大小一般都是100G以上,你就可以想象它收集了多少数据。
③那加密时比较正确的方案是什么?(涉及到加盐这个关键词)
当然我们可以像很多大平台那样,尽量引导用户去输入一些复杂组合的密码,来降低被采集在彩虹表里的概率,但是要设置这么复杂的密码,用户自己都记不住,气都气死了。
那就让用户还是输简单的密码吧,我们通过程序来把用户的简单密码变的复杂一些,然后再加密存储起来,当然验证的时候,用户输简单密码就能验证通过。这个过程就称之为加盐。下面再简单举个例子,解释一下加盐:
加盐在密码学中就是指,在密码的任意位置插入一些指定的字符串(即盐值salt)的过程。加盐的目的就是为了增强密码的复杂度,从而使得密码及其哈希值不被收集在彩虹表里,使得用户的密码无法被破解。
那么这个(盐值salt)有些什么要求呢?它怎么生成呢?
第一,对于每个用户来说,盐值最好具有唯一性,因为如果盐值不具备唯一性,那么万一一大片用户的原始密码相同,加盐后的密码也相同,这样万一破解了一个,就相当于破解了一大片,黑客会很开心的。比如可以使用用户某些具备唯一性的字段来作为盐值,如id、手机号等等,但是如果要使用现成的字段作为盐值的话,千万不要使用那些可变化的字段作为盐值,如nickname,万一用户改了nickname,再登录都登不进去了(除非你做一堆处理,犯不着)。
第二,盐值最好是由服务端为每个用户随机生成,并且保存在数据库中,之所以要随机生成也是为了使得密码的复杂度增加一些,说了是“最好”,也就是说并非必须如此。
第三,撒盐的方式最好是复杂点,比如分散的撒在原密码中,而不是简单的拼接在原密码的前面或者后面,这样也可以增加密码的复杂度,但同样不是必须如此,根据实际场景只要觉得复杂度够了就可以了。
现在假设把用户的id作为盐值,而且仅仅是拼在原密码后面,那么加密就是对12345611加密,数据库中存储的内容将大概是下面这样:
md5(12345611)
username | pwd | id |
---|---|---|
18666666666 | 43a416fa9c9b453b61175713074324c3 | 11 |
好了,这样的话,我们就通过加盐的方式加强了用户的密码的安全级别。但是这样是否就绝对安全了呢?也不是的。
因为如果数据库泄漏了,虽然黑客不能直接从彩虹表中匹配出密码,但是每个用户的盐值也同时泄漏了,黑客同样可以像我们一样正向撒盐构造新的彩虹数据来破解,他们要把我们随机生成的盐值、还要猜到我们撒盐的方式,这个做坏事的成本就太高了呀。如果真遇到这种安全界别的场景,使用MD5和加盐的方式已经不够用了,要考虑换加密方式了。
④那么实际开发中,如果我们采用了“MD5加密,不过要给密码加盐”的方式,客户端和服务端分别做些什么呢?
上面说到了盐值salt最好是有服务端为每个用户生成一个唯一的、随机的值,保存在服务端。
那么加盐的过程就可以由我们客户端来完成,也可以由服务端来完成。
方案一,加盐由客户端来完成:
- 客户端走一个接口,向服务端请求盐值salt
- 客户端md5(pwd+salt)
- 客户端走登录接口,服务端保存密码
方案二,加盐由服务端来完成:
- 客户端md5(pwd)
- 客户端走登录接口,服务端md5(客户端md5(pwd)+salt),服务端保存密码
我觉得方案二好。客户端和服务端只需一次交互。
(2)生成数字签名:
MD5算法也被广泛用来生成数字签名。(后面第三篇会说到微信支付其实就采用MD5算法来生成的数字签名的)
5、MD5算法的代码
//
// MD5Encrypt.h
// EncryptDemo
//
#import <Foundation/Foundation.h>
@interface MD5Encrypt : NSObject
/**
* MD5加密,返回32位十六进制小写密文
*
* @param string 原明文数据
*
* @return 经过MD5加密后的密文
*/
+ (NSString *)md5EncryptString_Lowercase_FromString:(NSString *)string;
/**
* MD5加密,返回32位十六进制大写密文
*
* @param string 原明文数据
*
* @return 经过MD5加密后的密文
*/
+ (NSString *)md5EncryptString_Uppercase_FromString:(NSString *)string;
@end
//
// MD5Encrypt.m
// EncryptDemo
//
#import "MD5Encrypt.h"
#import <CommonCrypto/CommonCrypto.h>
@implementation MD5Encrypt
/**
* MD5加密,返回32位十六进制小写密文
*
* @param string 原明文数据
*
* @return 经过MD5加密后的密文
*/
+ (NSString *)md5EncryptString_Lowercase_FromString:(NSString *)string {
// 加密数组(占16个字节)
unsigned char resultArray[CC_MD5_DIGEST_LENGTH];
// 加密
CC_MD5(string.UTF8String, (CC_LONG)strlen(string.UTF8String), resultArray);
// 密文
NSMutableString *md5EncryptString = [NSMutableString string];
for (NSInteger i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {
[md5EncryptString appendFormat:@"%02x", resultArray[i]];// X代表十六进制
}
return md5EncryptString;
}
/**
* MD5加密,返回32位十六进制大写密文
*
* @param string 原明文数据
*
* @return 经过MD5加密后的密文
*/
+ (NSString *)md5EncryptString_Uppercase_FromString:(NSString *)string {
// 加密数组(占16个字节)
unsigned char resultArray[CC_MD5_DIGEST_LENGTH];
// 加密
CC_MD5(string.UTF8String, (CC_LONG)strlen(string.UTF8String), resultArray);
// 密文
NSMutableString *md5EncryptString = [NSMutableString string];
for (NSInteger i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {
[md5EncryptString appendFormat:@"%02X", resultArray[i]];// X代表十六进制
}
return md5EncryptString;
}
@end
二、SHA-1算法
SHA家族算法英文全称是Secure Hash Algorithm,中文译作安全散列算法,包括SHA-1、SHA-256等好几种算法,因此SHA算法是一个算法家族,这里只是举例使用SHA-1算法。
1、SHA-1算法和MD5算法联系与区别
SHA-1算法和MD5算法都有MD4算法导出,因此他们俩的特点、缺陷、应用场景基本是相同的。
它俩的区别在于SHA-1算法在长度上是40位十六进制,即160位的二进制,而MD5算法是32位的十六进制,即128位的二进制,所以2的160次是远远超过2的128次这个数量级的,所以SHA-1算法相对来说要比MD5算法更安全一些。
2、SHA-1算法的代码
//
// SHA1Encrypt.h
// EncryptDemo
//
#import <Foundation/Foundation.h>
@interface SHA1Encrypt : NSObject
/**
* SHA1加密,返回40位十六进制小写密文
*
* @param string 原明文数据
*
* @return 经过SHA1加密后的密文
*/
+ (NSString *)sha1EncryptString_Lowercase_FromString:(NSString *)string;
/**
* SHA1加密,返回40位十六进制大写密文
*
* @param string 原明文数据
*
* @return 经过SHA1加密后的密文
*/
+ (NSString *)sha1EncryptString_Uppercase_FromString:(NSString *)string;
@end
//
// SHA1Encrypt.m
// EncryptDemo
//
#import "SHA1Encrypt.h"
#import <CommonCrypto/CommonCrypto.h>
@implementation SHA1Encrypt
/**
* SHA1加密,返回40位十六进制小写密文
*
* @param string 原明文数据
*
* @return 经过SHA1加密后的密文
*/
+ (NSString *)sha1EncryptString_Lowercase_FromString:(NSString *)string {
const char *cstr = [string cStringUsingEncoding:NSUTF8StringEncoding];
NSData *data = [NSData dataWithBytes:cstr length:string.length];
uint8_t digest[CC_SHA1_DIGEST_LENGTH];
CC_SHA1(data.bytes, (unsigned int)data.length, digest);
NSMutableString *sha1EncryptString = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
for(NSInteger i = 0; i < CC_SHA1_DIGEST_LENGTH; i ++) {
[sha1EncryptString appendFormat:@"%02x", digest[i]];
}
return sha1EncryptString;
}
/**
* SHA1加密,返回40位十六进制大写密文
*
* @param string 原明文数据
*
* @return 经过SHA1加密后的密文
*/
+ (NSString *)sha1EncryptString_Uppercase_FromString:(NSString *)string {
const char *cstr = [string cStringUsingEncoding:NSUTF8StringEncoding];
NSData *data = [NSData dataWithBytes:cstr length:string.length];
uint8_t digest[CC_SHA1_DIGEST_LENGTH];
CC_SHA1(data.bytes, (unsigned int)data.length, digest);
NSMutableString *sha1EncryptString = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
for(NSInteger i = 0; i < CC_SHA1_DIGEST_LENGTH; i ++) {
[sha1EncryptString appendFormat:@"%02X", digest[i]];
}
return sha1EncryptString;
}
@end