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 联系我们: