您的位置:首页 > 移动开发 > Android开发

RSA之php私钥签名与android、ios公钥加密

2016-07-12 17:02 351 查看
做公司项目时,考虑到后期的数据安全,决定采用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对接的时候变成坑了),关于
signature_alg(signature
algorithm
)请参考 Java Cryptography Architecture API Specification & Reference
中的附录 A


后端还要私钥解密数据,这里就不列出私钥了,参考上面的私钥格式。

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 encryptWithPub
8139
licKey:@"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流程测试结束
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: