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对接的时候变成坑了),关于 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 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流程测试结束

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值