由于最近公司与第三方平台要做兑换券的销售,于是需要写接口对接,对方文档写到,需要通过RSA非对称加密,对兑换码进行加密再传输,然而对方并没有SDK提供,提供了个JAVA的类,但是我们平台是使用php开发web,没办法,只能自己研究,当然也从这个过程中学到不少东西,特此记录一些关键点。
公钥私钥的格式
关于RSA加密的原理,可以自己搜索,RSA只能用于加密一些长度不长的数据。当然也有补充方案,就是分段加密。
查看了对方java代码,可以知道是 X.509 证书资源
,然后一般平台给出去的公钥是16进制字符串,php关于RSA加密、解密的函数 openssl_pkey_get_public 可以解密的证书:
- 一个 X.509 证书资源
- 一个file://path/to/file.pem格式的字符串。文件名必须包含一个PEM编码的证书或者密钥(也许二者都有).
- 一个 PEM 格式的公钥。
所以首先我们需要将对方给的16进制数转为PEM格式的公钥内容。下面是一个公钥的示例:
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC3//sR2tXw0wrC2DySx8vNGlqt
3Y7ldU9+LBLI6e1KS5lfc5jlTGF7KBTSkCHBM3ouEHWqp1ZJ85iJe59aF5gIB2kl
Bd6h4wrbbHA2XE1sq21ykja/Gqx7/IRia3zQfxGv/qEkyGOx+XALVoOlZqDwh76o
2n1vP1D+tD3amHsK7QIDAQAB
-----END PUBLIC KEY-----
PEM格式的公钥有开头和结尾的两个标示,然后每64位进行换行
php 转换十六进制公钥:
// 将十六进制,转换回普通字符串
$publicStr = base64_encode(hex2bin($public_key));
// 每64位进行换行
$key = (wordwrap($publicStr, 64, "\n", true))."\n";
// 添加pem格式头和尾
$pem_key = "-----BEGIN PUBLIC KEY-----\n" . $key . "-----END PUBLIC KEY-----\n";
这样,我们就得到了正常PEM证书中的字符串格式
RSA加密和解密
// public key encrypt
public function encryptByPublicKey($data, $public_key)
{
// 从证书中得到资源
$publicKeyPEM = openssl_pkey_get_public($public_key);
// 加密
$result = openssl_public_encrypt($data, $encrypted, $publicKeyPEM, OPENSSL_PKCS1_PADDING);
// 返回数据
return $result ? base64_encode($encrypted) : false;
}
public function decryptByPublicKey($data, $public_key)
{
// 从证书中得到资源
$publicKeyPEM = openssl_pkey_get_public($public_key);
// 解密
$result = openssl_public_decrypt(base64_decode($data), $decrypted, $publicKeyPEM, OPENSSL_PKCS1_PADDING);
// 返回数据
return $result ? $decrypted : false;
}
私钥同理,我们项目只用到了公钥
- 加密后需要进行
base64_encode
,因为加密后的数据直接输出是乱码,保证数据传输过程中不会丢失和出错 OPENSSL_PKCS1_PADDING
其实可以不写,是函数的默认值,这个是数据的填充方式,还有其他的值,这个需要询问对方使用的方式,一般默认这个,可以查看php文档:openssl_public_encrypt- 这里没有进行明文长度的限制,因为我们加密的东西是定长12位字符串,不会存在超过117字节的情况,如果你不是,需要分段加密,可以参考php文档官方的例子:openssl_public_decrypt,下面的
notes
详细的解释了如何计算明文的长度限制,还举例了分段加密
如果你使用我的代码成功完成了你的项目,恭喜你,如果不能,你可以自己根据具体项目情况进行研究改造,面对不知道的东西,克服就是你的学习过程
最后附上JAVA的RSA实现代码,不得感慨php真的方便,但同样,也是使你很多时候不知道底层的实现,这也是为什么很多人推荐,学一门动态和一门静态的语言的原因,对你很有帮助
Cipherimport java.io.ByteArrayOutputStream;
import java.security.Key;
import java.security.KeyFactory;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.Cipher;
/**
* RSA加密,解密, 加签 ,解签 算法集合
*
* RSA 公私钥非对称性加密算法
*
*
*/
public class RSAUtil {
/** 加密算法RSA */
public static final String KEY_ALGORITHM = "RSA";
/** 签名算法 */
public static final String SIGNATURE_ALGORITHM = "SHA1withRSA";// SHA1withRSA_MD5withRSA
/** RSA最大加密明文大小 */
private static final int MAX_ENCRYPT_BLOCK = 117;
/* RSA最大解密密文大小 */
private static final int MAX_DECRYPT_BLOCK = 128;
/**
* 利用公钥对数据进行加密
* @param data 待加密的数据字节数组
* @param publicKey base64 encode后的公钥
* @return
* @throws Exception
*/
public static byte[] encryptByPublicKey(byte[] data, String publicKey) throws Exception {
byte[] keyBytes = Base64Util.decode(publicKey);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key publicK = keyFactory.generatePublic(x509KeySpec);
// 对数据加密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, publicK);
int inputLen = data.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段加密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
} else {
cache = cipher.doFinal(data, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_ENCRYPT_BLOCK;
}
byte[] encryptedData = out.toByteArray();
out.close();
return encryptedData;
}
public static byte[] decryptByPublicKey(byte[] encryptedData, String publicKey) throws Exception {
byte[] keyBytes = Base64Util.decode(publicKey);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key publicK = keyFactory.generatePublic(x509KeySpec);
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, publicK);
int inputLen = encryptedData.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段解密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
} else {
cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_DECRYPT_BLOCK;
}
byte[] decryptedData = out.toByteArray();
out.close();
return decryptedData;
}
public static void main(String[] args) throws Exception {
byte[] d = HexUtil.decode("16进制处理的公钥,对应业务运营人员提供");
String encryStr = Base64Util.encode(encryptByPublicKey("abcsdsddsd".getBytes(), Base64Util.encode(d)));
System.out.println(encryStr);
//encryStr 为接口报文加密传输的内容
//如下为作废接口解密获取明文卡密
new String(decryptByPublicKey(Base64Util.decode("券码作废时壹钱包传递过来的加密券码"), Base64Util.encode(d)));
}
}
/**
*
*/
public class HexUtil {
private HexUtil() {
}
public static String encode(byte[] src) {
StringBuffer hs = new StringBuffer();
for (int i = 0; i < src.length; ++i) {
String stmp = Integer.toHexString(src[i] & 255).toUpperCase();
if (stmp.length() == 1) {
hs.append("0").append(stmp);
} else {
hs.append(stmp);
}
}
return hs.toString();
}
public static byte[] decode(String hex) throws SecurityException {
if (hex.length() % 2 != 0) {
throw new SecurityException("invalid hex string");
} else {
char[] arr = hex.toCharArray();
byte[] b = new byte[hex.length() / 2];
int i = 0;
int j = 0;
for (int l = hex.length(); i < l; ++j) {
String swap = "" + arr[i++] + arr[i];
int byteint = Integer.parseInt(swap, 16) & 255;
b[j] = (new Integer(byteint)).byteValue();
++i;
}
return b;
}
}
}
import org.apache.log4j.Logger;
import java.io.ByteArrayOutputStream;
public class Base64Util {
private static final Logger log = Logger.getLogger(Base64Util.class);
private static final char[] base64EncodeChars = new char[]{'A', 'B', 'C',
'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2',
'3', '4', '5', '6', '7', '8', '9', '+', '/'};
private static byte[] base64DecodeChars = new byte[]{-1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59,
60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1,
-1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1,
-1, -1};
private Base64Util() {
}
/**
* @param encode(编码的字符)
* @return 将编码的字符窜解码成可用字符窜
*/
public static String getFromBase64(String encode) {
byte[] b = null;
String result = null;
if (encode != null) {
BASE64Decoder decoder = new BASE64Decoder();
try {
b = decoder.decodeBuffer(encode);
result = new String(b, "utf-8");
} catch (Exception e) {
log.error("编码异常", e);
}
}
return result;
}
/**
* <code>encode</code>
*
* @param data
* @return
* @since 2012-2-9 liaoyp
*/
public static String encode(byte[] data) {
StringBuilder sb = new StringBuilder();
int len = data.length;
int i = 0;
int b1;
int b2;
int b3;
while (i < len) {
b1 = data[i++] & 0xff;
if (i == len) {
sb.append(base64EncodeChars[b1 >>> 2]);
sb.append(base64EncodeChars[(b1 & 0x3) << 4]);
sb.append("==");
break;
}
b2 = data[i++] & 0xff;
if (i == len) {
sb.append(base64EncodeChars[b1 >>> 2]);
sb.append(base64EncodeChars[((b1 & 0x03) << 4)
| ((b2 & 0xf0) >>> 4)]);
sb.append(base64EncodeChars[(b2 & 0x0f) << 2]);
sb.append("=");
break;
}
b3 = data[i++] & 0xff;
sb.append(base64EncodeChars[b1 >>> 2]);
sb.append(base64EncodeChars[((b1 & 0x03) << 4)
| ((b2 & 0xf0) >>> 4)]);
sb.append(base64EncodeChars[((b2 & 0x0f) << 2)
| ((b3 & 0xc0) >>> 6)]);
sb.append(base64EncodeChars[b3 & 0x3f]);
}
return sb.toString();
}
/**
* <code>decode</code>
*
* @param str
* @return
* @since 2012-2-9 liaoyp
*/
public static byte[] decode(String str) {
byte[] data = str.getBytes();
int len = data.length;
ByteArrayOutputStream buf = new ByteArrayOutputStream(len);
int i = 0;
int b1;
int b2;
int b3;
int b4;
while (i < len) { /* b1 */
do {
b1 = base64DecodeChars[data[i++]];
} while (i < len && b1 == -1);
if (b1 == -1) {
break;
} /* b2 */
do {
b2 = base64DecodeChars[data[i++]];
} while (i < len && b2 == -1);
if (b2 == -1) {
break;
}
buf.write((int) ((b1 << 2) | ((b2 & 0x30) >>> 4))); /* b3 */
do {
b3 = data[i++];
if (b3 == 61) {
return buf.toByteArray();
}
b3 = base64DecodeChars[b3];
} while (i < len && b3 == -1);
if (b3 == -1) {
break;
}
buf.write((int) (((b2 & 0x0f) << 4) | ((b3 & 0x3c) >>> 2))); /* b4 */
do {
b4 = data[i++];
if (b4 == 61) {
return buf.toByteArray();
}
b4 = base64DecodeChars[b4];
} while (i < len && b4 == -1);
if (b4 == -1) {
break;
}
buf.write((int) (((b3 & 0x03) << 6) | b4));
}
return buf.toByteArray();
}
}