使用RSA在Java端私钥加密,PHP端公钥验证
工作需要研究下RSA加密解密算法,一不小心差点把自己搞疯了,主要是我们服务器是PHP,而客户端肯定以后是Java了。所以我需要的是在Java端用私钥加密,而服务器用公钥进行验证。在这两端进行RSA加密解密需要严格的要求,这里我主要做一下记录。
首先是Java端:
-
使用的是javax.crypto.Cipher库,一般来说最好使用这个库,因为Android里面也带有这个库,而且使用起来比较方便。
-
生成私钥公钥对
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); //密钥位数 keyPairGen.initialize(1024); //密钥对 KeyPair keyPair = keyPairGen.generateKeyPair(); // 公钥 PublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); // 私钥 PrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
-
获取私钥,使用PKCS8EncodedKeySpec来生成,Base64库最好使用org.apache.commons.codec4
byte[] keyBytes; keyBytes = Base64.decodeBase64(key); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
-
这里需要注意的是获取instance的时候必须是RSA/ECB/PKCS1Padding,否则php是不支持pkcs8的。
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); cipher.init(Cipher.DECRYPT_MODE, ppkey);
-
RSA/ECB/PKCS1Padding好像加密的时候,好像不能一次性对超过117个字节的数据加密,可以做如下的处理:
byte[] enBytes = null; int i = 0; for (i = 0; i < ptext.length; i += 64) { // 注意要使用2的倍数,否则会出现加密后的内容再解密时为乱码 byte[] doFinal = cipher.doFinal(ArrayUtils.subarray(ptext, i,i + 64)); enBytes = ArrayUtils.addAll(enBytes, doFinal); } return enBytes;
-
需要注意的是这里加密的话,最好对原文进行MD5之后,在加密,那么在PHP端,进行验证的时候,只要验证原文的MD5值就行了,因为PHP的openssl对于比较长的字符串,好像并不能进行处理,Java没有此问题。就是这里困了我好久。
服务器PHP端
-
保存公钥,放到文件中,还是数据库中随便,不过需要注意的是Java中的公钥、私钥只有字符串就OK,但是PHP中的是有指定格式的,必须这样:
-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCFtYP+8zOR2WPJe/0c0w3kf3vmzcDksDPFgaqI psR+587tPm5YgZc53ii90wXsmjwVedU8yM8aZYpg7ZZALAximTR/JnK3A/+ffQG7lmNM0zC9StsD S3rD9bNIj0jGHUBT5Dxf98+TnEojWECeg5ZibZ51xzN8tChqB7MmApBIGwIDAQAB -----END PUBLIC KEY-----
可以通过下面的函数处理:
private function setupPubKey($pubkey) { if (is_resource ( $pubKey )) { return true; } $pem = chunk_split($pubkey,64,"\n"); $pem = "-----BEGIN PUBLIC KEY-----\n".$pem."-----END PUBLIC KEY-----\n"; $pubkey = openssl_pkey_get_public ( $pem ); return $pubkey; }
-
需要注意的是,这里验证的是原文的md5值,这样:
$result = (bool)openssl_verfify(md5($plainText),base64_decode($sign), OPENSSL_ALGO_DSS1);
必须使用OPENSSL_ALGO_DSS1,我也不知道为什么,估计是和RSA/ECB/PKCS1Padding对应,
不过在使用alipay或者 wandoujia进行签名验证的时候,可能不需要这个参数,请酌情增、删。
这个东西话费了我很多时间才调通,差点放弃,语言间对于RSA的实现会有所不同,描述也会有所不同。