iOS 實現(xiàn)RSA公鑰解密

對于RSA加解密來說,一般就是公鑰加密、私鑰解密;或者私鑰加密、公鑰驗簽。并且在iOS的API中同樣也是提供了這兩種形式的方法。

SecKeyEncrypt(加密)
SecKeyDecrypt(解密)
SecKeyRawVerify(驗簽)

但是在做RSA加密的時候,客戶端保存的只會有一個公鑰,有些時候會需要進行公鑰解密。后來查了一些資料最后還是找到了解決方案:通過 openssl 來實現(xiàn)。

RSA_public_encrypt
RSA_private_encrypt
RSA_public_decrypt
RSA_private_decrypt


更新: 在后面的深入了解之后發(fā)現(xiàn),其實iOS原生也能做私鑰加密和公鑰加密。

iOS原生的兩個方法其實說的是加密和解密,并沒有指定是用那個密鑰做加解密。先大致看下RSA加解密的原理:

1.密鑰組成

公鑰 (E,N)
私鑰 (E,D,N)

關(guān)于每個數(shù)是怎么來的這里不介紹,感興趣的可以自己查一下,這里只做加解密的原理解釋。

2.加解密原理

現(xiàn)在已經(jīng)有了密鑰對,那再看下加解密的過程

密文 = 明文E mod N
明文 = 密文D mod N

我們通過一個具體的例子來直觀體驗下,經(jīng)過計算我們現(xiàn)在得到一對具體的密鑰對:

公鑰=(E,N) = (5,323)
私鑰=(D,N) = (29,323)

B = AE mod N = pow(123, 5) % 323 = 225
A = BD mod N = pow(225, 29) % 323 = 123

如果 A(123) 為明文,那上面的過程就是 公鑰加密私鑰解密;
如果 B(225) 為明文,那上面的過程就是 私鑰加密公鑰解密;

換一下順序可能會更清除一點:
A = BD mod N = pow(225, 29) % 323 = 123 (私鑰加密)
B = AE mod N = pow(123, 5) % 323 = 225 (公鑰解密)

這樣一來我們就會發(fā)現(xiàn),其實加解密是同一個方法。那為什么會有加密和解密兩個方法呢?我的理解是:

加密就是,傳入數(shù)據(jù)直接做計算(就像上面的那樣)
解密就是,傳入數(shù)據(jù)直接做計算(還是上面的那樣),不過會根據(jù)填充模式做數(shù)據(jù)處理,把填充的隨機數(shù)剔除掉。

3.填充格式

一般最常用的是 PKCS1。這個填充格式會要求每次加密的數(shù)據(jù)比密鑰長度短11個字節(jié)(keySize - 11),輸出長度跟密鑰長度一致

PS 為隨機填充數(shù),M為明文

00 02 | PS | 00 | M     (公鑰加密)
00 01 | PS | 00 | M     (私鑰加密)

還有一個是None就是不填充。這個的密文長度可以跟密鑰一樣長,但是如果比密鑰短的話會在明文的前面填充零(0)

0000 | M
填充方式 最大輸入長度 輸出長度 填充內(nèi)容
PKCS1 keySize - 11 keySize 隨機數(shù)
none keySize keySize 00

4.公鑰解密

現(xiàn)在知道了加密的原理和填充方式,那試一下

- (NSData * )decryptWithPublicKey:(SecKeyRef)publicKeyRef cipherData:(NSData * )cipherData {

    size_t keySize = SecKeyGetBlockSize(publicKeyRef) * sizeof(uint8_t);
    double totalLength = [cipherData length];
    size_t blockSize = keySize;
    int blockCount = ceil(totalLength / blockSize);
    NSMutableData * plainData = [NSMutableData data];
    for (int i = 0; i < blockCount; i++) {
        NSUInteger loc = i * blockSize;
        long dataSegmentRealSize = MIN(blockSize, totalLength - loc);
        NSData * dataSegment = [cipherData subdataWithRange:NSMakeRange(loc, dataSegmentRealSize)];
        unsigned char * plainBuffer = malloc(keySize);
        memset(plainBuffer, 0, keySize);
        OSStatus status = noErr;
        size_t plainBufferSize ;
        status = SecKeyDecrypt(publicKeyRef,
                               kSecPaddingNone, // 解密的時候一定要用 無填充模式,拿到所有的數(shù)據(jù)自行解析
                               [dataSegment bytes],
                               dataSegmentRealSize,
                               plainBuffer,
                               &plainBufferSize
                               );
        if(status != noErr){
            free(plainBuffer);
            return nil;
        }

        NSData * data = [[NSData alloc] initWithBytes:plainBuffer length:plainBufferSize];

        NSData * startData = [data subdataWithRange:NSMakeRange(0, 1)];
        // 開頭應(yīng)該是 0001 但是原生解出來之后把開頭的 00 忽略了
        if ([[startData description] isEqualToString:@"<01>"]) {
            Byte flag[] = {0x00};
            NSRange startRange = [data rangeOfData:[NSData dataWithBytes:flag length:1] options:NSDataSearchBackwards range:NSMakeRange(0, data.length)];
            NSUInteger s = startRange.location + startRange.length;
            if (startRange.location != NSNotFound && s < data.length) {
                data = [data subdataWithRange:NSMakeRange(s, data.length - s)];
            }
        }

        [plainData appendData:data];
        free(plainBuffer);
    }

    return plainData;
}

這里有幾個需要注意的點

1、 填充方式一定要為kSecPaddingNone,因為我們不希望系統(tǒng)幫我們做數(shù)據(jù)的截取,默認(rèn)解出來的所有數(shù)據(jù)都是我們需要的
2、當(dāng)加密的填充模式為PKCS1 ,理論上開頭應(yīng)該是 0001 的,但是NSData 會把開頭的 00 忽略掉。所以開頭就成了01。
3、 當(dāng)加密的填充模式為None, 理論上填充的應(yīng)該都是 0 的,同樣NSData 會把開頭的 00 忽略掉,所以解出來的就是真實的數(shù)據(jù)不用處理。

5.私鑰加密

既然說到公鑰解密就順便說一下私鑰加密,原生這個并沒有這幾的私鑰解密,即使傳入私鑰使用解密的方法也不行。但是可以通過另外一條思路來實現(xiàn)。

在上面的原理介紹中提到加密解密其實用的是同一個方法,所以我們可以使用解密的方法來進行加密。實驗證明是可以的。

但是要注意

1、填充模式要設(shè)置為kSecPaddingNone。因為這個畢竟是一個解密的方法,如果不用kSecPaddingNone內(nèi)部會做數(shù)據(jù)的處理,但是這并不是我們需要的,我們需要的是完整的數(shù)據(jù)。

2、 還因為這里用的是一個解密的方法,所以分段私鑰加密處理之后得到的數(shù)據(jù)長度并不一定等于密鑰的長度,但是在公鑰解密的時候是按密鑰長度進行分段解密的,這樣就是產(chǎn)生異常。處理方案:在私鑰加密之后如果密文長度小于密鑰長度,要在密文前面填充 0 ;


更詳細(xì)的東西可以看Demo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容