您的位置:首页 > 编程语言 > Python开发

跨平台rsa签名与验签

2017-01-09 20:46 405 查看
本文主要讨论 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平台,大家见惯的密钥应该是“MIGfMA0GCSqGSIb3DpQb1mjeGLy6gw+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生成私钥,见
http://pydoc.net/Python/pycrypto/2.5/Crypto.PublicKey.RSA/

第二种情况,python生成密钥,签名, java验签

这种时候生成的密钥是这种格式的,即

“MIGfMA0GCSq......bV9Rn88lvxVvWbes4ENlhhwIDAQAB”

生成方法可以参考命令:
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


在java部分:

/**
* @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平台的验签了。另外,获取私钥的方法与获取公钥的方法是一样的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  rsa python java 验签 16进制