1. 程式人生 > >RSA之php私鑰簽名與android、ios公鑰加密

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流程測試結束