Mac和 iOS 下的對稱和非對稱加密算法的使用

分享在Mac 和 iOS 上使用到的對稱和非對稱加密算法. 包括RSA,DSA, AES, DES, 3DES 和 blowfish 等等.
由於要實現ssh協議, 因此用到了這些算法, 這些算法在mac和ios上的接口比較難用, 我在這折騰了好長時間, 這裏分享出來, 但願對你們有幫助.
(這裏不提 openssl 在 apple 上的接口了)html

主要參考了apple的文檔:ios

Cryptographic Services Guide算法

Apple Encrypting and Hashing Dataapi

先大體歸納一下總體狀況:
基本上這兩個平臺上的加密解密都和 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, and Base64 encoding and decoding.
    是 CoreFoundation 級別的api, 提供了, 最全的功能和算法支持. 包括對稱,非對稱算法. 實現加密,簽名功能. 但遺憾的是這個接口只在mac上有效. iOS上沒有. 但有些功能必需要用到, 因此要實現跨平臺的代碼,須要一些補丁.app

  2. Common Crypto—a C-level API that can perform most symmetric encryption and decryption tasks
    這是一個 C 風格的接口. 好消息是它在mac和ios上都有, 能夠跨平臺. 但壞消息是, 它只包含了, 對稱加密算法, 卻沒有非對稱算法. 所以只能加密解密,而不能簽名和驗證. 其實以前 Apple上還有一個 ComonCryptoRSA 模塊, 但後來不知爲什麼消失了.dom

  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接受後不久, 就被廢棄了. 如今已經不建議使用了. 因此就不提它了.ssh

在 iOS 上, 基本上有兩種方式:ide

  1. Common Crypto. 這個在上面已經說過了. 對稱算法接口.

  2. 使用系統提供的特有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)

這須要在兩個平臺獨立開發.

  1. 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)
  1. 對於 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

  1. 使用 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

相關文章
相關標籤/搜索