RSA算法是一種非對稱加密算法,常被用于加密數據傳輸.如果配合上數字摘要算法, 也可以用于文件簽名.
本文將討論如何在iOS中使用RSA傳輸加密數據.
本文環(huán)境
mac os
openssl-1.0.1j, openssl需要使用1.x版本, 推薦使用homebrew安裝.
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
#!/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:
- 在創(chuàng)建證書的時候, terminal會提示輸入證書信息. 根據wizard輸入對應信息就OK.
- 在創(chuàng)建p12密匙時, 會提示輸入密碼, 此時的密碼必須記住, 之后會用到.
- 如果上面指令有問題,請參考最新的openssl官方文檔, 以官方的為準. 之前在網上搜索指令, 被坑了一圈之后, 還是會到啃官方文檔上. 每條指令文檔在最后都會有幾個sample,參考sample即可.
iOS如何加載使用證書
將下面代碼添加到項目中:
https://gist.github.com/lvjian700/204c23226fdffd6a505d
代碼依賴 Base64編碼庫 , 如果使用cocoapods, 可以講下面依賴添加到Podfile:
pod 'Base64nl', '~> 1.2'
加密數據
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編碼的字符串:
console out 寫道
encrypted data: I1Mnu33cU7QcgaC9uo2bxV0vyfJSqAwyC3DZ+p8jm0G2EmcClarrR5R2xLDdXqvtKj+UJbES7TT+AgkK1NDoQvOJBY+jkmrpAchmRbV2jvi3cEZYQG955jrdSAu21NzQe8xWtEc3YzP+TACPdP4B3Cyy0u8N2RcSFWyxu0YKPXE=
解密數據
在iOS下解碼需要先加載private key, 之后在對數據解碼. 解碼的時候先進行Base64 decode, 之后在用private key decrypt加密數據.
NSLog(@"decryptor using rsa");
[rsa loadPrivateKeyFromFile:[[NSBundle mainBundle] pathForResource:@"private_key" ofType:@"p12"] password:@"123456"];
NSString *decryptedString = [rsa rsaDecryptString:encryptedString];
NSLog(@"decrypted data: %@", decryptedString);
之后會輸出解密后的數據:
console 寫道
decryptor using rsadecrypted data: hello ~
在服務器端解碼數據 ** (Java) **
在Java中解碼需要使用下述指令生成的pkcs8 private key:
gen shell 寫道
openssl pkcs8 -topk8 -in private_key.pem -out pkcs8_private_key.pem -nocrypt
具體解碼步驟:
加載pkcs8 private key:
讀取private key文件
去掉private key頭尾的"-----BEGIN PRIVATE KEY-----"和"-----BEGIN PRIVATE KEY-----"
刪除private key中的換行
對處理后的數據進行Base64解碼
使用解碼后的數據生成private key.
解密數據:
對數據進行Base64解碼
使用RSA decrypt數據.
這里我將iOS中"hello ~"加密的數據在Java中進行解碼:
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算法實現對文件的數字簽名.