相关文献

高级加密模式之AES工作原理

iOS AES/CBC/PKCS7Padding加密、解密问题

加密解密工具

iOS Int类型转换成NSData

AES补位填充模式

数据加解之AES篇


前言(摘录自上面文章)

        最近在重构之前写的HTTP代理,这个代理是由代理客户端和代理服务端组成的,二者之前使用SSL保证通信内容不会受到中间人(MITM)攻击。而新的实现打算移除SSL,因为SSL握手的开销过大,尤其是客户端与服务端之间隔了个太平洋,另一方面本月中旬的时候Google安全团队证明了SSLv3已经是不安全的了,需要升级到TLS,但TLS同样有握手的开销。在新的实现中客户端和服务端之间的通信将使用AES加密,每个连接使用独立的随机生成的密钥和初始化向量。客户端在向服务端发起连接后使用非对称加密算法RSA将密钥和初始化向量加密后发送给服务端,服务端在收到密钥和初始化向量后就全部使用AES加密通信,这保证了通信内容不会被窃听(但可能被篡改)。


NSData扩展AES的加解密

.h文件

#import <Foundation/Foundation.h>

@interface NSData (AES)

//加密
- (NSData *)AES128EncryptWithKey:(NSString *)key iv:(NSString *)iv;

//解密
- (NSData *)AES128DecryptWithKey:(NSString *)key iv:(NSString *)iv;

@end

.m文件

#import "NSData+AES.h"
#import <CommonCrypto/CommonCryptor.h>

@implementation NSData (AES)

//加密
- (NSData *)AES128EncryptWithKey:(NSString *)key iv:(NSString *)iv
{
    return [self AES128operation:kCCEncrypt key:key iv:iv];
}

//解密
- (NSData *)AES128DecryptWithKey:(NSString *)key iv:(NSString *)iv
{
    return [self AES128operation:kCCDecrypt key:key iv:iv];
}

- (NSData *)AES128operation:(CCOperation)operation key:(NSString *)key iv:(NSString *)iv
{
    char keyPtr[kCCKeySizeAES128 + 1];
    bzero(keyPtr, sizeof(keyPtr));
    [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
    
    // IV
    char ivPtr[kCCBlockSizeAES128 + 1];
    bzero(ivPtr, sizeof(ivPtr));
    [iv getCString:ivPtr maxLength:sizeof(ivPtr) encoding:NSUTF8StringEncoding];
    
    size_t bufferSize = [self length] + kCCBlockSizeAES128;
    void *buffer = malloc(bufferSize);
    size_t numBytesEncrypted = 0;
    
    
    CCCryptorStatus cryptorStatus = CCCrypt(operation, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
                                            keyPtr, kCCKeySizeAES128,
                                            ivPtr,
                                            [self bytes], [self length],
                                            buffer, bufferSize,
                                            &numBytesEncrypted);
    
    if(cryptorStatus == kCCSuccess){
        NSLog(@"Success");
        return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
    }else{
        NSLog(@"Error");
    }
    
    free(buffer);
    return nil;
}

@end


解密例子


要加密的字符:{"body":"aaa","service":"test"} // 最后得到的东西
key:664c4a6b60491e4d2371893479f2758e // md5
x.s.net.dto.MessageDTO@68ceda24[
  data=YYMandP9Mb/U+UZQ5++Trel57rrSJO6a16rNtr3j6S09xFKcw3WrWpZ6lWuigXJ2fHkm05CtZRR3zmU72BUs1w==
  timestamp=1519711287
  nonce=2x18OyzMUmyarlAj
  signature=e93a7eda7c5a5a8b2cb9fdc954894c965f600473
]

解密顺序

1.signature校验

NSString *data = [NSString stringWithFormat:@"%@",response[@"data"]];
    NSString *nonce = [NSString stringWithFormat:@"%@",response[@"nonce"]];
    NSString *signature = [NSString stringWithFormat:@"%@",response[@"signature"]];
    NSString *timestamp = [NSString stringWithFormat:@"%@",response[@"timestamp"]];
    
    
    NSMutableArray *arr = [[NSMutableArray alloc] init];
    [arr addObject:data];
    [arr addObject:nonce];
    [arr addObject:timestamp];
    [arr addObject:@"mkjtoken"];
    [arr sortUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {
        return [obj1 compare:obj2];
    }];
    NSString *shaStr = [arr componentsJoinedByString:@""];
    NSString *sha111 = [MKJEncryptAndDecrypt sha1:shaStr];
    NSLog(@"~~~~~~~~~~~~%@~~~~~~~~~~~~",sha111);

结果:e93a7eda7c5a5a8b2cb9fdc954894c965f600473

通过data,nonce,timestamp和协定好的字符串例如@"mkjtoken" 组成字符串 sha1和signature比较,校验成功没有被篡改继续下一步骤


2.AES解密

NSString *dataStr = data;
    NSData *originData = [self base64Decode:dataStr];
    
    NSString *key = [NSString stringWithFormat:@"%@%@",@"mkj",timestamp];
    NSString *md5Str = [[MKJEncryptAndDecrypt md5:key] substringToIndex:16];
    NSLog(@"md5--->%@",md5Str);
    NSData *result = [originData AES128DecryptWithKey:md5Str iv:md5Str];
    NSLog(@"解密前数据块%@",result);

结果:<32783138 4f797a4d 556d7961 726c416a 0000001f 7b22626f 6479223a 22616161 222c2273 65727669 6365223a 22746573 74227d6d 6b6a6170 704964> 该数据没有补位符

// 去掉AES之后的补位
+ (NSData *)deCodeData:(NSData *)data{
    Byte *bytes = (Byte *)data.bytes;
    int pad = (int)bytes[data.length - 1];
    if (pad < 1 || pad > 32) {
        pad = 0;
    }
    return [data subdataWithRange:NSMakeRange(0, data.length - pad)];
}
NSData *noCoverResult = [self deCodeData:result];
    NSLog(@"去掉补位数据块%@",noCoverResult)
// 计算头字节里面的正文字节长度
+ (NSInteger)revertLength:(NSData *)datas{
    NSInteger sourceNumber = 0;
    Byte *bytes = (Byte *)[datas bytes];
    for (NSInteger i = 0; i < 4; i++) {
        sourceNumber <<= 8;
        sourceNumber |= bytes[i] & 0xff;
    }
    return sourceNumber;
}
NSInteger lengthOrigin = [self revertLength:[noCoverResult subdataWithRange:NSMakeRange(16, 4)]];
    NSLog(@"原始数据长度%ld",lengthOrigin);

结果:0000001f  -> 31

// <7b22626f 6479223a 22616161 222c2273 65727669 6365223a 22746573 74227d>
    NSData *originData = [result subdataWithRange:NSMakeRange(20, lengthOrigin)];
    
    // {"body":"aaa","service":"test"}
    NSString *originStr = [[NSString alloc] initWithData:originData encoding:NSUTF8StringEncoding];
    
    // <6d6b6a61 70704964>
    NSData *tailData = [result subdataWithRange:NSMakeRange(20 + lengthOrigin, result.length - 20 - lengthOrigin)];
    
    // mkjappId 这个字段是需要进行比对再操作的,这里就没写那么严谨了,只是一个简单的Demo解密
    NSString *tailStr = [[NSString alloc] initWithData:tailData encoding:NSUTF8StringEncoding];




>1 先对后台返回的数据进行base64Decode

>2 AES的key是md5 截取前16位

>3 AES解密出后台包装好的二进制数据

>4 由于AES是会自动填充128位进行加密的,因此加密之后是会有占位符的,具体操作可以看头部的文献,所以解密之后的占位符要先去掉(后台协定前面20字节的最后4个字节代表原始数据的长度,剩下的最后字节就是固定字符串,需要进行校验)

>5 分离前20位的字节,取16-20来区间原始数据

>6 取剩下的尾部appId进行校验  取appid=20+dataLength - 总字符长度,验证appid

>7 解析取data=20-(20+dataLength)


结尾
后台的业务是这样的,我们只是根据这个业务进行解密,记录下基本解密逻辑,除了https之外,还可以在http上面通过RSA+AES的方式进行数据安全传输,也算是对知识的一个回顾,下面还有个处理更简单的版本


点击打开链接