一、加密算法分类

哈希(散列)函数:不属于加密算法。例如MD5、SHA1/256/512

对称加密算法:DES、3DES、AES(高级密码标准,mac电脑的钥匙串就是用AES)

非对称加密算法:RAS

1、Hash

Hash,一般翻译做“散列”,也有直接译为“哈希”的,就是把任意长度的输入通过散列算法变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值,简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数,最著名的一个hash算法就是MD5

Hash的特点:

  • 算法是公开的
  • 对相同数据运算,得到的结果是一样的
  • 对不同数据运算,如MD5得到的结果默认是128位,也就是32个字符(16进制标识)
  • 无法逆运算
  • 主要作为信息摘要,信息指纹,用来做数据识别的

用途:

  • 用户密码的加密:因为无法还原,所以加密性很好,哪怕泄露了,也午饭逆向出用户的真实密码
  • 搜索引擎
  • 版权
  • 数字签名

密码加密的方式:

  • 直接使用MD5
  • MD5加盐
  • HMAC加密方案
  • 添点东西

当我们需要把密码发送给服务器的时候我们要对密码进行加密,假如我们用RSA非对称加密的话,效率会很低,并且有安全隐患,因为当通过RSA加密后会对密码进行解密,然后匹配,但是这样的话服务器就需要明文存储密码,这是很不安全的:

对于密码在网络上传输有了两个原则:

  • 网络上不允许明文传递用户的隐私信息
  • 本地不允许明文保存用户隐私信息

所以可以用MD5转换,这样即使密文丢失了但是用户的原始密码是是无法破解的,我们来看一下MD5转换代码:

- (NSString *)md5String {
    const char *str = self.UTF8String;
    uint8_t buffer[CC_MD5_DIGEST_LENGTH];
    //将字符串指针、字符串的长度、以及密文存储的空间传进去,空间一般是16位,加密后会把密文以16进制形式放在待存储的空间中,
    CC_MD5(str, (CC_LONG)strlen(str), buffer);
    //拿到密文后,我们将密文拼接起来就得到一个密文字符串
    return [self stringFromBytes:buffer length:CC_MD5_DIGEST_LENGTH];
}

- (NSString *)stringFromBytes:(uint8_t *)bytes length:(int)length {
    NSMutableString *strM = [NSMutableString string];

    for (int i = 0; i < length; i++) {
        [strM appendFormat:@"%02x", bytes[i]];
    }
    return [strM copy];
}

我们对123456进行加密:就得到这样一个十六进制的字符串
e10adc3949ba59abbe56e057f20f883e

但是现在MD5虽然无法破解,但是因为同一字符串对应的密文是不变的,这样我们就可以通过密文匹配到相应的字符串,例如在CMD5上,我们就能通过密码匹配到常见的字符串

这个时候我们就需要对字符串加盐后在进行MD5转换,所谓加盐就是在字符串后拼接一个复杂字符串后再进行MD5转换,但是也有个弊端,就是这个盐是固定的必须要写到程序里面,一样可以通过暴力破解,所以我们可以通过HMAC方式进行加密:

HMAC:

  • 使用一个密钥加密,并且做两次散列
  • 实际开发中,密钥来自于服务器,动态的
  • 一个账号对应一个key,而且还可以更新 我们在注册或者换设备时候将key下发给客户端,然后在本地缓存起来,然后以后每次登陆时候从本地取出来对密码进行加密,这样我们就可以加上一个设备锁,当换设备时候需要原来设备同意才能在新设备上登陆
    这里有一个iOS交流圈:891 488 181 不管你是大牛还是小白都欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!
    苹果也提供了CCHmac的方式:
#pragma mark - HMAC 散列函数
- (NSString *)hmacMD5StringWithKey:(NSString *)key {
    const char *keyData = key.UTF8String;
    const char *strData = self.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];
}

因为每次登陆假如我们只用MD5密文的话,就会导致每次登陆传给服务器的信息都是一样的,这样就比较危险,如果被黑客劫持了,就会在后面时间用请求数据去破解,那我们可以加一个时间戳(服务器时间),这样每次请求都不一样,即使黑客劫持了也无所谓,因为有延迟问题,所以时间戳只精确到分钟,加时间戳方式:

  • 1、登录之前先从服务器请求到服务器的时间戳time
  • 2、然后在客户端通过HMAC和key对密码进行加密得到密文1
  • 3、将密文1拼接上时间戳再进行一次HMAC和key的加密得到密文2
  • 4、将密文2传给服务器进行校验,服务器从本地取出用户的密码密文也就是密文1拼接上当前时间戳后再进行HMAC和key的加密跟客户端传过来的进行匹配,相同则校验通过
  • 如果不同,则将密文1和服务器时间戳的前一分钟的时间戳拼接后在进行匹配,相同则通过,不同则返回密码不正确

搜索引擎:

对每个词都拿到Hash值,然后将这些Hash值进行相加,然后进行查找,这样不管你输入的词顺序是如何的,那么查找到内容都是整句话的,例如 张三 李四 王五,这三个词的hash值都是一样的,并且都是32位的16进制数字,不管他们哪个在前,加起来的和都是一样的

版权:

对原文件通过Hash运算得到一个Hash值,源文件里面有一个Hash值,假如有盗版文件上传到百度云盘上会因为Hash值不对,并且有百度云盘有存在秒传情况,这是因为云盘已经有了该文件。通过匹配Hash发现该文件已经有了,所以就不用再次上传

数字签名:

为啥用签名这个词呢,因为老外喜欢用支票,支票上的签名能够证明这玩意是你的,那么数字签名就是用于鉴别数字信息的方法

客户端有个支付信息,三方可能从中间攻击,篡改支付信息,这样就可能让客户多支付钱,怎么防止呢?

  • 可以对支付的信息转成MD5
  • 在对MD5密文进行RSA加密得到一个密文,这个就是数字签名
  • 传递给服务器后,服务器对密文进行RSA解密得到MD5密文1,再对支付信息转成MD5得到密文2,然后密文1和密文2进行匹配,如果一样,说明支付信息没有被修改

因为hash是无论多大的数得到的都是128位的数,所有肯定有些数据得到的hash值是一样的,这个就叫做散列碰撞

2、对称加密:

对称加密就是明文通过密钥能得到密文,密文通过密钥解密能得到明文

常见算法:

  • DES:数据加密标准(用得少,因为强度不够)
  • 3DES:用三个密钥对相同数据执行3次加密,强度增强,但是有3个密钥比较麻烦,所有用的比较少
  • AES:高级密码标准,钥匙串访问就是使用这个,FBI加密用的也是这个

异或一般用于游戏数据的常规加密

应用模式:

  • ECB:电子密码本模式,每一块数据独立加密。最基本的加密模式,也就是通常理解的加密,相同的明文将永远加密成相同的密文,无初始向量,因为每一块数据都是单独的,所以容易受到密码本重放攻击,一般情况很少用

  • CBC:密码分组链接模式,使用一个密钥和一个初始化向量(IV)对数据执行加密,明文被加密前要与前面的密文进行异或或运算后再加密,因此只要选择不同的初始向量,相同的密码加密后会形成不同的密文,这是目前应用最广泛的模式。CBC加密后的密文是上下文相关的,但明文的错误不会传递到后续分组,但是如果一个分组丢失,后面的分组将全部作废,也就是数据都是相关联的,后面一块数据的加解密依赖于前一块数据,如果其中一块数据遭到破坏,那么整个数据都无法解析

  • CBC可以有效的保证密文的完整性,如果一个数据块在传递时丢失或者改变了,后面的数据将无法正常解密

通过终端命令加密,我们会发现 介绍下AES的终端加解密命令:

/**
 *  终端测试指令
 *
 *  DES(ECB)加密
 *  $ echo -n hello | openssl enc -des-ecb -K 616263 -nosalt | base64
 *
 * DES(CBC)加密
 *  $ echo -n hello | openssl enc -des-cbc -iv 0102030405060708 -K 616263 -nosalt | base64
 *
 *  AES(ECB)加密
 *  $ echo -n hello | openssl enc -aes-128-ecb -K 616263 -nosalt | base64
 *
 *  AES(CBC)加密
 *  $ echo -n hello | openssl enc -aes-128-cbc -iv 0102030405060708 -K 616263 -nosalt | base64
 *
 *  DES(ECB)解密
 *  $ echo -n HQr0Oij2kbo= | base64 -D | openssl enc -des-ecb -K 616263 -nosalt -d
 *
 *  DES(CBC)解密
 *  $ echo -n alvrvb3Gz88= | base64 -D | openssl enc -des-cbc -iv 0102030405060708 -K 616263 -nosalt -d
 *
 *  AES(ECB)解密
 *  $ echo -n d1QG4T2tivoi0Kiu3NEmZQ== | base64 -D | openssl enc -aes-128-ecb -K 616263 -nosalt -d
 *
 *  AES(CBC)解密
 *  $ echo -n u3W/N816uzFpcg6pZ+kbdg== | base64 -D | openssl enc -aes-128-cbc -iv 0102030405060708 -K 616263 -nosalt -d
 *
 *  提示:
 *      1> 加密过程是先加密,再base64编码
 *      2> 解密过程是先base64解码,再解密
 */

加密代码:

加解密其实用的都是CCCrypt函数,只是传入的类型不一样

    /** AES - ECB */
    NSString * key = @"abc";
    NSString * encStr = [[EncryptionTools sharedEncryptionTools] encryptString:@"hello" keyString:key iv:nil];

    NSLog(@"加密的结果是:%@",encStr);

    NSLog(@"解密的结果是:%@",[[EncryptionTools sharedEncryptionTools] decryptString:encStr keyString:key iv:nil]);

    /** AES - CBC 加密 */
    uint8_t iv[8] = {1,2,3,4,5,6,7,8};
    NSData * ivData = [NSData dataWithBytes:iv length:sizeof(iv)];

    NSLog(@"CBC加密:%@",[[EncryptionTools sharedEncryptionTools] encryptString:@"hello" keyString:@"abc" iv:ivData]);

    NSLog(@"解密:%@",[[EncryptionTools sharedEncryptionTools] decryptString:@"u3W/N816uzFpcg6pZ+kbdg==" keyString:key iv:ivData]);

    - (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;//CBC 加密!
    } else {
        option = kCCOptionPKCS7Padding | kCCOptionECBMode;//ECB加密!
    }

    // 设置输出缓冲区
    NSData *data = [[NSData alloc] initWithBase64EncodedString:string options:0];
    size_t bufferSize = [data length] + self.blockSize;
    void *buffer = malloc(bufferSize);

    // 开始解密
    size_t decryptedSize = 0;
    /**CCCrypt 对称加密算法的核心函数(加密/解密)
     参数:
     1、kCCEncrypt 加密/kCCDecrypt 解密
     2、加密算法、默认的 AES/DES
     3、加密方式的选项
        kCCOptionPKCS7Padding | kCCOptionECBMode;//ECB加密!
        kCCOptionPKCS7Padding;//CBC 加密!
     4、加密密钥
     5、密钥长度
     6、iv 初始化向量,ECB 不需要指定
     7、加密的数据
     8、加密的数据长度
     9、缓冲区(地址),存放密文的
     10、缓冲区的大小
     11、加密结果大小
     */
    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];
}

3、加密算法存在的问题

像我们加密会直接将要加密的字符串转成NSData后丢给系统的加密函数,但是对于黑客来说,因为加密的函数是系统的函数,所以黑客可以通过hook系统的函数来拿到将要加密的数据,这样很不安全

我们可以通过符号断点拦截到系统函数CCCrypt,因为加密的数据在第七个参数,所以我们可以看一下寄存器x6的数据

我们就可以直接拿到加密的原数据,那怎么保护呢,我们可以针对这个漏洞去防护

所以我们加密的数据不要直接使用,可以做一下处理,例如游戏数据很多通过异或的方式处理下数据,因为黑客破解多数是通过看程序调了哪些方法去分析,基本没有去一行代码一行代码的去分析

作者:海浪宝宝
链接:https://juejin.cn/post/6844904112073474061