本文主要讨论 java 和python之前的跨平台rsa签名、验签,当然,其他语言也可以参考一下,比如php等等
在开发中我们经常遇到这一类问题,java平台和python平台进行通信时,比如我们服务器是python后台而对方的服务器是java后台,而如果要使用对方提供的接口就必须要rsa签名与验签,而这两个平台的rsa密钥的格式并不相同,这个时候,应该怎么办?
第一种情况,java生成密钥,签名, 使用python验签
java生成密钥的代码如下:
/**
* 将字节数组转换为16进制字符串的形式.
*/
public static final String bytesToHexStr(byte[] bcd) {
StringBuffer s = new StringBuffer(bcd.length * 2);
for (int i = 0; i < bcd.length; i++) {
s.append(HEX_LOOKUP_STRING[(bcd[i] >>> 4) & 0x0f]);
s.append(HEX_LOOKUP_STRING[bcd[i] & 0x0f]);
}
return s.toString();
}
/**
* 本方法用于产生1024位RSA公私钥对。
* @return 私钥、公钥
*/
private static String[] genRSAKeyPair() throws Exception {
KeyPairGenerator rsaKeyGen = null;
KeyPair rsaKeyPair = null;
System.out.println("Generating a pair of RSA key ... ");
rsaKeyGen = KeyPairGenerator.getInstance("RSA");
SecureRandom random = new SecureRandom();
random.setSeed(("" + System.currentTimeMillis() * Math.random() * Math.random()).getBytes());
rsaKeyGen.initialize(1024, random);
rsaKeyPair = rsaKeyGen.genKeyPair();
PublicKey rsaPublic = rsaKeyPair.getPublic();
PrivateKey rsaPrivate = rsaKeyPair.getPrivate();
String privateAndPublic[] = new String[2];
privateAndPublic[0] = bytesToHexStr(rsaPrivate.getEncoded());
privateAndPublic[1] = bytesToHexStr(rsaPublic.getEncoded());
System.out.println("私钥:" + privateAndPublic[0]);
System.out.println("公钥:" + privateAndPublic[1]);
System.out.println("1024-bit RSA key GENERATED.");
return privateAndPublic;
}
这样生成的密钥是这种形式(以公钥为例):
“
30819f30......
203010001
”
这是16进制的密钥
这种密钥是无法在python平台直接使用的,在python平台,大家见惯的密钥应该是“
MIGfMA0GCSqGSIb3D
pQb1mjeGLy6gw+AfOKZ1dpNbMUyZml+p3stTS......
”这种形式的。
那么python平台应该怎么用公钥验签呢?
主要思路是获取这个公钥的参数,如 n, e ,再在python平台上用 n,e 生成一个公钥, 如果python平台需要使用java生成的私钥,思路也是一样的,唯一不同的就是需要4个参数了,n,e,q,p
这种思路的好处是可以任意跨平台,不论这个平台或者这个库使用的是什么格式的密钥,都可以搞定,因为这些参数是rsa算法的基础,任意格式的密钥都是基于这些参数的。
首先, 可以在
PublicKey rsaPublic = rsaKeyPair.getPublic();
这一行打断点,获取到
rsaPublic
这个类的成员中就可以看到
modulus=cd6c01988......6187,
publicExponent=10001
这两个值其实就是 n 和 e(这两个数都是16进制的)
如果密钥的生成过程无法再重复,比如java的签名是某个其他第三方接口给你的,则需要根据
“
30819f......0001
”
进行反推。
/**
* 将16进制字符串还原为字节数组.
*/
public static final byte[] hexStrToBytes(String s) {
byte[] bytes;
bytes = new byte[s.length() / 2];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) Integer.parseInt(s.substring(2 * i, 2 * i + 2), 16);
}
return bytes;
}
/**
* 得到公钥对象
*
* @param key 密钥字符串(经过16进制编码)
* @throws Exception
*/
public static PublicKey getPublicKey(String key) throws Exception {
byte[] keyBytes = hexStrToBytes(key.trim());
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(keySpec);
}
/**
* 得到私钥对象
*
* @param key 密钥字符串(经过16进制编码)
* @throws Exception
*/
public static PrivateKey getPrivateKey(String key) throws Exception {
byte[] keyBytes = hexStrToBytes(key.trim());
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(keySpec);
}
根据以上代码,就可以使用16进制密钥get到
PublicKey
类型的一个实例,也就可以获取到
rsa公钥的n , e,获取rsa私钥的参数的方法与这个是一样的。
接着就可以在python平台上开始验签了,使用 python的Crypto库, 以下代码中的n 就是
PublicKey
这个类的成员变量
modulus的值, e就是
publicExponent, 一般java、python平台默认生成的rsa密钥用的e都是65537,即16进制的10001
key = Crypto.PublicKey.RSA.construct((long(n,16),long('10001',16)))
public_key = key.publickey().exportKey()
print(public_key)
打印结果:
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNbAGYhY2W/44KP/dbBYL3HA/D
qJSpmVyfsNwrr1PyIjD7WurKropwSomzEktQp5ZWXNSeMfvPLNgdNqwnTJwH96Xz
pWQVQ2B/n4jE/OHXSQNUiA5FXZWb9OMb0hjbpKFXELzHCqztBtdPSl90CEnbV9Rn
88lvxVvWbes4ENlhhwIDAQAB
-----END PUBLIC KEY-----
这个就是在python平台上我们经常使用的格式了, 至于具体如何去验签,这里不详谈了。
至于,如何使用RSA.construct生成私钥,见
第二种情况,python生成密钥,签名, java验签
这种时候生成的密钥是这种格式的,即
“
MIGfMA0GCSq......
bV9Rn
88lvxVvWbes4ENlhhwIDAQAB
”
生成方法可以参考命令:
openssl genrsa -out rsa_private_key.pem 1024
openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
openssl pkcs8 –nocrypt -topk8 -in rsa_private_key.pem -out pkcs8_rsa_private_key.pem
java平台是无法直接使用这种格式的密钥的,这个时候,验签如何进行?
第一种方法的思路是一样的,就是获取这个python密钥的n,e,然后再在java中生成对应的16进制密钥。
对应的方法十分比较简单,在python部分:
>>> import rsa
>>> (pubkey, privkey) = rsa.newkeys(1024, poolsize=8)
>>> pubkey.n
20945457913487......24L
>>> pubkey.e
65537
/**
* @param modulus
* 模
* @param exponent
* 指数
* @return
*/
public static RSAPublicKey getPublicKey(String modulus, String exponent) {
try {
BigInteger b1 = new BigInteger(modulus);
BigInteger b2 = new BigInteger(exponent);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(b1, b2);
return (RSAPublicKey) keyFactory.generatePublic(keySpec);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
后面再按照前面第一种情况中生成密钥的代码,
bytesToHexStr(rsaPublic.getEncoded());
即可得到16进制密钥
或者,还有另一种方式可以将python的密钥转化为java平台能使用的格式
可在java平台进行如下转化:
import android.util.Base64;
public static final String PUB_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNbAGYhY2W/44KP/dbBYL3HA/DqJSpmVyfsNwrr1PyIjD7WurKropwSomzEktQp5ZWXNSeMfvPLNgdNqwnTJwH96XzpWQVQ2B/n4jE/OHXSQNUiA5FXZWb9OMb0hjbpKFXELzHCqztBtdPSl90CEnbV9Rn88lvxVvWbes4ENlhhwIDAQAB";
public static String getKey(){
return byte2Hex(Base64.decode(PUB_KEY , Base64.DEFAULT));
}
public static String byte2Hex(byte[] digest) {
StringBuilder sb = new StringBuilder();
int len = digest.length;
String out = null;
for (int i = 0; i < len; i++) {
out = Integer.toHexString(0xFF & digest[i]);//原始方法
if (out.length() == 1) {
sb.append("0");//如果为1位 前面补个0
}
sb.append(out);
}
return sb.toString();
}
通过
getKey
(
)
获得的结果
“
30819f300......3010001
”
这就是 rsa的16进制密钥,即
与第一种情况中的java代码里
genRSAKeyPair
()
生成的密钥是同一类型,
再之后就可以进行java平台的验签了。另外,
获取私钥的方法与获取公钥的方法是一样的。