Android客戶端與PHP服務端RES公鑰私鑰互加解密


本來並非全部原創,可以歸納為原理整理,所以如果看到不是原創的也不要見怪,回正題。

運行環境:

服務端:

CentOS 5.6 i386

PHP:5.3.3

OpenSSL: OpenSSL 0.9.8e-fips-rhel5 01 Jul 2008


客戶端:

Android Studio Beta 0.8.0


第一步:在服務端生成RSA的公鑰和私鑰

openssl genrsa -out rsa_private_key.pem 1024

openssl pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -nocrypt -out private_key.pem

openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem

第一條命令生成原始的RSA私鑰文件  rsa_private_key.pem , 第二條將原始的RSA私鑰轉換為PKCS8格式,第三條生成RSA公鑰rsa_public_key.pem ,從上面可以看出私鑰可以生成對應的公鑰。私鑰我們用的客戶端 ,公鑰可以發給Android或IOS。本文這里只測試 Android。

第二步:PHP服務端的函數使用OpenSSL

class Rsa{
private static $PRIVATE_KEY='-----BEGIN PRIVATE KEY-----
在這里寫上你的私鑰,注意格式要保留段落
-----END PRIVATE KEY-----';

private static $PUBLIC_KEY = '-----BEGIN PUBLIC KEY-----
這里是你的公鑰,注意格式,要保留段落
-----END PUBLIC KEY-----
';
public $clientPublicKey = '';

/**
* 設置客戶端的公鑰
*/
public function setClientPublicKey($cPubKey){
$this->clientPublicKey = $cPubKey;
}

/**
*獲取服務端私鑰
*/
private static function getPrivateKey()
{
$privKey = self::$PRIVATE_KEY;
$passphrase = '';
return openssl_pkey_get_private($privKey,$passphrase);
}

/**
* 服務端公鑰加密數據
* @param unknown $data
*/
public static function publEncrypt($data){
$publKey = self::$PUBLIC_KEY;
$publickey = openssl_pkey_get_public($publKey);
//使用公鑰進行加密
$encryptData = '';
openssl_public_encrypt($data, $encryptData, $publickey);
return base64_encode($encryptData);
}

/**
* 服務端私鑰加密
*/
public static function privEncrypt($data){
if(!is_string($data))
{
return null;
}
return openssl_private_encrypt($data,$encrypted,self::getPrivateKey())? base64_encode($encrypted) : null;
}

/**
* 服務端私鑰解密
*/
public static function privDecrypt($encrypted)
{
if(!is_string($encrypted)){
return null;
}
$privatekey = self::getPrivateKey();
$sensitivData = '';
//return (openssl_private_decrypt(base64_decode($encrypted), $decrypted, self::getPrivateKey()))? $decrypted : null;
openssl_private_decrypt(base64_decode($encrypted), $sensitivData, $privatekey);
//var_dump($sensitivData);
return $sensitivData;
}

/**
* 客戶端的公鑰解密數據
* @param unknown $publicKey
* @param unknown $encryptString
*/
public function clientPublicDecrypt($encryptString){
if(!is_string($encryptString)) return null;
$encodeKey = $this->clientPublicKey;
$publicKey = openssl_pkey_get_public($encodeKey);
if(!$publicKey) {
exit("\nClient Publickey Can not used");
}
$sensitivData = '';
openssl_public_decrypt(base64_decode($encryptString), $sensitivData, $publicKey);
return $sensitivData;
}

/**
* 客戶端公鑰加密數據
* @param string $string 需要加密的字符串
* @return string Base64編碼的密文
*/
public function clientPublicEncrypt($string){
$publKey = $this->clientPublicKey;
$publicKey = openssl_pkey_get_public($publKey);
if(!$publicKey) {
exit("\nClient Publickey Can not used");
}
//使用公鑰進行加密
$encryptData = '';
openssl_public_encrypt($string, $encryptData, $publicKey);
return base64_encode($encryptData);
}

public function formatKey($key, $type = 'public'){
if($type == 'public'){
$begin = "-----BEGIN PUBLIC KEY-----\n";
$end = "-----END PUBLIC KEY-----";
}else{
$begin = "-----BEGIN PRIVATE KEY-----\n";
$end = "-----END PRIVATE KEY-----";
}
//$key = ereg_replace("\s", "", $key);
$key= preg_replace('/\s/','',$key);
$str = $begin;
$str .= substr($key, 0,64);
$str .= "\n" . substr($key, 64,64);
$str .= "\n" . substr($key, 128,64);
$str .= "\n" . substr($key,192,24);
$str .= "\n" . $end;
return $str;
}

}

需要說明這么兩 點:1、 PHP端 的公鑰和私鑰都是有頭和尾的注釋的,而Android是沒有的,所以在PHP在使用Android的公鑰時要加上-----BEGIN PUBLIC KEY----- ,反之Android要去掉。2、PHP端還是要段落的、所以要用的formatKey這個方法,要注意Android客戶端的KEY也是有段落的

使用方法:

$client_public_key = "這里寫上你的客戶端的公鑰";
$rsa = new Rsa();
$clientPublicKey = $rsa->formatKey($client_public_key);
$rsa->setClientPublicKey($clientPublicKey);

echo $rsa->clientPublicDecrypt("這里是客戶端私鑰加密的密文");

如何使用服務端私鑰加密這里就不再寫了,看上面方法的注釋就可以看到。

第三步:Android的公鑰私鑰以及如何加密和解密

先上Android的主函數


/**
* Created by dyb on 2014/7/8.
*/
public class RSACodeHelper {
private static final String TAG = "RSACodeHelper";
private static final String RSATYPE = "RSA/ECB/PKCS1Padding"; //Cipher必須用這種類型
public PublicKey mPublicKey; //這里要注意一下,原來用的類型是RSAPublicKey 但死活就是解不了服務端私鑰加密的密文改成PublicKey就可以了
public PrivateKey mPrivateKey; //同上

public void init(){
KeyPairGenerator keyPairGen = null;
try {
//設置使用哪種加密算法
keyPairGen = KeyPairGenerator.getInstance("RSA");
//密鑰位數
keyPairGen.initialize(1024); //一定要和服務端的長度保持一致
//密鑰對
KeyPair keyPair = keyPairGen.generateKeyPair();
//公鑰
mPublicKey = keyPair.getPublic();
//私鑰
mPrivateKey = keyPair.getPrivate();
MyLog.i(TAG,"RSA 構造函數完成");
}catch (NoSuchAlgorithmException e){
e.printStackTrace();
}
}

/**
* 取得公鑰
* @param key 公鑰字符串
* @return 返回公鑰
* @throws Exception
*/
public static PublicKey getPublicKey(String key) throws Exception{
byte[] keyBytes = base64Dec(key);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(keySpec);
MyLog.d(TAG,"Cipher.getInstance:"+keyFactory.getAlgorithm());
return publicKey;
}

/**
* 取得私鑰
* @param key
* @return
* @throws Exception
*/
public static PrivateKey getPrivateKey(String key) throws Exception{
byte[] keyBytes = base64Dec(key);

PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
return privateKey;

}

/**
* 使用客戶端公鑰加密字符串
* @param str 需要加密的字符串
* @return 密文
*/
public String cPubEncrypt(String str){
String strEncrypt = null;

//實例化加解密類
try {
Cipher cipher = Cipher.getInstance(RSATYPE);
//明文
byte[] plainText = str.getBytes();
//加密
cipher.init(Cipher.ENCRYPT_MODE,mPublicKey);
//將明文轉化為根據公鑰加密的密文,為byte數組格式
byte[] enBytes = cipher.doFinal(plainText);
strEncrypt = base64Enc(enBytes);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
}catch (InvalidKeyException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
}finally {
return strEncrypt;
}
}

/**
* 使用服務端的公鑰加密
* @param str
* @return
*/
public String sPubEncrypt(String str){
String strEncrypt = null;
try {
//轉換服務端的公鑰
PublicKey publicKey = getPublicKey("這個參數是你服務端的公鑰,可以使用自己的方式來保存公鑰");
//實例化加密類
Cipher cipher = Cipher.getInstance(RSATYPE);
//取得明文的二進制
byte[] plainText = str.getBytes();
//加密
cipher.init(Cipher.ENCRYPT_MODE,publicKey);
byte[] enBytes = cipher.doFinal(plainText);
strEncrypt = base64Enc(enBytes);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}finally {
return strEncrypt;
}
}

/**
* 使用客戶端公鑰解密
* @param encString
* @return
*/
public String cPubDecrypt(String encString){
Cipher cipher = null;
String strDecrypt = null;
try {
cipher = Cipher.getInstance(RSATYPE);
cipher.init(Cipher.DECRYPT_MODE,mPublicKey);
//先將轉為Base64編碼的加密后數據轉化為Byte數組
byte[] enBytes = base64Dec(encString);
//解密為byte數組,應該為字符串數組,最后轉化為字符串
byte[] deBytes = cipher.doFinal(enBytes);
//strDecrypt = base64Enc(deBytes);
strDecrypt = new String(deBytes);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
}finally {
return strDecrypt;
}
}
/**
* 使用客戶端私鑰解密
* @param encString
* @return
*/
public String cPriDecrypt(String encString){
Cipher cipher = null;
String strDecrypt = null;
try {
cipher = Cipher.getInstance(RSATYPE);
cipher.init(Cipher.DECRYPT_MODE,mPrivateKey);
//先將轉為Base64編碼的加密后數據轉化為Byte數組
MyLog.i(TAG,"string Lenght:" +encString+":"+ encString.length());
byte[] enBytes = base64Dec(encString);
//解密為byte數組,應該為字符串數組,最后轉化為字符串
byte[] deBytes = cipher.doFinal(enBytes);
strDecrypt = new String(deBytes);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
}finally {
return strDecrypt;
}
}

/**
* 使用客戶端私鑰加密
* @param deString
* @return
*/
public String cPriEncrypt(String deString){
String strEncrypt = null;

//實例化加解密類
try {
Cipher cipher = Cipher.getInstance(RSATYPE);
//明文
byte[] plainText = deString.getBytes();
//加密
cipher.init(Cipher.ENCRYPT_MODE,mPrivateKey);
//將明文轉化為根據公鑰加密的密文,為byte數組格式
byte[] enBytes = cipher.doFinal(plainText);
strEncrypt = base64Enc(enBytes);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
}catch (InvalidKeyException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
}finally {
return strEncrypt;
}
}
/**
* base64編碼
* @param enBytes
* @return
*/
public static String base64Enc(byte[] enBytes){
return Base64.encodeToString(enBytes,Base64.DEFAULT);
}

/**
* base64解碼
* @param str
* @return
*/
public static byte[] base64Dec(String str){
return Base64.decode(str,Base64.DEFAULT);
}

}


服務端公鑰在Android端的存在形式,這是我個人的方法:

public static final String RSA_PUBLIC = "第一行" +"\r"+
"第二行" + "\r"+
"第三行" +"\r"+
"第四行";

然后在RSACodeHelper.java 中使用服務端公鑰解密的時候,把這里的參數傳進去就可以了。



具體使用方法:

RSACodeHelper rsaCodeHelper = new RSACodeHelper();
rsaCodeHelper.init();
String str = "123456";
String strPublicKey = rsaCodeHelper.base64Enc(rsaCodeHelper.mPublicKey.getEncoded());
MyLog.d(TAG,"客戶端公鑰"+ strPublicKey);
//String sPubEnString = rsaCodeHelper.sPubEncrypt("123456");
//MyLog.d(TAG,"服務端公鑰加密后:"+sPubEnString);

String strClientPubEncrypt = rsaCodeHelper.cPubEncrypt(str);
//MyLog.i(TAG,"客戶端公鑰加密密文:"+ strClientPubEncrypt);
//MyLog.i(TAG,"客戶端私鑰解為明文1:" + rsaCodeHelper.cPriDecrypt(strClientPubEncrypt));


//客戶端私鑰加密,公鑰解密
String clientPrivateEncrypt = rsaCodeHelper.cPriEncrypt(str);
MyLog.i(TAG,"客戶端私鑰加密密文:"+ clientPrivateEncrypt);
MyLog.i(TAG,"客戶端公鑰解密:" + rsaCodeHelper.cPubDecrypt(clientPrivateEncrypt));

需要說明一點,用這種方法取得的公鑰,每次都不一樣,所以最好把公鑰和私鑰都保存起來,然后把公鑰發給服務端。這樣服務端才可以正在解密。

原來沒有注意,害我好苦,什么原因都找了一天也沒有找到為什么服務端解密不了客戶端用私鑰加密的密文。后來才發現客戶端每次生成的都是不一樣的,用不同的公鑰去解密文肯定是解不開的。

所以我們要把客戶端的公鑰和私鑰都存起來,使用的時候可以調用:PublicKey publicKey = getPublicKey(”這里寫上從存儲中讀來的公鑰“);

當然私鑰就是:PrivateKey privateKey = getPrivateKey("傳入從服務端讀來的私鑰");

因此,這個函數還是需要改進的,就是在初始化init()的時候,要傳入我們存儲的公鑰和私鑰,以保證客戶端和服務端的統一。


在PHP中如果解密失敗返回的是空

openssl_public_decrypt(base64_decode($encryptString), $sensitivData, $publicKey);  失敗會返回空值。

同樣在Android中 

byte[] deBytes = cipher.doFinal(enBytes); 該方法如果解密失敗也會返回 Null 


該方法為本來的一次測試實現如下功能:

PHP 私鑰加密后 Android可以使用PHP的公鑰來解密。

PHP 拿Android 的公鑰來加密,Android可以用自己的私鑰來解密

Android 使用私鑰加密后 PHP可以使用Android 的公鑰來解密

ANdroid 用PHP的公鑰加密 PHP可以用自己的私鑰來解密。

總之:私鑰都是自己留着,公鑰可以發給對方。用自己的私鑰加密,對方有自己手里的公鑰可以解密。 拿對方的公鑰來加密,對方肯定有他自己的私鑰自己來解嚴就是了。

在查詢資料的時候有人遇到過這樣的問題,因為Base64在傳送的時候有URL中的一些限制,所以要進行UrlEncode編碼,但IOS好像會自動進行編碼。


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
  © 2014-2022 ITdaan.com 联系我们: