Mac和 iOS下的对称和非对称加密算法的使用

Mac 和 iOS 上使用到的对称非对称加密算法. 包括RSA,DSA, AES, DES, 3DESblowfish 等等.
因为要实现ssh协议, 所以用到了这些算法, 这些算法在mac和ios上的接口比较难用(这里不提 openssl 在 apple 上的接口了)

原作者主要参考了apple的文档:

Cryptographic Services Guide
Apple Encrypting and Hashing Data

先大致概括一下整体情况:

基本上这两个平台上的加密解密都和keychain service 密不可分. keychain是mac和ios上用来保存证书,密码,key 等等敏感信息的存储区. 有专门的api访问这些接口. 有时候我们为了获得一个 key 的实例,必须想办法把数据导入到keychain中,然后才能通过keychain获得一个key的实例.

在mac 上, 有三个方式实现加密解密签名服务:

  1. Security Transforms API —a Core-Foundation-level API that provides support for signing and verifying, symmetric cryptography, andBase64 encoding and decoding.
    是 CoreFoundation 级别的api, 提供了, 最全的功能和算法支持. 包括对称,非对称算法. 实现加密,签名功能. 但遗憾的是这个接口只在mac上有效. iOS上没有. 但有些功能必须要用到, 所以要实现跨平台的代码,需要一些补丁.

  2. Common Crypto—a C-level API that can perform most symmetric encryption and decryption tasks
    这是一个 C 风格的接口. 好消息是它在mac和ios上都有, 可以跨平台. 但坏消息是, 它只包含了, 对称加密算法, 却没有非对称算法. 因此只能加密解密,而不能签名和验证. 其实之前 Apple上还有一个 ComonCryptoRSA 模块, 但后来不知为何消失了.

  3. 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 上, 基本上有两种方式:

  1. 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
    &params,
    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 , 用起来非常简单.

代码阶段(AES对称加密)

@class NSString;  

@interface NSData (Encryption)  

- (NSData *)AES256ParmEncryptWithKey:(NSString *)key;   //加密  
- (NSData *)AES256ParmDecryptWithKey:(NSString *)key;   //解密  

@end  
#import "Encryption.h"  
#import <CommonCrypto/CommonCryptor.h>  

@implementation NSData (Encryption)  

- (NSData *)AES256ParmEncryptWithKey:(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;  
    voidvoid *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 *)AES256ParmDecryptWithKey:(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;  
    voidvoid *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;  
}  

转载:
1. http://www.cnblogs.com/shuidao/p/4392312.html
2. http://blog.csdn.net/showhilllee/article/details/2959724

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可以使用 OpenSSL 库中的 EVP 接口来进行 ECC 算法的非对称加密和解密。以下是一个简单的示例程序,可以对大文件进行加密和解密: ```c++ #include <openssl/evp.h> #include <openssl/ec.h> #include <openssl/ecdh.h> #include <openssl/err.h> #include <iostream> #include <fstream> using namespace std; int main(int argc, char* argv[]) { // 读取密钥文件 EC_KEY* ec_key = EC_KEY_new_by_curve_name(NID_secp256k1); BIO* key_bio = BIO_new_file("private_key.pem", "r"); PEM_read_bio_ECPrivateKey(key_bio, &ec_key, NULL, NULL); BIO_free(key_bio); // 初始化加密上下文 EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); EVP_CIPHER_CTX_init(ctx); EVP_EncryptInit_ex(ctx, EVP_ecies(), NULL, NULL, NULL); EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_ECIES_SET_CURVE_NAME, NID_secp256k1, NULL); EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_ECIES_SET_SHARED_INFO_DATA, "SharedInfo", strlen("SharedInfo"), NULL); // 打开输入文件 ifstream ifs(argv[1], ios::binary | ios::in); if (!ifs.good()) { cerr << "Failed to open input file." << endl; return 1; } // 打开输出文件 ofstream ofs(argv[2], ios::binary | ios::out); if (!ofs.good()) { cerr << "Failed to open output file." << endl; return 1; } // 循环读取并加密文件数据 unsigned char inbuf[4096], outbuf[4096 + EVP_MAX_CIPHER_BLOCK_SIZE]; int inlen, outlen; while (!ifs.eof()) { ifs.read(reinterpret_cast<char*>(inbuf), sizeof(inbuf)); inlen = ifs.gcount(); EVP_EncryptUpdate(ctx, outbuf, &outlen, inbuf, inlen); ofs.write(reinterpret_cast<char*>(outbuf), outlen); } // 结束加密并清理上下文 EVP_EncryptFinal_ex(ctx, outbuf, &outlen); ofs.write(reinterpret_cast<char*>(outbuf), outlen); EVP_CIPHER_CTX_free(ctx); // 关闭文件 ifs.close(); ofs.close(); // 读取密钥文件 ec_key = EC_KEY_new_by_curve_name(NID_secp256k1); key_bio = BIO_new_file("private_key.pem", "r"); PEM_read_bio_ECPrivateKey(key_bio, &ec_key, NULL, NULL); BIO_free(key_bio); // 初始化解密上下文 ctx = EVP_CIPHER_CTX_new(); EVP_CIPHER_CTX_init(ctx); EVP_DecryptInit_ex(ctx, EVP_ecies(), NULL, NULL, NULL); EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_ECIES_SET_CURVE_NAME, NID_secp256k1, NULL); EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_ECIES_SET_SHARED_INFO_DATA, "SharedInfo", strlen("SharedInfo"), NULL); // 打开输入文件 ifs.open(argv[2], ios::binary | ios::in); if (!ifs.good()) { cerr << "Failed to open input file." << endl; return 1; } // 打开输出文件 ofs.open(argv[3], ios::binary | ios::out); if (!ofs.good()) { cerr << "Failed to open output file." << endl; return 1; } // 循环读取并解密文件数据 while (!ifs.eof()) { ifs.read(reinterpret_cast<char*>(inbuf), sizeof(inbuf)); inlen = ifs.gcount(); EVP_DecryptUpdate(ctx, outbuf, &outlen, inbuf, inlen); ofs.write(reinterpret_cast<char*>(outbuf), outlen); } // 结束解密并清理上下文 EVP_DecryptFinal_ex(ctx, outbuf, &outlen); ofs.write(reinterpret_cast<char*>(outbuf), outlen); EVP_CIPHER_CTX_free(ctx); // 关闭文件 ifs.close(); ofs.close(); return 0; } ``` 在运行程序之前,需要先生成一对 ECC 密钥对,并将私钥写入到文件 private_key.pem 中。可以使用以下命令来生成密钥对: ``` openssl ecparam -name secp256k1 -genkey -noout -out private_key.pem openssl ec -in private_key.pem -pubout -out public_key.pem ``` 在运行程序时,需要指定输入文件、加密后输出文件和解密后输出文件的路径。例如: ``` ./ecc_encrypt_decrypt input_file encrypted_file decrypted_file ``` 需要注意的是,ECC 算法的加密和解密速度比较慢,对于大文件可能需要等待一些时间。此外,为了保证安全性,需要注意密钥的保护和传输安全等问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值