Mac和 iOS 下的对称和非对称加密算法的使用
分享在Mac 和 iOS 上使用到的对称和非对称加密算法. 包括RSA,DSA, AES, DES, 3DES 和 blowfish 等等.
因为要实现ssh协议, 所以用到了这些算法, 这些算法在mac和ios上的接口比较难用, 我在这折腾了好长时间, 这里分享出来, 希望对大家有帮助.
(这里不提 openssl 在 apple 上的接口了)
主要参考了apple的文档:
Apple Encrypting and Hashing Data
先大致概括一下整体情况:
基本上这两个平台上的加密解密都和 keychain service 密不可分. keychain是mac和ios上用来保存证书,密码,key 等等敏感信息的存储区. 有专门的api访问这些接口. 有时候我们为了获得一个 key 的实例,必须想办法把数据导入到keychain中,然后才能通过keychain获得一个key的实例. 后面再说.
在mac 上, 有三个方式实现加密解密签名服务:
Security Transforms API —a Core-Foundation-level API that provides support for signing and verifying, symmetric cryptography, and Base64 encoding and decoding.
是 CoreFoundation 级别的api, 提供了, 最全的功能和算法支持. 包括对称,非对称算法. 实现加密,签名功能. 但遗憾的是这个接口只在mac上有效. iOS上没有. 但有些功能必须要用到, 所以要实现跨平台的代码,需要一些补丁.Common Crypto—a C-level API that can perform most symmetric encryption and decryption tasks
这是一个 C 风格的接口. 好消息是它在mac和ios上都有, 可以跨平台. 但坏消息是, 它只包含了, 对称加密算法, 却没有非对称算法. 因此只能加密解密,而不能签名和验证. 其实之前 Apple上还有一个 ComonCryptoRSA 模块, 但后来不知为何消失了.CDSA/CSSM —a legacy API that should be used only to perform tasks not supported by the other two APIs, such as asymmetric encryption
这个名字比较吓人, Common Data Security Architecture (CDSA) 通用数据安全架构. 很奇怪,它被apple接受后不久, 就被废弃了. 现在已经不建议使用了. 所以就不提它了.
在 iOS 上, 基本上有两种方式:
Common Crypto. 这个在上面已经说过了. 对称算法接口.
使用系统提供的特有api实现加密,解密, 签名和验证:
系统提供了下面4个函数:
SecKeyEncrypt—encrypts a block of data using the specified key.
SecKeyDecrypt—decrypts a block of data using the specified key.
SecKeyRawSign—signs a block of data using the specified key.
SecKeyRawVerify—verifies a signature against a block of data and a specified key.
基于上面的分析, 我们秉着尽可能减少代码重复, 跨平台开发的原则: 对称算法就使用 “Common Crypto” 模块了. 因为两个平台都有. 而非对称则需要分别实现了.
下面详细分享一些细节:
一, 非对称加密算法, 签名和验证.(RSA/DSA signature and verity)
这需要在两个平台独立开发.
- Mac 平台.
 
在 mac 平台上, 我们使用它的 Security Transforms API.
参考这里: Security Transforms Programming Guide-Signing and Verifying
上面有很好的 代码 片段. 需要注意的是如何把 RSA 的参数 变成 api 需要的 SecKeyRef 对象.
这是它的导入片段.
    params.keyUsage = NULL;
    params.keyAttributes = NULL;
    SecExternalItemType itemType = kSecItemTypeCertificate;
    SecExternalFormat externalFormat = kSecFormatPEMSequence;
    int flags = 0;
 oserr = SecItemImport(cfdataprivatekey,
        NULL, // filename or extension
        &externalFormat, // See SecExternalFormat for details
        &itemType, // item type
        flags, // See SecItemImportExportFlags for details
        ¶ms,
        NULL, // Don't import into a keychain
        &temparray);
    if (oserr) {
        fprintf(stderr, "SecItemImport failed (oserr=%d)\n", oserr);
        CFShow(temparray);
        exit(-1);
    }
    privatekey = (SecKeyRef)CFArrayGetValueAtIndex(temparray, 0);
这里是为了创建 SecKeyRef 实例. 通过 SecItemImport 把数据导入.变成SecKeyRef实例. 数据放在 cfdataprivatekey 中. 这个数据必须是 Pem格式的证书. 因为这个case下需要私钥, 所以这个证书需要包含私钥, 都是pem格式.
这里特别介绍一下, 如何从ssh 的公钥格式导入. 以RSA为例, RSA的公钥其实是一个底数e, 和一个大整数 m ,
e = [int32(len), bytes(value)]
m = [int32(len), bytes(value)]
e 和 m 的结构一样. 先是4个字节的长度, 然后紧跟上字节序列. len是 大端在前的, 跟通常的小端是有区别的.
完整的机构大概是这个样子的:
Binary = [0x00, 0x00, 0x00, 0x07, 'ssh-rsa', e, m]
keydata = 'ssh-rsa' + Base64Encode(Binary)
这个keydata 就可以用来构建上面用到的参数cfdataprivatekey了.
对于DSA, 结构跟上面类似:
p = [int32(len), bytes(value)]
q = [int32(len), bytes(value)]
g = [int32(len), bytes(value)]
y = [int32(len), bytes(value)]
Binary = [0x00, 0x00, 0x00, 0x07, 'ssh-dss', p, q, g, y]
keydata = 'ssh-dss' + Base64Encode(Binary)
- 对于 iOS, 平台, 我们使用上面说的两个函数来签名和验证:
 
SecKeyRawSign—signs a block of data using the specified key.
SecKeyRawVerify—verifies a signature against a block of data and a specified key.
这两个函数要命的是都需要一个 SecKeyRef 参数, iOS 上还真没有直接的方式可以通过大整数直接创建 SecKeyRef的实例.
要么通过 keychain 读取. 或者通过 SecPKCS12Import() 函数导入 pkcs12 格式的包含私钥的证书, 然后获得 SecIdentityRef 实例. 然后再通过 SecIdentityCopyPrivateKey() 函数把其中的 私钥导出成 SecKeyRef实例.
OSStatus extractIdentityAndTrust(CFDataRef inPKCS12Data,
                                 SecIdentityRef *outIdentity,
                                 SecTrustRef *outTrust,
                                 CFStringRef keyPassword)
{
    OSStatus securityError = errSecSuccess;
    const void *keys[] =   { kSecImportExportPassphrase };
    const void *values[] = { keyPassword };
    CFDictionaryRef optionsDictionary = NULL;
    /* Create a dictionary containing the passphrase if one
       was specified.  Otherwise, create an empty dictionary. */
    optionsDictionary = CFDictionaryCreate(
                                                  NULL, keys,
                                                  values, (keyPassword ? 1 : 0),
                                                  NULL, NULL);  // 1
    CFArrayRef items = NULL;
    securityError = SecPKCS12Import(inPKCS12Data,
                                    optionsDictionary,
                                    &items);                    // 2
    //
    if (securityError == 0) {                                   // 3
        CFDictionaryRef myIdentityAndTrust = CFArrayGetValueAtIndex (items, 0);
        const void *tempIdentity = NULL;
        tempIdentity = CFDictionaryGetValue (myIdentityAndTrust,
                                                       kSecImportItemIdentity);
        CFRetain(tempIdentity);
        *outIdentity = (SecIdentityRef)tempIdentity;
        const void *tempTrust = NULL;
        tempTrust = CFDictionaryGetValue (myIdentityAndTrust, kSecImportItemTrust);
        CFRetain(tempTrust);
        *outTrust = (SecTrustRef)tempTrust;
    }
    if (optionsDictionary)                                      // 4
        CFRelease(optionsDictionary);
    if (items)
        CFRelease(items);
    return securityError;
}
另外一个方法是, 苹果官方给的示例代码, 强制拼凑 SecKeyRef示例.
这里有 SecKeyWrapper 的实例代码: SecKeyWrapper 的实例代码
并且可以在这里直接下载到 源码: SecKeyWrapper 源码
这个源码里面有很多 苹果写的例子. 非常好. 对剑使用这里面的代码实现.
二, 对于对称加密算法.
这个比较简单了, 我们直接使用 Common Crypto 模块. 在mac 和ios上可以跨平台.
请参考这里: Apple Common Crypto library
- 使用 CCCryptorCreate 或者 CCCryptorCreateWithMode 创建 CCCryptorRef 对象.
然后不断的调用 CCCryptorUpdate. 进行加密/解密.
最后调用:CCCryptorFinal. 获取最后一块加密方法. 
建议使用 CCCryptorCreateWithMode 方法.因为它能指定更多的参数. 比如 加密算法的padding 和ciphermode.
最后再顺便分享一下Mac 和iOS上生成 密码学安全的随机数的方法: Generating Random Numbers
简单的来说. 在mac上, 可以通过 fopen 读取 /dev/random 设备获得密码学安全的随机数.
FILE *fp = fopen("/dev/random", "r");
if (!fp) {
    perror("randgetter");
    exit(-1);
}
uint64_t value = 0;
int i;
for (i=0; i<sizeof(value); i++) {
    value <<= 8;
    value |= fgetc(fp);
}
fclose(fp);
而在 iOS 上, 由于不能读取设备, 它提供了 专门的 方法: SecRandomCopyBytes, 用起来非常简单.
欢迎访问我的独立博客 https://blog.byneil.com
Mac和 iOS 下的对称和非对称加密算法的使用的更多相关文章
- webrtc 在MAC下和iOS下的编译
		
一:安装brew和git 1. mkdir /usr/local 2. curl -LsSf http://github.com/mxcl/homebrew/tarball/master | sudo ...
 - 第十四章 调试及安全性(In .net4.5) 之 对称及非对称加密
		
1. 概述 本章内容包括:对称及非对称加密算法..net中的加密类.使用哈希操作.创建和管理签名认证.代码访问权限 和 加密字符串. 2. 主要内容 2.1 使用对称和非对称加密 ① 对称加密:使用同 ...
 - 非对称加密算法--DH
		
注意:本节内容主要参考自<Java加密与解密的艺术(第2版)>第8章“高等加密算法--非对称加密算法” 11.1.非对称加密算法 特点: 发送方和接收方均有一个密钥对(公钥+私钥),其中公 ...
 - 第十一章 非对称加密算法--DH
		
注意:本节内容主要参考自<Java加密与解密的艺术(第2版)>第8章“高等加密算法--非对称加密算法” 11.1.非对称加密算法 特点: 发送方和接收方均有一个密钥对(公钥+私钥),其中公 ...
 - 非对称加密算法DH
		
特点: 发送方和接收方均有一个密钥对(公钥+私钥),其中公钥传播,私钥自己保存,不需要传播 私钥不需要传播的特性解决了对称加密算法中密钥传播的困难(这个困难一般通过线下传递可以解决) 加密安全性极高, ...
 - 【ionic】Mac IOS下真机调试
		
模拟调试不能保证真机一定没问题,所以真机调试是非常必要的一步 IOS设备 启用设备调试 在IOS设备中(Iphone,Ipad)中开始web检测器 设备->safari->高级->w ...
 - iframe在ios下无故扩大的问题探究
		
移动端页面内嵌了个 iframe,在 ios 下打开却发现页面怪异.比如 demo.代码如下: <!DOCTYPE html> <html lang="zh-CN" ...
 - iOS下使用SHA1WithRSA算法加签源码
		
首先了解一下几个相关概念,以方便后面遇到的问题的解决: RSA算法:1977年由Ron Rivest.Adi Shamirh和LenAdleman发明的,RSA就是取自他们三个人的名字.算法基于一个数 ...
 - Mac和iOS开发资源汇总
		
小引 本文主要汇集一些苹果开发的资源,会经常更新,建议大家把这篇文章单独收藏(在浏览器中按command+D). 今天(2013年7月19日)收录了许多中文网站和博客.大家一定要去感受一下哦. 如果大 ...
 
随机推荐
- html5播放mp4视频代码
			
1.nginx支持flv和mp4格式播放 默认yum安装nginx centos7安装nginx时候应该是默认安装nginx_mod_h264_streaming模块的 # nginx -V查看是否安 ...
 - C++ 实例化对象 p->printX()
			
一.从栈实例化对象 我们首先定义一个类,类的名字叫TV,里面包括两个成员变量,两个成员函数. class TV // 定义一个电视的类TV { public: ]; // 定义类的属性,一个数组 in ...
 - Bootstrap-Other:CSS编码规范
			
ylbtech-Bootstrap-Other:CSS编码规范 1.返回顶部 1. Bootstrap CSS编码规范 语法 用两个空格来代替制表符(tab) -- 这是唯一能保证在所有环境下获得一致 ...
 - [转]MVC 经验总结_EF
			
&& o.Name != "") .OrderByDescending(o => o.ID) .OrderBy(o => o.Name) .Select ...
 - nginx收到空包问题
			
tcpdump有收包,但是nginx的access.log显示post数据为空 可以通过tcpdump监控端口 http://www.cnblogs.com/linn/p/4792468.html 修 ...
 - VueRouter
			
使用VueRouter的前提: 1, 必须导入vue-router.js文件 2, 要有VueRouter()实例 3, 要把VueRouter实例挂载到Vue实例中 4, 路由的入口 ...
 - selenium+python自动化83-pip安装selenium报Read time out  HTTPSConnectionPool(host='pypi.python.org' port443)
			
遇到问题 1.有些小伙伴在用pip安装selenium时候报 Read time out HTTPSConnectionPool(host='pypi.python.org' port443) 2.估 ...
 - python爬虫----基本操作
			
一.爬虫基本操作 有些网站和其他网站是有关系(链接),全球的网站就相当于一个蜘蛛网,我们放一只蜘蛛在上面爬,一定能够把网爬个遍.那么如果我们要爬取互联网上内容我们就相当于放一只蜘蛛在上面. 爬虫分为 ...
 - 读书笔记--Linux Shell脚本攻略
			
总结的来说,这本书很实践性和实用性强,都是给的具体的例子,直接可以在终端操作实践,比单纯只看不动手务实多了,另外就是,这本书涵盖的内容也比较广,从文本操作到服务器管理到远程ssh等等,都给出来作者挑选 ...
 - 用Msxml2.XMLHTTP 与 Msxml2.ServerXMLHTTP 发生网页请求
			
发送 HTTP 请求时,首先想到的一般是 Msxml2.XMLHTTP(Microsoft.XMLHTTP 已经不提倡使用了). ServerXMLHTTP 为不同 Web 服务器之间的服务器安全 H ...