漫谈iOS RSA非对称加密与解密

前言



最近公司的客户端安全性出现了严重的问题,如今这个出解决方案并自我测试验证可行性的重任落在了我的身上,学习了很多他人的文章,再经过多次讨论,最后才确定最终解决方案。笔者在这里讲讲这一经历中所需要了解的知识。

iOS客户端想要加密传输数据以防被窃取,最可靠的方式莫过于使用公钥加密算法加密,使用HTTPS协议在整个传输过程中都使用了这个技术,对于未能使用HTTPSHTTP接口,我们能否实现RSA加密呢?

PHP端实现解密可参考:iOS与PHP端实现RSA非对称加密解密

提示:本篇文章只讲RSA非对称加密相关知识。

RSA非对称加密介绍



非对称加密算法可能是世界上最重要的算法,它是当今电子商务等领域的基石。简而言之,非对称加密就是指加密公钥和解密密钥是不同的,而且加密公钥和解密密钥是成对出现。非对称加密又叫公钥加密,也就是说成对的密钥,其中一个是对外公开的,所有人都可以获得,人们称之为公钥;而与之相对应的称为私钥,只有这对密钥的生成者才能拥有。

公钥私钥具有以下重要特性:

对于一个私钥,有且只有一个与之对应的公钥。公钥公开给任何人,私钥通常是只有生成者拥有。公/私钥通常是1024位或者2048位,越长安全系数越高,但是解密越困难。尽管拿到了公钥,如果没有私钥,要想解密那几乎是不可能的,至少现在在世界上还没有人公开出来说成功解密的。

非对称加密算法如此强大可靠,却有一个弊端,就是加解密比较耗时。因此,在实际使用中,往往与对称加密和摘要算法结合使用。对称加密很好理解,本篇文章不细读对称加密。我们再来看一下摘要算法。

RSA非对称加密典型用法


  • 防止中间人攻击:将明文通过接收人的公钥加密,传输给接收人,因为只有接收人拥有对应的私钥,别人不可能拥有或者不可能通过公钥推算出私钥,所以传输过程中无法被中间人截获。只有拥有私钥的接收人才能解密得到原文。此用法通常用于交换对称密钥。比如,在登录时在客户端生成随机密钥,然后使用公钥加密传输到服务端,服务端使用私钥解密得到随机密钥,此密钥就用于后续的AES加密/解密使用。
  • 身份验证和防止篡改:通常会将所有的参数按照某种规则拼接并排序,然后使用某种算法加密生成摘要,然后在服务端使用同样的规则生成摘要,比较两个摘要以确定身份和是否被篡改过。

摘要算法

上面提到摘要算法,摘要算法是指可以将任意长度的文本,通过一个算法,得到一个固定长度的文本。这里文本不一定只是文本,可以是字节数据。所以摘要算法可以将很长的内容变成一个固定长度的东西。

摘要算法具有以下重要特性:

  • 只要源内容不同,计算得到的结果,必然不同。
  • 无法通过摘要算法可逆拿到源内容

典型的摘要算法有大名鼎鼎的MD5SHA。摘要算法主要用于比对信息源是否一致,因为只要源内容发生变化,得到的摘要必然不同;而且通常结果要比源短很多,所以称为“摘要信息”。

数字签名

理解了非对称加密和摘要算法,来看一下数字签名,实际上数字签名就是两者结合。现在假设我们需要传输好几个参数,如何确定接口中所传输的参数值是否被篡改过?这时候数字签名就可以解决这个问题了。

常用的解决办法:将参数按照指定的规则拼接(拼接规则要与服务端协商统一),然后排序(保证服务端的顺序与客户端的一致),然后通过摘要算法如MD5得到摘要信息,再通过AES加密摘要信息,服务端按照同样的规则,生成摘要,然后比对是否一致。

温馨提示:专业叫数字签名,实际工作中大家都叫加盐防篡改。

CA签发证书

实际上,数字证书就是通过数字签名实现的数字化的证书。在一般的证书组成部分中还加入了其他的信息,比如证书有效期,公司组织名称等,过了有效期需要重新签发。

跟现实生活中的签发机构一样,数字证书的签发机构也有若干,并有不同的用处。比如苹果公司就可以签发跟苹果公司有关的证书,而跟web访问有关的证书则是由几家全世界公认的机构进行签发。这些签发机构称为CACertificate Authority)。

对于被签发人,通常都是企业或开发者。对于需要搭建基于SSL的网站,那么需要从几家国际公认的CA去申请证书;对如需要开发iOS的应用程序,需要从苹果公司获得相关的证书。这些申请通常是企业或者开发者个人提交给CA的。当然申请所需要的材料、资质和费用都各不相同,是由这些CA制定的,比如苹果要求$99或者$299的费用。

web应用相关的SSL证书的验证方通常是浏览器;iOS各种证书的验证方是iOS设备。我们之所以必须从CA处申请证书,就是因为CA已经将整个验证过程规定好了。

生成自签名证书

// 生成1024位私钥
openssl genrsa -out private_key.pem 1024

// 根据私钥生成CSR文件
openssl req -new -key private_key.pem -out rsaCertReq.csr

// 根据私钥和CSR文件生成crt文件
openssl x509 -req -days 3650 -in rsaCertReq.csr -signkey private_key.pem -out rsaCert.crt

// 为IOS端生成公钥der文件
openssl x509 -outform der -in rsaCert.crt -out public_key.der

// 将私钥导出为这p12文件
openssl pkcs12 -export -out private_key.p12 -inkey private_key.pem -in rsaCert.crt

注意:在生成私钥时,是需要密码的,一定要记住密码。

得到了public_key.der公钥和private_key.p12私钥,就可以进行加密和解密了。

NSString *encryptString = [self rsaEncryptText:@"123456好哇好哇哈"];
NSLog(@"加密:123456好哇好哇哈:%@", encryptString);
NSLog(@"解密结果为:%@", [self rsaDecryptWithText:encryptString]);

公开HYBRSAEncrypt

请注意,代码中直接放到工程并不能直接使用,有部分转成base64字符串的方法是一个扩展,自己替换成你们自己的方法就可以了。代码有部分是参考他人的。如果觉得是您写的,或者与您的相同,不希望公开可给我邮件。

//
//  HYBRSAEncrypt.h
//
//  Created by huangyibiao on 15/12/16.
//  Copyright  2015年 edu.huangyibiao.com. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface HYBRSAEncrypt : NSObject

// 加密相关
- (void)loadPublicKeyWithPath:(NSString *)derFilePath;
- (void)loadPublicKeyWithData:(NSData *)derData;
- (NSString *)rsaEncryptText:(NSString *)text;
- (NSData *)rsaEncryptData:(NSData *)data;

// 解密相关
- (void)loadPrivateKeyWithPath:(NSString *)p12FilePath password:(NSString *)p12Password;
- (void)loadPrivateKeyWithData:(NSData *)p12Data password:(NSString *)p12Password;
- (NSString *)rsaDecryptText:(NSString *)text;
- (NSData *)rsaDecryptData:(NSData *)data;

@end

实现文件中,有几个API是本人的扩展方法,请替换成你们所封装的API

//
//  HYBRSAEncrypt.m
//
//  Created by huangyibiao on 15/12/16.
//  Copyright  2015年 edu.huangyibiao.com. All rights reserved.
//

#import "HYBRSAEncrypt.h"

@interface HYBRSAEncrypt () {
  SecKeyRef _publicKey;
  SecKeyRef _privateKey;
}

@end

@implementation HYBRSAEncrypt

- (void)dealloc {
  if (nil != _publicKey) {
    CFRelease(_publicKey);
  }

  if (nil != _privateKey) {
    CFRelease(_privateKey);
  }
}

#pragma mark - 加密相关
- (void)loadPublicKeyWithPath:(NSString *)derFilePath {
      NSData *derData = [[NSData alloc] initWithContentsOfFile:derFilePath];
  if (derData.length > 0) {
    [self loadPublicKeyWithData:derData];
  } else {
    NSLog(@"load public key fail with path: %@", derFilePath);
  }
}

- (void)loadPublicKeyWithData:(NSData *)derData {
  SecCertificateRef myCertificate = SecCertificateCreateWithData(kCFAllocatorDefault, (__bridge CFDataRef)derData);
  SecPolicyRef myPolicy = SecPolicyCreateBasicX509();
  SecTrustRef myTrust;
  OSStatus status = SecTrustCreateWithCertificates(myCertificate,myPolicy,&myTrust);
  SecTrustResultType trustResult;

  if (status == noErr) {
    status = SecTrustEvaluate(myTrust, &trustResult);
  }

  SecKeyRef securityKey = SecTrustCopyPublicKey(myTrust);
  CFRelease(myCertificate);
  CFRelease(myPolicy);
  CFRelease(myTrust);

  _publicKey = securityKey;
}

- (NSString *)rsaEncryptText:(NSString *)text {
  NSData *encryptedData = [self rsaEncryptData:[text hdf_toData]];
  NSString *base64EncryptedString = [NSString hdf_base64StringFromData:encryptedData
                                                                length:encryptedData.length];
  return base64EncryptedString;
}

- (NSData *)rsaEncryptData:(NSData *)data {
  SecKeyRef key = _publicKey;

  size_t cipherBufferSize = SecKeyGetBlockSize(key);
  uint8_t *cipherBuffer = malloc(cipherBufferSize * sizeof(uint8_t));
  size_t blockSize = cipherBufferSize - 11;
  size_t blockCount = (size_t)ceil([data length] / (double)blockSize);

  NSMutableData *encryptedData = [[NSMutableData alloc] init] ;
  for (int i = 0; i < blockCount; i++) {
    size_t bufferSize = MIN(blockSize,[data length] - i * blockSize);
    NSData *buffer = [data subdataWithRange:NSMakeRange(i * blockSize, bufferSize)];
    OSStatus status = SecKeyEncrypt(key,
                                    kSecPaddingPKCS1,
                                    (const uint8_t *)[buffer bytes],
                                    [buffer length],
                                    cipherBuffer,
                                    &cipherBufferSize);
    if (status == noErr) {
      NSData *encryptedBytes = [[NSData alloc] initWithBytes:(const void *)cipherBuffer
                                                      length:cipherBufferSize];
      [encryptedData appendData:encryptedBytes];
    } else {
      if (cipherBuffer) {
        free(cipherBuffer);
      }

      return nil;
    }
  }

  if (cipherBuffer){
    free(cipherBuffer);
  }

  return encryptedData;
}

#pragma mark - 解密相关
- (void)loadPrivateKeyWithPath:(NSString *)p12FilePath password:(NSString *)p12Password {
  NSData *data = [NSData dataWithContentsOfFile:p12FilePath];

  if (data.length > 0) {
    [self loadPrivateKeyWithData:data password:p12Password];
  } else {
    NSLog(@"load private key fail with path: %@", p12FilePath);
  }
}

- (void)loadPrivateKeyWithData:(NSData *)p12Data password:(NSString *)p12Password {
  SecKeyRef privateKeyRef = NULL;
  NSMutableDictionary * options = [[NSMutableDictionary alloc] init];

  [options setObject:p12Password forKey:(__bridge id)kSecImportExportPassphrase];

  CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
  OSStatus securityError = SecPKCS12Import((__bridge CFDataRef)p12Data,
                                           (__bridge CFDictionaryRef)options,
                                           &items);

  if (securityError == noErr && CFArrayGetCount(items) > 0) {
    CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
    SecIdentityRef identityApp = (SecIdentityRef)CFDictionaryGetValue(identityDict,
                                                                      kSecImportItemIdentity);
    securityError = SecIdentityCopyPrivateKey(identityApp, &privateKeyRef);

    if (securityError != noErr) {
      privateKeyRef = NULL;
    }
  }

  _privateKey = privateKeyRef;

//  CFRelease(items);
}

- (NSString *)rsaDecryptText:(NSString *)text {
  NSData *data = [NSData hdf_base64DataFromString:text];

  NSData *decryptData = [self rsaDecryptData:data];

  NSString *result = [[NSString alloc] initWithData:decryptData encoding:NSUTF8StringEncoding];
  return result;
}

- (NSData *)rsaDecryptData:(NSData *)data {
  SecKeyRef key = _privateKey;

  size_t cipherLen = [data length];
  void *cipher = malloc(cipherLen);

  [data getBytes:cipher length:cipherLen];
  size_t plainLen = SecKeyGetBlockSize(key) - 12;

  void *plain = malloc(plainLen);
  OSStatus status = SecKeyDecrypt(key, kSecPaddingPKCS1, cipher, cipherLen, plain, &plainLen);

  if (status != noErr) {
    return nil;
  }

  NSData *decryptedData = [[NSData alloc] initWithBytes:(const void *)plain length:plainLen];

  return decryptedData;
}

@end

写在最后

笔者经历了九九八十一难才调通,在前端实现RSA加密和解密,并验证成功。这里面关系到很多方面的知识,大家可以参考苹果官方关于安全性方面的文档。如果这里写得不好或者读完文章您还不懂怎么做,不要担心,这是很正常的。因为笔者也是经过了好几天的研究和讨论才在安全性方面有所了解。

阅读原文

关注我



微信公众号:iOSDevShares
有问必答QQ群:324400294

时间: 2024-11-03 21:56:09

漫谈iOS RSA非对称加密与解密的相关文章

解密-关于RSA非对称加密的问题。请各位大神帮我看下以下问题,新人求助。。。感激不尽

问题描述 关于RSA非对称加密的问题.请各位大神帮我看下以下问题,新人求助...感激不尽 关于RSA非对称加密的问题.编程语言采用的是C++ 现在需要设计一个用来加密解密程序.里面包含两个接口,一个是加密,一个是解密. rsa 对称加密是公钥和私钥进行加密,接口设计如下: int EncodeRSA(unsigned char pub_key,unsigned int pass_len, unsigned char data,unsigned int data_len,unsigned char

c#与JavaScript实现对用户名、密码进行RSA非对称加密

原文:c#与JavaScript实现对用户名.密码进行RSA非对称加密 博主最近手上这个项目呢(就是有上百个万恶的复杂excel需要解析的那个项目,参见博客:http://www.cnblogs.com/csqb-511612371/p/4885930.html),由于是一个内网项目,安全性要求很低,不需要做什么报文加密. 但是总觉得用户名密码都是明文传输,略微有点坑甲方... 想了想,那就做个RSA加密,把用户名.密码做密文传输吧...至于为什么是RSA,因为也想趁机学习一下,DES.MD5什

Java中RSA非对称密钥加解密使用示例

一.简介: RSA加密算法是最常用的非对称加密算法,CFCA在证书服务中离不了它.RSA是第一个比较完善的公开密钥算法,它既能用于加密,也能用于数字签名.这个算法经受住了多年深入的密码分析,虽然密码分析者既不能证明也不能否定RSA的安全性,但这恰恰说明该算法有一定的可信性,目前它已经成为最流行的公开密钥算法. 二.RSA的公钥.私钥的组成,以及加密.解密的公式可见于下表 三.使用方式: ① 假设A.B机器进行通信,已A机器为主: ② A首先需要用自己的私钥为发送请求数据签名,并将公钥一同发送给B

Linux下OpenSSL的DSA与RSA非对称加密解析

在日常系统管理工作中,需要作一些加解密的工作,通过openssl工具包就能完成我们很多需求! 1. openssl RSA 加解密 RSA是基于数论中大素数的乘积难分解理论上的非对称加密法,使用公私钥的方法进行加解密 公钥 用于加密,它是向所有人公开的 ; 私钥用于解密,只有密文的接收者持有 生成一个密钥(私钥) 代码如下: [root@hunterfu ~]# openssl genrsa -out private.key 1024 注意: 需要注意的是这个文件包含了公钥和密钥两部分,也就是说

RSA 非对称加密 数字签名 数字证书

什么是RSA加密算法 RSA加密算法是一种非对称加密算法,算法的数学基础是极大数分解难题. RSA加密算法的强度也就是极大数分解的难度,目前700多位(二进制)的数字已经可以破解,1024位认为是比较安全的,2048则是非常安全的. 在RSA加密算法中,密钥由两部分组成,称之为公钥和私钥,私有由发送方自己保存,不能泄漏.公钥由发送方公布出去.发送方发送消息时,会用公钥对消息进行加码,接收方必须要使用对应的私钥才能将加密后的信息解开.因此,只要私钥不泄漏,通信内容就不会被破解. 如何保证消息不会被

RSA js加密 C#解密 出现错误&amp;amp;quot;不正确的数据&amp;amp;quot;

问题描述 最近想把RSA加密方法应用的项目,于是去网上查了Rsa加密的使用方法,找到了Javascript库:http://www.ohdave.com/rsa/结合C#自用的RSACryptoServiceProvider实现逻辑如下:1.RSA对象:internalRSACryptoServiceProviderRsa{get{if(HttpContext.Cache["Rsa"]!=null){RSACryptoServiceProviderencryptKeys=(RSACry

详解.Net下的加密解密算法(6) 玩转非对称加密

本博文来聊聊怎么玩转非对称加密吧,这里主要介绍.NET算法下的3种非对称加密算法:DSA,RSA,ECDsa.上两篇博文分 别为Hash家族和非对称加密家族找到了lead,现在我们就为非对称加密技术找个合适的lead吧. 首先创建一个接口 :"IEncryptAndDecrypt",然后为上面的3中算法分别创建3个实现类并让这些类实现接口"IEncryptAndDecrypt".它们 的情况如下图: 这下我们把这些哥们都召集起来了,现在我们就给他们找一个lead:&

详解.NET下的加密解密算法(3) 非对称加密

本博文列出了.NET下常用的非对称加密算法,并将它们制作成小DEMO,希望能对大家有所帮助. RSA static string EnRSA(string data,string publickey) { RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); byte[] cipherbytes; rsa.FromXmlString(publickey); cipherbytes = rsa.Encrypt(Encoding

php rsa 加密,解密,签名,验签详解_php技巧

php rsa 加密,解密,签名,验签 由于对接第三方机构使用的是Java版本的rsa加解密方法,所有刚开始在网上搜到很多PHP版本的rsa加解密,但是对接java大多都不适用. 以下php版本是适用于对接java接口,java适用密钥再php语言使用是需要添加 -----BEGIN CERTIFICATE----- -----END CERTIFICATE----- 使用密钥:加密公钥  public_key.cer 解密私钥  private_key.key 签名私钥 sign_key.ke