背景
1977年,Ronald Rivest,Adi Shamir和Leonard Adleman提出了一种实现方案,即RSA;它后来变成了非对称密码方案中使用最广泛的一种。RSA也成为Rivest-Shamri-Adkeman算法,他是目前最广泛的一种非对称密钥方案,不过椭圆曲线和离散对数方案也逐渐普及。在实际中RSA应用广泛,但是我们却常用于:
@数据小片段的加密,尤其用于密钥传输
@数字签名,比如Internet上的数字证书。
写这篇文章的目的当前也是有自己记录的背景:使用Android手机从服务器上拿到Ukey的公钥-----Android手机使用公钥加密随机生成的密钥--------通过无线传送给Ukey------Ukey调用自己的私钥机密获取随机密钥,建立通信。
如果只是想测试java公私钥或者openssl,可以转到http://blog.csdn.net/chaijunkun/article/details/7275632,下面的内容就不适合了。
---------------------------------------分割----------------------------------------------------------------------
遇上的问题在java中如何装载Ukey生成的公钥,java环境下使用的是openssl密码环境,这里当然要感谢Eric A. Young和Tim J. Hudson是他们给我们带来了极大的方便,缺点是让我们看不见更多的底层,和RSA的运行机制。
首先说一下常规:
/**
* 随机生成密钥对,采用DER编码格式
* 一定要注意的是:publicKey生成的为162 bytes
* 这里的publicKey不仅仅包含了128密钥信息,还有编码和序列等信息
* 这个publicKey结构我查不到
*/
public void genKeyPair(){
KeyPairGenerator keyPairGen= null;
try {
keyPairGen= KeyPairGenerator.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
keyPairGen.initialize(1024, new SecureRandom());
KeyPair keyPair= keyPairGen.generateKeyPair();
this.privateKey= (RSAPrivateKey) keyPair.getPrivate();
this.publicKey= (RSAPublicKey) keyPair.getPublic();
System.out.println(publicKey.getEncoded().length+"*****");
System.out.println(AES.byteArrayToString(publicKey.getEncoded()));
}
下面就是加载公钥
/**
* 从字节加载公钥
* @param publicKeyStr 公钥数据字符串
* @throws Exception 加载公钥时产生的异常
*/
public void loadPublicKey1(byte[] bytepublicKey) throws Exception{
try {
KeyFactory keyFactory= KeyFactory.getInstance("RSA");
X509EncodedKeySpec keySpec= new X509EncodedKeySpec(bytepublicKey);
this.publicKey= (RSAPublicKey) keyFactory.generatePublic(keySpec);
} catch (NoSuchAlgorithmException e) {
throw new Exception("无此算法");
} catch (InvalidKeySpecException e) {
throw new Exception("公钥非法");
} catch (NullPointerException e) {
throw new Exception("公钥数据为空");
}
}
这里可以看到使用keyFactory.generatePublic加载keySpec,这里的keySpec是X509格式,如果你bytepublicKey没有162个字节的话,那么在keyFactory.generatePublic注定会报出“公钥非法”的错误。但是我得ukey生成的公钥只有128 bytes也就注定我无法使用X509格式。
-----------------------------------分割------------------------------------------
首先说Ukey:
Ukey采用
RSA_GenKey(&kpub, &kpri, RSA_1024+RSA_E_0x10001+RSA_noCRT);
1024位密钥,RSA算法,公钥类型10001 16进制,私钥类型不采用CRT。
加密出的数据:
01 02 03 04 05 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
加密后:
70 B2 86 5F 2E 1B C9 EC FC E1 40 30 0F 89 20 97 31 88 61 24 6B 42 9B 5D C4 78 FE D6 AA 1C 0C F6 24 59 F1 2F 73 41 53 C3 BB 15 AB C3 6C 8B 61 75 3E 27 06 4C E6 D2 BF B0 9C D5 AF 89 89 93 D0 9E FF EE 42 C1 36 B7 77 D9 6A 9D D8 74 20 31 FF 01 EB C5 CA D0 24 33 3E 9F 3B E9 28 6E C7 CE 51 A9 2F 40 DA DB 17 43 A7 52 39 8F E5 A4 57 AB 65 FC 93 22 89 47 DF 72 6A 34 83 6F 20 4A E7 53 68 15
再来说android:
/**
* 加载字节公钥信息
*/
public void loadPublicKey(byte[] bytepublicKey) throws Exception{
try {
System.out.println(bytepublicKey.length+"*****");//DER
KeyFactory keyFactory= KeyFactory.getInstance("RSA");
RSAPublicKeySpec keySpec= new RSAPublicKeySpec(new BigInteger(
bytepublicKey), new BigInteger("10001", 16));
this.publicKey= (RSAPublicKey) keyFactory.generatePublic(keySpec);
} catch (NoSuchAlgorithmException e) {
throw new Exception("无此算法");
} catch (NullPointerException e) {
throw new Exception("公钥数据为空");
}
}
数据加密流程:
/**
* 加密过程
* @param publicKey 公钥
* @param plainTextData 明文数据
* @return
* @throws Exception 加密过程中的异常信息
*/
public byte[] encrypt(RSAPublicKey publicKey, byte[] plainTextData) throws Exception{
if(publicKey== null){
throw new Exception("加密公钥为空, 请设置");
}
Cipher cipher= null;
try {
cipher= Cipher.getInstance("RSA", new BouncyCastleProvider());
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] output= cipher.doFinal(plainTextData);
return output;
} catch (NoSuchAlgorithmException e) {
throw new Exception("无此加密算法");
} catch (NoSuchPaddingException e) {
e.printStackTrace();
return null;
}catch (InvalidKeyException e) {
throw new Exception("加密公钥非法,请检查");
} catch (IllegalBlockSizeException e) {
throw new Exception("明文长度非法");
} catch (BadPaddingException e) {
throw new Exception("明文数据已损坏");
}
}
下面贴一下加密前的数据:
加密后的数据:
这里可以核对一下和Ukey加密出来的数据是一样的。在android上的关键点就是
RSAPublicKeySpec keySpec= new RSAPublicKeySpec(new BigInteger(
bytepublicKey), new BigInteger("10001", 16));
这里使用了RSAPublicKeySpec,公钥格式为10001 16进制,非常的关键。