RSA之php私鑰簽名與android、ios公鑰加密
做公司專案時,考慮到後期的資料安全,決定採用rsa演算法加密。
先科普下,RSA演算法是一種非對稱演算法,演算法需要一對金鑰,使用其中一個加密,需要使用另外一個才能解密。我們在進行RSA加密通訊時,就把公鑰放在客戶端,私鑰留在伺服器。由於ios公鑰解密需要第三方庫並且很耗效能,所以採用了後端(PHP)私鑰簽名->客戶端公鑰驗證簽名,客戶端公鑰加密->後端(PHP)私鑰解密。
首先在伺服器端通過openssl生成私鑰和公鑰(openssl安裝與配置請另找資料),在網上看到有文章說ios識別不了pem格式的公鑰,需要提供der格式(後來ios用了pem。。)
生成私鑰的同時生成der格式的公鑰(生成私鑰的過程當中會讓你輸入私鑰密碼和公司個人資訊)
openssl req -x509 -out public_key.der -outform der -new -newkey rsa:1024 -keyout private_key.pem
通過私鑰生成pem格式公鑰(需要輸入私鑰密碼)
openssl rsa -in private_key.pem -pubout -out public_key.pem
私鑰儲存在伺服器端,保密性強,這裡是PHP,PHP使用openssl擴充套件。
將私鑰中的字串完整的複製到程式碼中(這裡私鑰僅供參考格式,使用時請替換成自己生成的私鑰),在讀取私鑰時還需要私鑰密碼,然後對傳輸的資料進行簽名後base64加密(只有加入簽名的資料才能保證不被串改)。
public function test_sign_rsa(){ $private_key = '-----BEGIN ENCRYPTED PRIVATE KEY----- MIICxjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQILXiWgwL2RPMCAggA MBQGUTqGSIb3DQMHBAh8CsWwJ2LzwQSCAoAFYUKruMB6IXhdTz7+1pDfGFuawQAq n2YplQ0NUnOjE4SFaC821A9Ew6Lp7YJN2b6ol3BR/V4VDmrKtIZr3QFTjTlfgcBF wYETRXcKuAgD/f+a2EzEAKvIEcd7ykm00qi8qVLX7pSNtdkVHWxd9rLsTt/9GhSs ic3Wlons2cBdM6G1luIsRcOmgMxqxxmutjqGB4JPapJ1EVeSgFq/akkOCSa+o5RJ ClzD+cxVQomtSwASrMwAwHzGd0ulu6djdi58GW0Qi0QMWLaTsFTmi0oiL+U2lAXO 2qRpOr0iyamf7rSg//+KN059m7gUtsbyRSJWs7tuGnZ1zKhZz/f1ALUI3p4N43tV WjJr5PqKtqLfajDahrVcn6G/f49WCMEdgICv3mKCum48+n/zdIUrsZ5XjuqryOCY pYdv9931T/wIjOe6D6iDQNIHAhG2N/oYDcKG7MriHgAXOviR57LHEIY9PdgzVNfL aL7kLAfUTsmjwYSOIH7tOyyWTGCJIfrw6S3xmUedsAzk9Hg5Nb8SvkPq2lPf7OhM kJZpQrIHyvFZ2A/fwwY2ioiTCf4PABG/vRtX+1/EGjpWs9Z+AqTeyDrRxXJhAz+G 7GFHmtOzTKlyNJYn/ZBAdaF/drDaiZA0/DnBzDScpC021ALMw2a90Whi7cDOT5PS vAGlzj+R3lkjJkuKaE5bUFI90Drwpi8JNhVAkRi0zyDAnlZMq0G3s+4L4BMPVWBz 3dZGG1jhO6q1feFe62vcoYU1nBkxMnk0VsMUbIIn7PyDaWckNNePIKhTn1XHK2j7 gQJ7onP3IoNu5Ef6yqFJC60vpDAPaCuLnX4wE1/qwexlckI/kjd+JoyT -----END ENCRYPTED PRIVATE KEY-----'; $pi_key = openssl_pkey_get_private($private_key,"私鑰密碼"); $sign = ''; $data = "hello"; if(openssl_sign($data,$sign,$pi_key)){ $sign = base64_encode($sign); echo JSONP(array( "msg" => $data, "sign" => $sign )); return; } }
這裡的openssl_sign方法其實還隱藏了一個引數,也就是說當我最後一個引數不傳時是預設以SHA1withRSA的方式進行簽名的(為什麼要提這個,因為和android對接的時候變成坑了),關於
後端還要私鑰解密資料,這裡就不列出私鑰了,參考上面的私鑰格式。
public function test_des_rsa(){
$private_key = '填寫私鑰';
$pi_key = openssl_pkey_get_private($private_key,"私鑰密碼");
$data = $_POST["msg"];
$sign = $_POST["sign"];
$des_sign = '';
openssl_private_decrypt(base64_decode($sign),$des_sign,$pi_key);
wjc_log($des_sign);//日誌紀錄解密結果
echo JSONP(array("des_sign"=>$des_sign));//沒有JSONP方法請替換成json_encode
return;
}
然後android呼叫介面並驗證簽名,當時參考了這篇文章 http://blog.csdn.net/wangbaochu/article/details/45058061,然後發現總是簽名失敗,當我開始懷疑我的證書時,突然就注意到了SIGNATURE_ALGORITHM,android在那裡寫著MD5withRSA,然後我的直覺就引導我去查了下文件,發現比較常用的有MD5withRSA和SHA1withRSA,那麼問題來了,PHP端用了哪種演算法,於是就有了上面對openssl_sign的吐槽,android這裡改成SHA1withRSA後驗證成功。接著測試公鑰加密的方法,發現PHP無法解密,PHP已做過測試,肯定是android的加密方法錯誤,於是將那個超級繁雜的方法替換掉,測試成功(這年頭資料多,正確的少啊),下面貼測試成功的程式碼
import android.util.Base64;
import java.io.ByteArrayOutputStream;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
/**
* Created by Administrator on 2016/7/12.
* Rsa加解密演算法
*/
public class RSAUtils {
public static final String KEY_ALGORITHM = "RSA";
public static final String SIGNATURE_ALGORITHM = "MD5withRSA";
public static final String SIGNATURE_ALGORITHMSHA = "SHA1withRSA";
private static final String PUBLIC_KEY = "RSAPublicKey";
private static final String PRIVATE_KEY = "RSAPrivateKey";
private static final int MAX_ENCRYPT_BLOCK = 117;
private static final int MAX_DECRYPT_BLOCK = 128;
/**
* 生成RSA的公私祕鑰對
*/
public static Map<String, Object> genKeyPair() throws Exception {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
keyPairGen.initialize(1024);
KeyPair keyPair = keyPairGen.generateKeyPair();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
Map<String, Object> keyMap = new HashMap<String, Object>(2);
keyMap.put(PUBLIC_KEY, publicKey);
keyMap.put(PRIVATE_KEY, privateKey);
return keyMap;
}
/**
* SHA1+RSA 簽名校驗演算法
* @param data 原始的資料
* @param publicKey RSA解密公鑰
* @param sign 簽名過的資料經過Base64之後的字串
* @return
*/
public static boolean verify(byte[] data, String publicKey, String sign) throws Exception {
byte[] keyBytes = Base64.decode(publicKey, Base64.DEFAULT);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PublicKey publicK = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHMSHA);
signature.initVerify(publicK);
signature.update(data);
return signature.verify(Base64.decode(sign, Base64.DEFAULT));
}
/**
* 使用公鑰加密
* @param content
* @param public_key
* @return
*/
public static String encryptByPublic(String content,String public_key) {
try {
PublicKey pubkey = getPublicKeyFromX509(KEY_ALGORITHM, public_key);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, pubkey);
byte plaintext[] = content.getBytes("UTF-8");
byte[] output = cipher.doFinal(plaintext);
String s = new String(Base64.encode(output,Base64.DEFAULT));
return s;
} catch (Exception e) {
return null;
}
}
/**
* 得到公鑰
* @param algorithm
* @param bysKey
* @return
*/
private static PublicKey getPublicKeyFromX509(String algorithm,
String bysKey) throws NoSuchAlgorithmException, Exception {
byte[] decodedKey = Base64.decode(bysKey,Base64.DEFAULT);
X509EncodedKeySpec x509 = new X509EncodedKeySpec(decodedKey);
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
return keyFactory.generatePublic(x509);
}
/**
* 獲取私鑰
*/
public static String getPrivateKey(Map<String, Object> keyMap) throws Exception {
Key key = (Key) keyMap.get(PRIVATE_KEY);
return Base64.encodeToString(key.getEncoded(), Base64.DEFAULT);
}
/**
* 獲取公鑰
*/
public static String getPublicKey(Map<String, Object> keyMap) throws Exception {
Key key = (Key) keyMap.get(PUBLIC_KEY);
return Base64.encodeToString(key.getEncoded(), Base64.DEFAULT);
}
}
最後是ios的驗證和加密,順利測試成功
- (void)viewDidLoad {
[super viewDidLoad];
NSString *publicKeyFilePath = [[NSBundle mainBundle] pathForResource:@"public_key.pem" ofType:nil];
HBRSAHandler* handler = [HBRSAHandler new];
[handler importKeyWithType:KeyTypePublic andPath:publicKeyFilePath];
_handler = handler;
[self validation];
[self postRSA];
}
/**
* 加密
*/
- (void)postRSA
{
NSString* result = [_handler encryptWithPublicKey:@"what the fuck"];
NSDictionary *dic = @{@"sign":result};
[MHNetworkManager postReqeustWithURL:@"http://域名.com/test/test_des_rsa" params:dic successBlock:^(NSDictionary *returnData) {
NSLog(@"----- %@",returnData);
} failureBlock:^(NSError *error) {
NSLog(@"%@",error);
} showHUD:NO];
}
/**
* 驗證
*/
- (void)validation {
BOOL result = [_handler verifyString:@"hello"withSign:@"簽名"];
NSLog(@"%@",[NSString stringWithFormat:@"驗證簽名結果(1成功,0失敗): %d",result]) ;
}
至此rsa流程測試結束