阅读 313

iOS逆向攻防之HASH,数字签名,对称加密算法

在密码学中,加密部分主要分为对称加密非对称加密非对称加密主要有RSA非对称加密(使用公钥/私钥来加密解密),对称加密主要有DES/3DES/AES对称加密算法,顺带提一下我们今天介绍的Hash算法,Hash属于一种消息摘要算法,不属于加密算法,但是由于其单向运算,不可逆性,所以Hash是加密算法中的构成部分,Hash算法主要有MD5/Sha1/Sha2,这几个只是Hash算法加密精度有所不同。

那么紧接之前的非对称加密RSA,直接上这次的干货部分

1、Hash概述
2、数字签名
3、对称加密算法简介
4、对称加密算法终端命令
    5、对称加密算法终端演练
6、对称加密算法代码演练
7、CCCrypt函数
复制代码

一、Hash概述


1、Hash的概念

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

2、Hash特点

1、算法是公开的
2、对相同的数据运算,得到的结果是一样的
3、对于不同的数据运算,如MD5(Hash算法的一种)的到的结果默认是128位的,32个字符(16进制标识)
4、没法逆运算(因为哈希值是映射关系,会存在散列碰撞【即:无限个数据加密得到有限个数据,就存在一个或许多个数据存在同样的哈希值】)
5、信息摘要,信息’指纹‘,一般是用来做数据识别的(由于没法做逆运算,所以一般不会用来做加密数据,只是把数据的哈希值取到,然后用来对比,做数据识别的)

复制代码

3、Hash函数(单向散列函数)

1、MD5(Message Digest Algorithm 5)
2、SHA(Secure Hash Algorithm)
   SHA又分为:
   SHA-1
   SHA-2系列(224,256,384,512,512/224,512/256统称为SHA-2系列)
3、MAC(Message Authentication Code)
4、CRC(Cyclic Redundancy Check)
5、SM3(国产哈希算法)

复制代码

4、Hash用途

1、用户密码的加密
2、搜索引擎
3、版权
4、数字签名(应用签名)

复制代码

5、HMAC

什么是HMAC?HMAC(Hash-based message authentication code)是一种使用Hash函数(单向散列函数)来构造消息认证码的方法,利用哈希算法,以一个密钥和一个消息为输入,生成一个消息摘要作为输出。主要是为了能让人对对方身份正确性和消息有效性进行验证,与消息摘要的最大不同,就是有签名密钥!

HMAC通过两次hash两个不同的key来生成。 目前还没有发现有任何的方法来产生碰撞。

HMAC中所使用的单向散列函数并不仅限于一种,任何高强度的Hash函数(单向散列函数)都可以被用于HMAC。 比如使用SHA-1、SHA-224、SHA-256、SHA-384、SHA-512所构造的HMAC,分别称为HMAC-SHA1、HMAC-SHA-224、HMAC-SHA-384、HMAC-SHA-512。

二、数字签名


1、什么是数字签名

数字签名就是用于鉴别数字信息的方法;

2、数字签名

下面我们以电商支付金额这个场景来描述数字签名的具体意义:

数字签名理解示例.png

图示中,经过RSA加密的原商品信息Hash值这个整体就叫做数字签名。

数字签名的概念.png

三、对称加密概述


1、对称加密算法定义:

对称加密方式:就是明文通过密钥加密得到密文。密文通过密钥解密得到明文。

2、对称加密常见算法

1、DES(Data Encryption Standard):数据加密标准,速度较快,适用于加密大量数据的场合;(题外话:实际上用的不多,因为加密强度不够)
2、3DES(Triple DES):是基于DES的对称算法,对相同的数据用3个不同的密钥执行3次加密,强度更高;(题外话:不过因为3个密钥管理起来麻烦,所以一般不是很常用~一出生就挂掉了,很惨。。。)
3、RC2和RC4:用变长密钥对大量数据进行加密,比DES快哦~
4、AES(Advanced Encryption Standard):高级加密标准,是下一代的加密算法标准,速度快,安全级别高,在21世纪AES标准的一个实现是Rijndael算法;(题外话:很安全,苹果的钥匙串访问就是用的AES,美国国家安全局也是用的AES,想要暴力破解基本不可能)

复制代码

3、对称加密应用模式 对称加密主要有两种应用模式,下面来详细介绍一下

ECB(Electronic Code Book):电子密码本模式。每一块数据, 独立加密。

ECB是最基本的加密方式,也就是通常理解的加密,相同的明文将永远加密成相同的密文,无初始向量,容易受到密码本重放攻击,一般情况下很少用。

CBC(Cipher Block Chaining):密码分组链接模式。使用一个密钥和一个初始化向量(IV)对数据进行加密。

CBC加密方式,明文被加密前要与前面的密文进行异或运算后再加密,因此只要选择不同的初始向量,相同的密文加密后会形成不同的密文,这是目前应用最广泛的模式。CBC加密后的密文是上下文相关的,但明文的错误不会传递到后续分组,但如果一个分组丢失,后面的分组将全部作废(同步错误).

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

四、对称加密算法终端命令


AES对称加密算法两种应用模式下的终端命令分别如下: 1、AES(ECB)的加密与解密

AES(ECB)加密'battleMage'字符串

$ echo -n battleMage | openssl enc -aes-128-ecb -K 616263 -nosalt | base64

复制代码

AES(ECB)解密'battleMage'字符串

$ echo -n kXcE5nnetsinAMBEcK6D5g== | base64 -D | openssl enc -aes-128-ecb -K 616263 -nosalt -d

复制代码

2、AES(CBC)的加密与解密

AES(CBC)加密'battleMage'字符串

$ echo -n battleMage | openssl enc -aes-128-cbc -iv 0102030405060708 -K 616263 -nosalt | base64

复制代码

AES(CBC)解密'battleMage'字符串

$ echo -n H3tn3dXCEtKNvijJYLsStw== | base64 -D | openssl enc -aes-128-cbc -iv 0102030405060708 -K 616263 -nosalt -d

复制代码

五、对称加密算法终端演练对比


1、新建一个message.txt文本文件

$ vi message.txt

复制代码

回车进入编辑界面,点击'i',进入编辑界面,输入5排'1234567890',点击'esc',再点击'shift+:',输入'wq'回车保存。

message.txt文件.png

2、对该'message.txt'文件直接使用AES(ECB)进行加密,然后输出一个'meg1.bin'文件

$ openssl enc -des-ecb -K 616263 -nosalt -in message.txt -out meg1.bin

复制代码

直接敲回车,得到一个 meg1.bin 的文件

然后直接修改message.txt文件,把最后一排的第一个1改成2,

AES(ECB)修改之后再次加密.png

再次使用上述命令进行加密,然后输出一个'meg2.bin'文件

$ openssl enc -des-ecb -K 616263 -nosalt -in message.txt -out meg2.bin

复制代码

直接敲回车,得到一个 meg2.bin 的文件

接下来使用xxd命令查看meg1.bin 和 meg2.bin文件

同样通过AES(CBC)加密'message.txt'并输出一个‘meg3.bin’文件

$ openssl enc -aes-128-cbc -iv 0102030405060708 -K 616263 -nosalt -in message.txt -out meg3.bin

复制代码

再次手动编辑message.txt文件,把message.txt还原,然后通过AES(CBC)加密并输出一个‘meg4.bin’文件

$ openssl enc -aes-128-cbc -iv 0102030405060708 -K 616263 -nosalt -in message.txt -out meg4.bin
复制代码

对比如下图,

AES两种方式ECB和CBC的区别.png

六、对称加密算法代码演练


接下来开始代码演练部分,需要导入一个工具类,工具类代码并不多,这里直接贴工具类的内容吧,工具类头文件AES,DES各种终端命令也都包含在内了:

.h文件

#import <Foundation/Foundation.h>
#import <CommonCrypto/CommonCrypto.h>

/**
 *  终端测试指令
 *
 *  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解码,再解密
 */
@interface EncryptionTools : NSObject
    
+ (instancetype)sharedEncryptionTools;
    
    /**
     @constant   kCCAlgorithmAES     高级加密标准,128位(默认)
     @constant   kCCAlgorithmDES     数据加密标准
     */
    @property (nonatomic, assign) uint32_t algorithm;
    
    /**
     *  加密字符串并返回base64编码字符串
     *
     *  @param string    要加密的字符串
     *  @param keyString 加密密钥
     *  @param iv        初始化向量(8个字节)
     *
     *  @return 返回加密后的base64编码字符串
     */
- (NSString *)encryptString:(NSString *)string keyString:(NSString *)keyString iv:(NSData *)iv;
    
    /**
     *  解密字符串
     *
     *  @param string    加密并base64编码后的字符串
     *  @param keyString 解密密钥
     *  @param iv        初始化向量(8个字节)
     *
     *  @return 返回解密后的字符串
     */
- (NSString *)decryptString:(NSString *)string keyString:(NSString *)keyString iv:(NSData *)iv;
    
    @end


复制代码

.m文件

#import "EncryptionTools.h"

@interface EncryptionTools()
    @property (nonatomic, assign) int keySize;
    @property (nonatomic, assign) int blockSize;
    @end

@implementation EncryptionTools
    
+ (instancetype)sharedEncryptionTools {
    static EncryptionTools *instance;
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
        instance.algorithm = kCCAlgorithmAES;
    });
    
    return instance;
}
    
- (void)setAlgorithm:(uint32_t)algorithm {
    _algorithm = algorithm;
    switch (algorithm) {
        case kCCAlgorithmAES:
        self.keySize = kCCKeySizeAES128;
        self.blockSize = kCCBlockSizeAES128;
        break;
        case kCCAlgorithmDES:
        self.keySize = kCCKeySizeDES;
        self.blockSize = kCCBlockSizeDES;
        break;
        default:
        break;
    }
}
    
- (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];
}
    
    @end

复制代码

接下来新建工程,把工具类.h,.m拖入工程,在ViewController.m实现touchBegin方法

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSString * key = @"abc";
    uint8_t iv[8] = {1,2,3,4,5,6,7,8};
    
    //备注:选择的是AES(ECB), 初始向量iv直接传nil,如果选择 AES(CBC),初始向量iv需要传值,具体可见工具类.m文件中实现,是判断iv是否为nil来选取加密方式的
    //1、选择AES(ECB)
    NSString * encStr =  [[EncryptionTools sharedEncryptionTools]encryptString:@"hello" keyString:key iv:nil];
    
    NSLog(@"AES(ECB)加密的结果是:%@", encStr);
    NSLog(@"AES(ECB)解密的结果是:%@", [[EncryptionTools sharedEncryptionTools]decryptString:encStr keyString:key iv:nil]);
    
    //2、选择AES(CBC)
    NSData * ivData = [NSData dataWithBytes:iv length:sizeof(iv)];
    NSString * encStr1 =  [[EncryptionTools sharedEncryptionTools]encryptString:@"hello" keyString:key iv:ivData];
    
    NSLog(@"AES(CBC)加密的结果是:%@", encStr1);
    NSLog(@"AES(CBC)解密的结果是:%@", [[EncryptionTools sharedEncryptionTools]decryptString:encStr1 keyString:key iv:ivData]);
    
}


@end


复制代码

点击运行,运行结果OK

2019-10-12 21:57:26.858675+0800 CryptDemo[1790:115503] AES(ECB)加密的结果是:d1QG4T2tivoi0Kiu3NEmZQ==
2019-10-12 21:57:26.858896+0800 CryptDemo[1790:115503] AES(ECB)解密的结果是:hello
2019-10-12 21:57:26.859040+0800 CryptDemo[1790:115503] AES(CBC)加密的结果是:u3W/N816uzFpcg6pZ+kbdg==
2019-10-12 21:57:26.859194+0800 CryptDemo[1790:115503] AES(CBC)解密的结果是:hello

复制代码

七、CCCrypt函数


第六步已经使用过封装好的加密工具类EncryptionTools.h,这个工具类只是封装了CCCrypt函数,下面我们来研究一下加密工具的核心函数CCCrypt函数:

使用CCCrypt函数,需要引入系统库

#import <CommonCrypto/CommonCrypto.h>

复制代码

不管是加密还是解密都是使用这个函数,下面我们来介绍一下这个函数中的参数,参数解释我直接备注在API的后面,注意里面有坑!!!

CCCrypt函数

参数介绍
1、 CCOperation op :操作类型:加密or解密,枚举值;
    kCCEncrypt 代表加密
    kCCDecrypt 代表解密

2、 CCAlgorithm alg:加密算法,枚举值;
    kCCAlgorithmAES     高级加密标准,128位(默认)
    kCCAlgorithmDES     数据加密标准
    
3、 CCOptions options:加密应用模式,枚举值;
	注意注意!!!!!!!!!!这里有个坑;kCCOptionPKCS7Padding代表填充模式,这个options必须加上填充模式;
    CCCrypt的option默认是CBC,所以只需要补充一个填充模式就能代表CBC; 但是ECB就需要额外再加上一个kCCOptionECBMode,所以选择ECB就需要kCCOptionPKCS7Padding | kCCOptionECBMode;
    
    所以想要选择CBC和ECB,需要按下面进行填写!
	kCCOptionPKCS7Padding;  代表CBC
	kCCOptionPKCS7Padding | kCCOptionECBMode; 代表ECB

4、 const void *key :加密的密钥的指针
5、 size_t keyLength:密钥的长度
6、 const void *iv: 初始化向量
7、 const void *dataIn:加密的原始数据
8、 size_t dataInLength:加密的原始数据的长度
9、 void *dataOut:加密后密文的内存地址
10、size_t dataOutAvailable:加密后密文的缓冲区大小
11、size_t *dataOutMoved :加密结果的大小


CCCryptorStatus CCCrypt(
    CCOperation op,         /* kCCEncrypt, etc. */
    CCAlgorithm alg,        /* kCCAlgorithmAES128, etc. */
    CCOptions options,      /* kCCOptionPKCS7Padding, etc. */
    const void *key,
    size_t keyLength,
    const void *iv,         /* optional initialization vector */
    const void *dataIn,     /* optional per op and alg */
    size_t dataInLength,
    void *dataOut,          /* data RETURNED here */
    size_t dataOutAvailable,
    size_t *dataOutMoved)


复制代码

需要注意的是,直接使用这个函数安全隐患非常的大!因为这个函数是系统提供的,不管你是加密还是解密,都是调用了CCCrypt函数,而黑客可以通过越狱手机附加调试或者是非越狱手机重签调试,能够用函数断点断到你的CCCrypt函数,然后通过寄存器直接获取函数的对应参数,根据上面函数的介绍,我们的数据其实是其中的第七个参数'const void *dataIn', 第七个参数的下标为6, 然后调用汇编指令'x6',就可以拿到你的数据的地址,然后转一下类型,就能直接打印出你的加密数据!具体操作如下:

1、还是打开之前的工程,设置函数断点CCCrypt,然后使用真机运行!!!必须用真机,因为真机和模拟器的CPU不一样

1、符号断点设置为CCCrypt.png

2、运行工程,模拟黑客调试,然后点击屏幕出发touchBegin方法,然后断点停在了CCCrypt函数的地方

3、因为函数在调用的时候,都是存在CPU的寄存器上,输入寄存器查看指令

register read x6

复制代码

read x6是读取该函数对应的第7个参数,第一个参数是x0

4、拿到地址,然后强转类型,蒙圈了吧,你的数据就泄漏了

2、寄存器读取加密的数据.png

所以这个函数不能直接使用,现在只说基础,后面会详细说安全防护~今天就说到这里了~