分享在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