RSA演算法是一種非對稱加密演算法,常被用於加密資料傳輸.如果配合上數字摘要演算法, 也可以用於檔案簽名.
本文將討論如何在iOS中使用RSA傳輸加密資料.
本文環境
- mac os
- openssl-1.0.1j, openssl需要使用1.x版本, 推薦使用[homebrew](http://brew.sh/)安裝.
- Java 8
RSA基本原理
RSA使用”祕匙對”對資料進行加密解密.在加密解密資料前,需要先生成公鑰(public key)和私鑰(private key).
- 公鑰(public key): 用於加密資料. 用於公開, 一般存放在資料提供方, 例如iOS客戶端.
- 私鑰(private key): 用於解密資料. 必須保密, 私鑰洩露會造成安全問題.
iOS中的Security.framework提供了對RSA演算法的支援.這種方式需要對密匙對進行處理, 根據public key生成證照, 通過private key生成p12格式的密匙.
除了Secruty.framework, 也可以將openssl庫編譯到iOS工程中, 這可以提供更靈活的使用方式.
本文使用Security.framework的方式處理RSA.
使用openssl生成密匙對
Github Gist: https://gist.github.com/lvjian700/635368d6f1e421447680
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#!/usr/bin/env bash echo "Generating RSA key pair ..." echo "1024 RSA key: private_key.pem" openssl genrsa -out private_key.pem 1024 echo "create certification require file: rsaCertReq.csr" openssl req -new -key private_key.pem -out rsaCertReq.csr echo "create certification using x509: rsaCert.crt" openssl x509 -req -days 3650 -in rsaCertReq.csr -signkey private_key.pem -out rsaCert.crt echo "create public_key.der For IOS" openssl x509 -outform der -in rsaCert.crt -out public_key.der echo "create private_key.p12 For IOS. Please remember your password. The password will be used in iOS." openssl pkcs12 -export -out private_key.p12 -inkey private_key.pem -in rsaCert.crt echo "create rsa_public_key.pem For Java" openssl rsa -in private_key.pem -out rsa_public_key.pem -pubout echo "create pkcs8_private_key.pem For Java" openssl pkcs8 -topk8 -in private_key.pem -out pkcs8_private_key.pem -nocrypt echo "finished." |
Tips:
- 在建立證照的時候, terminal會提示輸入證照資訊. 根據wizard輸入對應資訊就OK.
- 在建立p12密匙時, 會提示輸入密碼, 此時的密碼必須記住, 之後會用到.
- 如果上面指令有問題,請參考最新的openssl官方文件, 以官方的為準. 之前在網上搜尋指令, 被坑了一圈之後, 還是會到啃官方文件上. 每條指令文件在最後都會有幾個sample,參考sample即可.
iOS如何載入使用證照
將下面程式碼新增到專案中:
https://gist.github.com/lvjian700/204c23226fdffd6a505d
程式碼依賴Base64編碼庫, 如果使用cocoapods, 可以講下面依賴新增到Podfile:
1 |
pod 'Base64nl', '~> 1.2' |
加密資料
1 2 3 4 5 6 7 8 9 10 |
RSAEncryptor *rsa = [[RSAEncryptor alloc] init]; NSLog(@"encryptor using rsa"); NSString *publicKeyPath = [[NSBundle mainBundle] pathForResource:@"public_key" ofType:@"der"]; NSLog(@"public key: %@", publicKeyPath); [rsa loadPublicKeyFromFile:publicKeyPath]; NSString *securityText = @"hello ~"; NSString *encryptedString = [rsa rsaEncryptString:securityText]; NSLog(@"encrypted data: %@", encryptedString); |
__[rsa rsaEncryptString:securityText]__會返回decrypted base64編碼的字串:
解密資料
在iOS下解碼需要先載入private key, 之後在對資料解碼. 解碼的時候先進行Base64 decode, 之後在用private key decrypt加密資料.
1 2 3 4 |
NSLog(@"decryptor using rsa"); [rsa loadPrivateKeyFromFile:[[NSBundle mainBundle] pathForResource:@"private_key" ofType:@"p12"] password:@"123456"]; NSString *decryptedString = [rsa rsaDecryptString:encryptedString]; NSLog(@"decrypted data: %@", decryptedString); |
之後會輸出解密後的資料:
decrypted data: hello ~
在伺服器端解碼資料(Java)
在Java中解碼需要使用下述指令生成的pkcs8 private key:
具體解碼步驟:
- 載入pkcs8 private key:
- 讀取private key檔案
- 去掉private key頭尾的”—–BEGIN PRIVATE KEY—–“和”—–BEGIN PRIVATE KEY—–“
- 刪除private key中的換行
- 對處理後的資料進行Base64解碼
- 使用解碼後的資料生成private key.
- 解密資料:
- 對資料進行Base64解碼
- 使用RSA decrypt資料.
這裡我將iOS中”hello ~”加密的資料在Java中進行解碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Paths; import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.util.Base64; import static java.lang.String.format; public class Encryptor { public static void main(String[] args) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException { PrivateKey privateKey = readPrivateKey(); String message = "AFppaFPTbmboMZD55cjCfrVaWUW7+hZkaq16Od+6fP0lwz/yC+Rshb/8cf5BpBlUao2EunchnzeKxzpiPqtCcCITKvk6HcFKZS0sN9wOhlQFYT+I4f/CZITwBVAJaldZ7mkyOiuvM+raXMwrS+7MLKgYXkd5cFPxEsTxpMSa5Nk="; System.out.println(format("- decrypt rsa encrypted base64 message: %s", message)); // hello ~, encrypted and encoded with Base64: byte[] data = encryptedData(message); String text = decrypt(privateKey, data); System.out.println(text); } private static String decrypt(PrivateKey privateKey, byte[] data) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); cipher.init(Cipher.DECRYPT_MODE, privateKey); byte[] decryptedData = cipher.doFinal(data); return new String(decryptedData); } private static byte[] encryptedData(String base64Text) { return Base64.getDecoder().decode(base64Text.getBytes(Charset.forName("UTF-8"))); } private static PrivateKey readPrivateKey() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { byte[] privateKeyData = Files.readAllBytes( Paths.get("/Users/twer/macspace/ios_workshop/Security/SecurityLogin/tools/pkcs8_private_key.pem")); byte[] decodedKeyData = Base64.getDecoder() .decode(new String(privateKeyData) .replaceAll("-----\w+ PRIVATE KEY-----", "") .replace("n", "") .getBytes()); return KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decodedKeyData)); } } |
直行成功後控制檯會輸出”hello ~”.
總結
這種加密傳輸方式會被用在網銀類App中.雖然網銀會採用全站https方案, 但是在安全登入這塊會使用另一個證照對登入資訊加密, 這樣可以雙層確保資料安全.
基於RSA加密解密演算法, 還可以將其運用在數字簽名場景.以後有空在聊如何使用RSA演算法實現對檔案的數字簽名.