iOS与PHP/Android AES128 ECB NoPadding加密
谈谈AES加密,网上有很多的版本,当我没有真正在加密安全问题前,总以为百度出来某个AES加密算法就可以直接使用,实际上当我真正要做加密时,遇到了很多的坑,原来不是拿过来就能用的。写下本篇文章,记录下曾经遇到的坑,严防以后再出现同样的坑。
AES规则
原输入数据不够16字节的整数位时,就要补齐。因此就会有padding,若使用不同的padding,那么加密出来的结果也会不一样。
AES加密算法
苹果提供给我们的API只有这一个函数用来加密或者解密:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | ( /* kCCEncrypt, etc. */ /* kCCAlgorithmAES128, etc. */ /* kCCOptionPKCS7Padding, etc. */ , , /* optional initialization vector */ /* optional per op and alg */ , /* data RETURNED here */ , ) ; |
- 其中第一个
CCOperation
只有两个值,要么是kCCEncrypt
表示加密,要么是kCCDecrypt
表示解密。 - 第二个参数表示加密的算法,它只有以下向种类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 | { , , , , ; ; |
我们这里使用的是kCCAlgorithmAES128
表示使用AES128位加密。
- 第三个参数表示选项,这里使用的是
kCCOptionECBMode
,表示ECB:
1 2 3 4 5 6 7 8 9 | { /* options for block ciphers */ , 0x0002 /* stream ciphers currently have no options */ ; ; |
- 第四个参数表示加密/解密的密钥。
- 第五个参数keyLength表示密钥的长度。
- 第六个参数iv是个固定值,通过直接使用密钥即可。大家一定要注视这个参数,如果安卓、服务端和iOS端不统一,那么加密结果就会不一样,解密可能能解出来,但是解密后在末尾会出现一些\0、\t之类的。
- 第七个参数dataIn表示要加密/解密的数据。
- 第八个参数dataInLength表示要加密/解密的数据的长度。
- 第九个参数dataOut用于接收加密后/解密后的结果。
- 第十个参数dataOutAvailable表示加密后/解密后的数据的长度。
- 第十一个参数dataOutMoved表示实际加密/解密的数据的长度。(因为有补齐)
加密算法
依赖于第三方库:GTMBase64,这个库已经几年没有维护了,现在还是MRC版本,要使用请到GITHUB查看使用教程,那里有ARC接入说明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | { { ; } ; ; ; ; ; ; ; ; ; ; { ; } ; ; { // 这里是关键,这里是使用NoPadding的 ; } ; ; ; ; , , , , , , , , , , ; { ; ; } ; ; } |
对于加密算法,大家一定要注意,保证iOS、安卓、服务端的加密规则是一定的,建议统一使用No Padding的,这里使用No Padding是这样的:
1 2 3 4 5 6 | { // 这里是关键,这里是使用NoPadding的 ; } |
其实所谓Padding就是指在位数不够需要补齐时,使用什么来填充,而No Padding就是使用16个0,对应0x0000.如果三端不统一,加密出来就算能解密,也会出现一些奇怪的字符,甚至会有部分乱码。
另外,这里使用的是kCCOptionECBMode
,也就是ECB。在安卓端和PHP端,也得使用ECB。在调试过程中,发现PHP使用CBC解密不了IOS端的。于是改成了使用ECB。
解密算法
依赖于第三方库:GTMBase64,这个库已经几年没有维护了,现在还是MRC版本,要使用请到GITHUB查看使用教程,那里有ARC接入说明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | { { ; } ; ; ; ; ; ; ; ; ; ; ; , , , , , , , , , , ; { ; ; ; } ; ; } |
解密时也得跟加密一样指定为ECB,否则解出来会出现乱码,或者末尾会出现\0、\t之类的符号。
写在最后
开发中总会遇到各种坑,网上查了很多的资料,但是终究没有说明解决的办法,而是只将自己的代码放出来。对于刚接触这方面知识的开发人员来说,是很懵懂的。甚至很多新手会觉得系统就是这样的,我也没办法。其实总会有解决办法的,关键在于与其他各端统一连调。
一行代码实现加密
更新:MD5加密是单向的,只能加密不能解密(破解除外)。标题可能会引起读者误解,已经改正,感谢Li_Cheng同学的提醒,另外笔者发现Li_Cheng同学有篇MD5加密更为详尽的文章,推荐阅读:iOS开发 关于MD5加密的相关使用
java端的加密解密,读者可以看我同事的这篇文章http://www.jianshu.com/p/98569e81cc0b
最近做了一个移动项目,是有服务器和客户端类型的项目,客户端是要登录才行的,服务器也会返回数据,服务器是用Java开发的,客户端要同时支持多平台(Android、iOS),在处理iOS的数据加密的时候遇到了一些问题。起初采取的方案是DES加密,老大说DES加密是对称的,网络抓包加上反编译可能会被破解,故采取RSA方式加密。RSA加密时需要公钥和私钥,客户端保存公钥加密数据,服务器保存私钥解密数据。(iOS端公钥加密私钥解密、java端公钥加密私钥解密,java端私钥加密公钥解密都容易做到,iOS不能私钥加密公钥解密,只能用于验签)。
问题
问题1:iOS端公钥加密的数据用Java端私钥解密。
iOS无论使用系统自带的sdk函数,用mac产生的或者使用java的jdk产生的公钥和私钥,进行加密解密自己都可以使用。不过ios加密,java解密,或者反过来就不能用了。要么是无法创建报告个-9809或-50的错误,要么解出来是乱码。ios系统函数种只有用公钥加密,私钥解密的方式。而公钥加密每次结果都不同。
MAC上生成公钥、私钥的方法,及使用
- 1.打开终端,切换到自己想输出的文件夹下
- 2.输入指令:
openssl
(openssl是生成各种秘钥的工具,mac已经嵌入 - 3.输入指令:
genrsa -out rsa_private_key.pem 1024
(生成私钥,java端使用的) - 4.输入指令:
rsa -in rsa_private_key.pem -out rsa_public_key.pem -pubout
(生成公钥) - 5.输入指令:
pkcs8 -topk8 -in rsa_private_key.pem -out pkcs8_rsa_private_key.pem -nocrypt
(私钥转格式,在ios端使用私钥解密时用这个私钥)注意:在MAC上生成三个.pem格式的文件,一个公钥,两个私钥,都可以在终端通过指令vim xxx.pem 打开,里面是字符串,第三步生成的私钥是java端用来解密数据的,第五步转换格式的私钥iOS端可以用来调试公钥、私钥解密(因为私钥不留在客户端)
问题2:服务器返回数据也要加密,老大打算用java私钥加密,ios用公钥解密(由于iOS做不到用私钥加密公钥解密,只能私钥加密公钥验签),所以这种方案也有问题。
通过看一些大牛的介绍,了解了iOS常用的加密方式
- 1 通过简单的URLENCODE + BASE64编码防止数据明文传输
- 2 对普通请求、返回数据,生成MD5校验(MD5中加入动态密钥),进行数据完整性(简单防篡改,安全性较低,优点:快速)校验
- 3 对于重要数据,使用RSA进行数字签名,起到防篡改作
- 4 对于比较敏感的数据,如用户信息(登陆、注册等),客户端发送使用RSA加密,服务器返回使用DES(AES)加密
原因:客户端发送之所以使用RSA加密,是因为RSA解密需要知道服务器私钥,而服务器私钥一般盗取难度较大;如果使用DES的话,可以通过破解客户端获取密钥,安全性较低。而服务器返回之所以使用DES,是因为不管使用DES还是RSA,密钥(或私钥)都存储在客户端,都存在被破解的风险,因此,需要采用动态密钥,而RSA的密钥生成比较复杂,不太适合动态密钥,并且RSA速度相对较慢,所以选用DES)
所以此次加密,我们选择了第四种加密方式
加密方式
ios端进行DES加密、解密时非常方便
1、引入头文件 #import "DES3Util.h"
2、加密时调用类方法 +(NSString *) encryptUseDES:(NSString *)plainText key:(NSString *)key;
3、解密时调用类方法 +(NSString *)decryptUseDES:(NSString *)cipherText key:(NSString *)key;
ios端进行RSA加密、解密时非常方便
1、引入头文件 #import "RSAUtil.h"
2、公钥加密时调用类方法:
+ (NSString *)encryptString:(NSString *)str publicKey:(NSString *)pubKey;
+ (NSData *)encryptData:(NSData *)data publicKey:(NSString *)pubKey;
3、私钥解密时调用类方法
+ (NSString *)decryptString:(NSString *)str privateKey:(NSString *)privKey;
+ (NSData *)decryptData:(NSData *)data privateKey:(NSString *)privKey;
ios端进行MD5加密、解密时非常方便
1、引入头文件 #import "MD5Util"
2、加密时调用方法:- (NSString *)md5:(NSString *)str;
ios端进行AES加密、解密时非常方便
1、引入头文件 #import "AES.h"
2、加密时调用方法
+ (NSString *)encrypt:(NSString *)message password:(NSString *)password;
2、解密时调用的方法
+ (NSString *)decrypt:(NSString *)base64EncodedString password:(NSString *)password;
MD5加密
iOS中提供了很多种加密算法,对于存储密码,可以使用不可逆的MD5加密。
使用MD5加密需要导入头文件:
''#import <CommonCrypto/CommonDigest.h>
##### 简单的MD5加密
+ ( NSString *)md5String:( NSString *)str
{
const char *myPasswd = [str UTF8String ];
unsigned char mdc[ 16 ];
CC_MD5 (myPasswd, ( CC_LONG ) strlen (myPasswd), mdc);
NSMutableString *md5String = [ NSMutableString string ];
for ( int i = 0 ; i< 16 ; i++) {
[md5String appendFormat : @"%02x" ,mdc[i]];
}
return md5String;
}
##### 复杂一些的MD5加密
+ ( NSString *)md5String:( NSString *)str
{
const char *myPasswd = [str UTF8String ];
unsigned char mdc[ 16 ];
CC_MD5 (myPasswd, ( CC_LONG ) strlen (myPasswd), mdc);
NSMutableString *md5String = [ NSMutableString string ];
[md5String appendFormat : @"%02x" ,mdc[ 0 ]];
for ( int i = 1 ; i< 16 ; i++) {
[md5String appendFormat : @"%02x" ,mdc[i]^mdc[ 0 ]];
AES加密
高级加密标准(Advanced Encryption Standard,AES),又称Rijndael加密法。 以下实现代码中分别为NSData和NSString增加了一个Category。使用时直接调用即可。
需要注意的是,AES并不能作为HASH算法,加密并解密后的结果,并不一定与原文相同,使用时请注意进行结果验算。例如解密原文的长度,格式规则等。 NG实例
原文:170987350 密码:170
Objective-c的AES加密和解密算法的具体实现代码如下: 1.拓展NSData,增加AES256加密方法
// //NSData+AES256.h // #import <Foundation/Foundation.h> #import <CommonCrypto/CommonDigest.h> #import <CommonCrypto/CommonCryptor.h> @interface NSData(AES256) -(NSData *) aes256_encrypt:(NSString *)key; -(NSData *) aes256_decrypt:(NSString *)key; @end // //NSData+AES256.m // #import "NSData+AES256.h" @implementation NSData(AES256) - (NSData *)aes256_encrypt:(NSString *)key //加密 { char keyPtr[kCCKeySizeAES256+1]; bzero(keyPtr, sizeof(keyPtr)); [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding]; NSUInteger dataLength = [self length]; size_t bufferSize = dataLength + kCCBlockSizeAES128; void *buffer = malloc(bufferSize); size_t numBytesEncrypted = 0; CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding | kCCOptionECBMode, keyPtr, kCCBlockSizeAES128, NULL, [self bytes], dataLength, buffer, bufferSize, &numBytesEncrypted); if (cryptStatus == kCCSuccess) { return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted]; } free(buffer); return nil; } - (NSData *)aes256_decrypt:(NSString *)key //解密 { char keyPtr[kCCKeySizeAES256+1]; bzero(keyPtr, sizeof(keyPtr)); [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding]; NSUInteger dataLength = [self length]; size_t bufferSize = dataLength + kCCBlockSizeAES128; void *buffer = malloc(bufferSize); size_t numBytesDecrypted = 0; CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding | kCCOptionECBMode, keyPtr, kCCBlockSizeAES128, NULL, [self bytes], dataLength, buffer, bufferSize, &numBytesDecrypted); if (cryptStatus == kCCSuccess) { return [NSData dataWithBytesNoCopy:buffer length:numBytesDecrypted]; } free(buffer); return nil; } @end |
2.拓展NSString,增加AES256加密方法,需要导入NSData+AES256.h
// //NSString +AES256.h // #import <Foundation/Foundation.h> #import <CommonCrypto/CommonDigest.h> #import <CommonCrypto/CommonCryptor.h> #import "NSData+AES256.h" @interface NSString(AES256) -(NSString *) aes256_encrypt:(NSString *)key; -(NSString *) aes256_decrypt:(NSString *)key; @end // //NSString +AES256.h // @implementation NSString(AES256) -(NSString *) aes256_encrypt:(NSString *)key { const char *cstr = [self cStringUsingEncoding:NSUTF8StringEncoding]; NSData *data = [NSData dataWithBytes:cstr length:self.length]; //对数据进行加密 NSData *result = [data aes256_encrypt:key]; //转换为2进制字符串 if (result && result.length > 0) { Byte *datas = (Byte*)[result bytes]; NSMutableString *output = [NSMutableString stringWithCapacity:result.length * 2]; for(int i = 0; i < result.length; i++){ [output appendFormat:@"%02x", datas[i]]; } return output; } return nil; } -(NSString *) aes256_decrypt:(NSString *)key { //转换为2进制Data NSMutableData *data = [NSMutableData dataWithCapacity:self.length / 2]; unsigned char whole_byte; char byte_chars[3] = {'\0','\0','\0'}; int i; for (i=0; i < [self length] / 2; i++) { byte_chars[0] = [self characterAtIndex:i*2]; byte_chars[1] = [self characterAtIndex:i*2+1]; whole_byte = strtol(byte_chars, NULL, 16); [data appendBytes:&whole_byte length:1]; } //对数据进行解密 NSData* result = [data aes256_decrypt:key]; if (result && result.length > 0) { return [[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding]autorelease]; } return nil; } @end |
- 上一篇 »python Aes 加密 解密
- 下一篇 ».NET与Java互通AES算法加密解密