Hash
Hash,一般翻译做散列、杂凑,或音译为哈希,是把任意长度的输入(又叫做预映射pre-image)通过散列算法变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
常用Hash函数
MD5
SHA-1
SHA2(SHA2包括了SHA-224、SHA-256、SHA-384,和SHA-512)
Hash的特点
- 算法是公开的
- 对相同的数据运算后,得到的结果是一样的
- 对不同数据运算,如MD5得到的结果默认是128位(0x - 32byte)
- Hash是不可逆运算
- 信息摘要,信息“指纹”,用于做数据的识别匹配
因为Hash的算法以及特性,Hash结果有“必然存在,偶然出现”的散列碰撞
Hash的用途
- 搜索引擎
- Rabin-Karp字符串搜索算法 是一个相对快速的字符串搜索算法,它所需要的平均搜索时间是O(n).这个算法是创建在使用散列来比较字符串的基础上的。
- 用户密码的加密
- 一般服务器不需要知道用户的真是密码,而是通过Hash匹配
- 如果直接使用MD5加密,容易被反查询
- 加盐,不使用固定的加盐,提高安全性
- HMAC加密方案:通过动态Key来加密
- 加时间戳的方式,灵活,可保证加密结果每次不同,增加时效性。
- 数字签名
- 对传输的原数据进行Hash
- 使用RSA加密Hash值(这部分数据就是原始数据的签名信息)
- 将 原始数据+数字签名 一起发送给服务器验证
- 版权
- 文件检验【针对文件二进制的hash】
用户密码加密使用
- 安全的传递密码信息
- 服务器不需要保存用户的真实密码
使用方式/使用弊端
1、直接对密码进行MD5的Hash,容易被散列碰撞库查询出来
2、加盐方式,提高了一定的安全性,但是salt是固定的,一但泄露,就不安全了
3、HMAC加密方案,使用一个Key,并且做了两次散列,实际开发中,Key来自于服务器【相对安全】
针对HMAC加密方案做一个安全流出
- 在注册的时候,服务器针对用户生成一个对应的Key
- 注册时,服务器保存的是用户HMAC加密后的密码
- 多设备操作:如果用户在新设备上登录,需要获取登录权限(Key),服务器在接收请求时
- 如果没有开启权限锁,服务器直接返回用户对应的权限(Key)
- 如果开启了权限锁,需要已经登录的设备上请求是否给予权限(Key)
- 针对第三方截获密码的Hash值
- 客户端在进行登录操作时,追加服务器的时间戳进行Hash
- 服务器在检验时,最多进行两次的匹配方式
例如
客户端请求 ((HMAC哈希值)+“201910081801”).MD5
服务器验证 ((HMAC哈希值)+“201910081802”).MD5,如果不匹配再验证((HMAC哈希值)+“201910081801”).MD5【存在59秒的时候发起的请求,服务器接收后时间过了】- 这样使得请求的Hash校验有一定的时效安全
对称加密
- 需要对加密和解密使用相同密钥的加密算法。由于其速度快,对称性加密通常在消息发送方需要加密大量数据时使用。对称性加密也称为密钥加密。
- 所谓对称,就是采用这种加密方法的双方使用方式用同样的密钥进行加密和解密。密钥是控制加密及解密过程的指令。算法是一组规则,规定如何进行加密和解密。
- 因此加密的安全性不仅取决于加密算法本身,密钥管理的安全性更是重要。因为加密和解密都使用同一个密钥,如何把密钥安全地传递到解密者手上就成了必须要解决的问题。
常见算法
常用的单向加密算法:
- DES(Data Encryption Standard):数据加密标准,速度较快,适合用于加密大量数据
- 3DES(Triple DES):是基于DES,对一块数据用3个不同的密钥进行3次加密,强化了安全性
- AES(Advanced Encryption Standard):高级加密标准,速度快,安全级别高,支持128,192,256,512位密钥的加密
算法特征
- 加密方和解密方使用同一个密钥;
- 加密解密的速度比较快,适合数据比较长时的使用;
- 密钥传输的过程不安全,且容易被破解,密钥管理也比较麻烦;
优缺点
优点:算法公开,计算量小,加密速度快,加密效率高
缺点:
- 在数据传递钱,发送发和接收方需要商定好密钥,然后保存好密钥。
- 如果有一方的密钥泄露,那么信息就不安全了。
- 对于安全性,每对用户拥有自己的独立密钥,这使得收发双方会拥有大量的密钥,让管理成为负担。
AES应用
- ECB(Electronic Code Book):电子密码本模式。
- 每块数据,独立加密(待处理的信息被分成大小合适的分组,然后分别对每一分组独立进行加密或解密处理)。
- 这是最基本的加密模式,相同的明文将永远加密成相同的密文,无初始化向量,容易受到密码本重放攻击,一般情况下用于小数据量的字符信息的安全性保护,例如密钥保护。
- CBC(Cipher Block Chaining):密码分组链接模式。
- 使用一个密钥和一个初始化向量[IV]对数据进行加密。
- 明文被加密前要与前面的密文进行异或运算后在加密,因此只要选择不同的初始化向量,相同的明文加密后不会出现相同的密文,是目前使用最广泛的模式。
- CBC可以有效的保证密文的完整性,如果一块数据块在传递中丢失或者改变,后续的数据将无法正常的解密。
OpenSSL 终端指令
加密
- AES(ECB)加密字符串
$ echo -n string | openssl enc - aes-128-ecb -K 616263 -nosalt | base64- AES(CBC)加密字符串
$ echo -n string | openssl enc -aes-128-cbc -iv 0102030405060708 -K 616263 -nosalt | base64
解密
- AES(ECB)解密
$ echo -n 需要解密的base64 | base64 -D | openssl enc -aes-128-ecb -K 616263 -nosalt -d- AES(CBC)解密
$ echo -n 需要解密的base64 | base64 -D | openssl enc -aes-128-cbc -iv 0102030405060708 -K 616263 -nosalt -d
CCCrypt函数的使用
- import <CommonCrypto/CommonCrypto.h>
- 加密解密的算法都是使用到CCCrypt这个函数
Parameter:
CCOperation op > KCCEncrypt加密 / KCCDecrypt 解密
CCAlgorithm alg > 加密算法【kCCAlgorithm 枚举】
CCOptions options > 加密选项:ECB/CBC
const void *key > 加密的密钥
size_t keyLength > 密钥的长度
const void *iv > iv 初始化向量
const void *dataIn > 要被加密的数据
size_t dataInLength > 要被加密数据的长度
void *dataOut > 密文的内存地址
size_t dataOutAvailable > 密文缓冲区的大小
size_t *dataOutMoved > 解密结果大小
- (NSString *)encryptString:(NSString *)string keyString:(NSString *)keyString iv:(NSData *)iv {
// 设置秘钥
NSData *keyData = [keyString dataUsingEncoding:NSUTF8StringEncoding];
uint8_t cKey[self.keySize];
bzero(cKey, sizeof(cKey));
[keyData getBytes:cKey length:self.keySize];
// 设置iv
uint8_t cIv[self.blockSize];
bzero(cIv, self.blockSize);
int option = 0;
if (iv) {
[iv getBytes:cIv length:self.blockSize];
option = kCCOptionPKCS7Padding;
} else {
option = kCCOptionPKCS7Padding | kCCOptionECBMode;
}
// 设置输出缓冲区
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
size_t bufferSize = [data length] + self.blockSize;
void *buffer = malloc(bufferSize);
// 开始加密
size_t encryptedSize = 0;
//加密解密都是它 -- CCCrypt
CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,
self.algorithm,
option,
cKey,
self.keySize,
cIv,
[data bytes],
[data length],
buffer,
bufferSize,
&encryptedSize);
NSData *result = nil;
if (cryptStatus == kCCSuccess) {
result = [NSData dataWithBytesNoCopy:buffer length:encryptedSize];
} else {
free(buffer);
NSLog(@"[错误] 加密失败|状态编码: %d", cryptStatus);
}
return [result base64EncodedStringWithOptions:0];
}
- (NSString *)decryptString:(NSString *)string keyString:(NSString *)keyString iv:(NSData *)iv {
// 设置秘钥
NSData *keyData = [keyString dataUsingEncoding:NSUTF8StringEncoding];
uint8_t cKey[self.keySize];
bzero(cKey, sizeof(cKey));
[keyData getBytes:cKey length:self.keySize];
// 设置iv
uint8_t cIv[self.blockSize];
bzero(cIv, self.blockSize);
int option = 0;
if (iv) {
[iv getBytes:cIv length:self.blockSize];
option = kCCOptionPKCS7Padding;
} else {
option = kCCOptionPKCS7Padding | kCCOptionECBMode;
}
// 设置输出缓冲区
NSData *data = [[NSData alloc] initWithBase64EncodedString:string options:0];
size_t bufferSize = [data length] + self.blockSize;
void *buffer = malloc(bufferSize);
// 开始解密
size_t decryptedSize = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,
self.algorithm,
option,
cKey,
self.keySize,
cIv,
[data bytes],
[data length],
buffer,
bufferSize,
&decryptedSize);
NSData *result = nil;
if (cryptStatus == kCCSuccess) {
result = [NSData dataWithBytesNoCopy:buffer length:decryptedSize];
} else {
free(buffer);
NSLog(@"[错误] 解密失败|状态编码: %d", cryptStatus);
}
return [[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding];
}