<!-- /* Font Definitions */ @font-face {font-family:宋体; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-alt:SimSun; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 680460288 22 0 262145 0;} @font-face {font-family:幼圆; panose-1:0 0 0 0 0 0 0 0 0 0; mso-font-alt:宋体; mso-font-charset:134; mso-generic-font-family:auto; mso-font-format:other; mso-font-pitch:auto; mso-font-signature:1 135135232 16 0 262144 0;} @font-face {font-family:"/@宋体"; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 680460288 22 0 262145 0;} @font-face {font-family:楷体_GB2312; panose-1:0 0 0 0 0 0 0 0 0 0; mso-font-alt:宋体; mso-font-charset:134; mso-generic-font-family:auto; mso-font-format:other; mso-font-pitch:auto; mso-font-signature:1 135135232 16 0 262144 0;} @font-face {font-family:"/@幼圆"; panose-1:0 0 0 0 0 0 0 0 0 0; mso-font-charset:134; mso-generic-font-family:auto; mso-font-format:other; mso-font-pitch:auto; mso-font-signature:1 135135232 16 0 262144 0;} @font-face {font-family:"/@楷体_GB2312"; panose-1:0 0 0 0 0 0 0 0 0 0; mso-font-charset:134; mso-generic-font-family:auto; mso-font-format:other; mso-font-pitch:auto; mso-font-signature:1 135135232 16 0 262144 0;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; mso-pagination:widow-orphan; font-size:12.0pt; font-family:"Times New Roman"; mso-fareast-font-family:宋体;} @page Section1 {size:612.0pt 792.0pt; margin:72.0pt 90.0pt 72.0pt 90.0pt; mso-header-margin:36.0pt; mso-footer-margin:36.0pt; mso-paper-source:0;} div.Section1 {page:Section1;} -->
2.7 生成非对称加密的公钥和私钥
前面几节的程序中,加密和解密使用的是同一个密钥,这种方式称为对称加密。使用对称密钥时,若 A 想让 B 向其秘密传送信息, A 必须先将密钥提供给 B ,或者由 B 将密钥提供给 A 。如果在传递密钥过程中密钥被窃取,则 A 和 B 之间的通信就不再安全了。非对称加密解决了这一问题。它将加密的密钥和解密的密钥分开。 A 事先生成一对密钥,一个用于加密,称为公钥(公钥),一个用于解密,称为私钥。由于产生这一对密钥的一些数学特性,公钥加密的信息只能用私钥解密。这样, A 只要将公钥对外公开,不论谁就可以使用这个公钥给 A 发送秘密信息了。 A 接收到加密信息后可以用私钥打开。由于只需要传递公钥,而公钥只能加密不能解密,因此即使攻击者知道了公钥也无济于事。本节以 RSA 算法为例介绍 Java 中如何生成公钥和私钥。
实例说明
本实例演示了如何使用 Java 中定义好的类创建 RSA 公钥和私钥。
编程思路:
Java 的 KeyPairGenerator 类提供了一些方法来创建密钥对以便用于非对称加密,密钥对创建好后封装在 KeyPair 类型的对象中,在 KeyPair 类中提供了获取公钥和私钥的方法。具体步骤如下:
( 1 ) 创建密钥对生成器
KeyPairGenerator kpg=KeyPairGenerator.getInstance("RSA");
分析:密钥对生成器即 KeyPairGenerator 类型的对象,和 2.2.1 小节的第 1 步中介绍的 KeyGenerator 类一样, KeyPairGenerator 类是一个工厂类,它通过其中预定义
的一个静态方法 getInstance( )获取 KeyPairGenerator 类型的对象。 getInstance()方法的参数是一个字符串,指定非对称加密所使用的算法,常用的有 RSA, DSA等。
( 2 ) 初始化密钥生成器
kpg.initialize(1024);
分析:对于密钥长度。对于 RSA 算法,这里指定的其实是 RSA 算法中所用的模的位数。可以在 512 到 2048 之间。
( 3 ) 生成密钥对
KeyPair kp=kpg.genKeyPair( );
分析:使用 KeyPairGenerator 类的 genKeyPair( )方法生成密钥对,其中包含了一对公钥和私钥的信息。
( 4 ) 获取公钥和私钥
PublicKey pbkey=kp.getPublic( );
PrivateKey prkey=kp.getPrivate( );
分析:使用 KeyPair 类的 getPublic( )和 getPrivate( )方法获得公钥和私钥对象。
//文件: Skey_RSA.java
import java.io.*;
import java.security.*;
import javax.crypto.*;
import javax.crypto.spec.*;
public class Skey_RSA{
public static void main(String args[]) throws Exception{
KeyPairGenerator kpg=KeyPairGenerator.getInstance("RSA");
kpg.initialize(1024);
KeyPair kp=kpg.genKeyPair();
PublicKey pbkey=kp.getPublic();
PrivateKey prkey=kp.getPrivate();
FileOutputStream f1=new FileOutputStream("Skey_RSA_pub.dat");
ObjectOutputStream b1=new ObjectOutputStream(f1);
b1.writeObject(pbkey);
FileOutputStream f2=new FileOutputStream("Skey_RSA_priv.dat");
ObjectOutputStream b2=new ObjectOutputStream(f2);
b2.writeObject(prkey);
}
}
分析:本实例和 2.2.1 小节一样使用对象流将密钥保存在文件中,所不同的是加密所用
的公钥和解密所用的私钥分开保存。将公钥对外公布,供其他人加密使用,而把私钥秘密保
存,在需要解密时使用。
运行程序
输入 java Skey_RSA 运行程序,当前目录下将生成两个文件: Skey_RSA_pub.dat 和
Skey_RSA_priv.dat ,前者保存着公钥,后者保存着私钥。将文件 Skey_RSA_pub.dat 对外公
布(如放在 Web 服务器上给大家下载,或者直接拷贝给所有需要的人),而 Skey_RSA_priv.dat
秘密保存。
2.8 使用 RSA 算法进行加密和解密
2.7 节的程序创建了 RSA 的公钥和密钥,本节使用公钥进行加密,然后使用私钥对加密的信息进行解密。
2.8.1 使用 RSA 公钥进行加密
实例说明
本实例以加密一串最简单的字符串“ Hello World! ”为例,演示了如何使用 2.7 节生成的 RSA 公钥文件 Skey_RSA_pub.dat 进行加密。
编程思路:
使用 RSA 公钥进行加密的代码和 2.3.1 小节使用 DESede 进行加密其实没什么大的区别,只是 Cipher 类的 getInstance( ) 方法的参数中应该指定使用 RSA 。但由于 J2SDK1.4 中只实现了 RSA 密钥的创建,没有实现 RSA 算法,因此需要安装其他加密提供者软件才能直接使用 Cipher 类执行加密解密。其实有了 RSA 公钥和私钥后,自己编写程序从底层实现 RSA 算法也并不复杂。本实例给出简单的例子实现了 RSA 加密,使读者只使用 J2SDK1.4 便能直观地了解非对称加密算法。 RSA 算法是使用整数进行加密运算的,在 RSA 公钥中包含了两个信息:公钥对应的整数 e 和用于取模的整数 n 。对于明文数字 m ,计算密文的公式是: me mod n 。因此,编程步骤如下:
( 1 ) 获取公钥
FileInputStream f=new FileInputStream("Skey_RSA_pub.dat");
ObjectInputStream b=new ObjectInputStream(f);
RSAPublicKey pbk=(RSAPublicKey)b.readObject( );
分析: 从 2.7 节生成的公钥文件 Skey_RSA_pub.dat 中读取公钥,由于 2.7 节使用的是 RSA 算法,因此从文件读取公钥对象后强制转换为 RSAPublicKey 类型,以便后面读取 RSA 算法所需要的参数 .
( 2 ) 获取公钥的参数 (e, n)
BigInteger e=pbk.getPublicExponent();
BigInteger n=pbk.getModulus();
分析:使用 RSAPublicKey 类的 getPublicExponent( )和 getModulus( )方法可以分别获得公始中 e 和 n 的值。由于密钥很长,因此对应的整数值非常大,无法使用一般的整型来存储, Java 中定义了 BigInteger 类来存储这类很大的整数并可进行各种运算。
( 3 ) 获取明文整数 (m)
String s="Hello World!";
byte ptext[]=s.getBytes("UTF8");
BigInteger m=new BigInteger(ptext);
分析:明文是一个字符串,为了用整数表达这个字符串,先使用字符串的 getBytes( ) 方法 将其转换为 byte 类型数组,它其实是字符串中各个字符的二进制表达方式,这一串二进制数转换为一个整数将非常大,因此仍旧使用 BigInteger 类将这个二进制串转换为整型。本实例中出于简化,将整个字符串转换为一个整数。实际使用中,应该对明文进行分组,因为 RSA 算法要求整型数 m 的值必须小于 n。
( 4 ) 执行计算
BigInteger c=m.modPow(e,n);
分析:计算前面的公式: me mod n。 BigInteger 类中已经提供了方法 modPow( )来执行这个计算。底数 m 执行这个方法,方法 modPow( )的第一个参数即指数 e,第二个参数即模 n。方法返回的结果即公式 me mod n 的计算结果,即密文。
//文件: Enc_RSA.java
import java.security.*;
import java.security.spec.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import javax.crypto.interfaces.*;
import java.security.interfaces.*;
import java.math.*;
import java.io.*;
public class Enc_RSA{
public static void main(String args[]) throws Exception{
String s="Hello World!";
FileInputStream f=new FileInputStream("Skey_RSA_pub.dat");
ObjectInputStream b=new ObjectInputStream(f);
RSAPublicKey pbk=(RSAPublicKey)b.readObject( );
BigInteger e=pbk.getPublicExponent();
BigInteger n=pbk.getModulus();
System.out.println("e= "+e);
System.out.println("n= "+n);
byte ptext[]=s.getBytes("UTF8");
BigInteger m=new BigInteger(ptext);
BigInteger c=m.modPow(e,n);
System.out.println("c= "+c);
String cs=c.toString( );
BufferedWriter out=
new BufferedWriter(new OutputStreamWriter(
new FileOutputStream("Enc_RSA.dat")));
out.write(cs,0,cs.length( ));
out.close( );
}
}程序最后将密文 c 打印出来,并以字符串形式保存在文件中。
运行程序
输入 java Enc_RSA 运行程序
2.8.2 使用 RSA 私钥进行解密
实例说明
本实例使用 2.7 节生成的私钥文件 Skey_RSA_priv.dat ,对 2.8.1 小节生成的密文文件 Enc_RSA.dat 进行解密。
编程思路:
和 2.8.1 小节类似,使用 RSA 私钥进行解密的代码也可以在 Cipher 类的 getInstance( ) 方法的参数中指定使用 RSA ,使用解密模式进行解密。但需要安装其他加密提供者软件才能直接使用 Cipher 类执行加密解密。本实例给出简单的例子从底层实现 RSA 解密,以便只使用 J2SDK1.4 便能直观地了解非对称加密算法。 RSA 算法的解密和加密类似,在 RSA 私钥中包含了两个信息:私钥对应的整数 d 和用于取模的整数 n 。其中的 n 和加密时的 n 完全相同。对于密文数字 c ,计算明文的公式是: cd mod
n ,之所以加密时由公式 me mod n 得到的密文 c 通过这个公式计算一下就可以反过来得到原来的明文 m ,有其本身的数学规律决定。从编程角度只需要知道这个结果就行了。编程步骤如下:
( 1 ) 读取密文
BufferedReader in=
new BufferedReader(new InputStreamReader(
new FileInputStream("Enc_RSA.dat")));
String ctext=in.readLine();
BigInteger c=new BigInteger(ctext);
分析: 从 2.8.1 小节生成的密文文件 Enc_RSA.dat 中读取密文,由于 2.8.1 小节保存的只是一行字符串,因此只要一条 readLine( )语句即可。由于这一行字符串表示的是一个很大的整型数,因此使用 BigInteger 类来表示这个整型数。
( 2 ) 获取私钥
FileInputStream f=new FileInputStream("Skey_RSA_priv.dat");
ObjectInputStream b=new ObjectInputStream(f);
RSAPrivateKey prk=(RSAPrivateKey)b.readObject( );
分析: 从 2.7 节生成的私钥文件 Skey_RSA_priv.dat 中读取公钥,由于 2.7 节使用的是 RSA 算法,因此从文件读取公钥对象后强制转换为 RSAPrivateKey 类型,以便后面读取 RSA 算法所需要的参数。
( 3 ) 获取私钥的参数 (d, n)
BigInteger d=prk.getPrivateExponent( );
BigInteger n=prk.getModulus( );
分析:使用 RSAPrivateKey 类的 getPrivateExponent( ) 和 getModulus( )方法可以分别获得公始中 d 和 n 的值。
( 4 ) 执行计算
BigInteger m=c.modPow(d,n);
分析:使用 BigInteger 的 modPow( )方法计算前面的公式: cd mod n。方法返回的结果即公式 cd mod n 的计算结果,即明文对应的整型数 m。
( 5 ) 计算明文整型数对应的字符串
byte[] mt=m.toByteArray();
for(int i=0;i<mt.length;i++){
System.out.print((char) mt[i]);
}
分析: RSA 算法解密的结果 m 是一个很大的整数,为了计算出其对应的字符串的值,先使用 BigInteger 类的 toByteArray( )方法得到代表该整型数的字节数组,然后将数组中每个元素转换为字符,组成字符串。
//文件: Dec_RSA.java
import java.security.*;
import java.security.spec.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import javax.crypto.interfaces.*;
import java.security.interfaces.*;
import java.math.*;
import java.io.*;
public class Dec_RSA{
public static void main(String args[]) throws Exception{
BufferedReader in=
new BufferedReader(new InputStreamReader(new FileInputStream("Enc_RSA.dat")));
String ctext=in.readLine();
BigInteger c=new BigInteger(ctext);
FileInputStream f=new FileInputStream("Skey_RSA_priv.dat");
ObjectInputStream b=new ObjectInputStream(f);
RSAPrivateKey prk=(RSAPrivateKey)b.readObject( );
BigInteger d=prk.getPrivateExponent();
BigInteger n=prk.getModulus();
System.out.println("d= "+d);
System.out.println("n= "+n);
BigInteger m=c.modPow(d,n);
System.out.println("m= "+m);
byte[] mt=m.toByteArray();
System.out.println("PlainText is ");
for(int i=0;i<mt.length;i++){
System.out.print((char) mt[i]);
}
}
}
运行程序
输入 java Dec_RSA 运行程序
2.9 使用密钥协定创建共享密钥
非对称加密解决了密钥分发的难题,但其计算量比对称密钥大,因此一般并不使用非对称加密加密大量数据。常见的做法是:主要数据通过对称密钥加密,而使用非对称加密来分发对称密钥。将两者的优势结合了起来。例如若 A 和 B 之间想秘密传送大量数据,一方(如 A )先创建公钥和私钥对,公钥对外公布,另一方(如 B )创建对称密钥,然后使用公钥加密对称密钥,传递给 A , A 收到后用私钥解密,得到对称密钥,以后 A 和 B 之间就可以使用对称密钥加密通信了。除了这种方式以外,还可以使用密钥协定来交换对称密钥。执行密钥协定的标准算法是 DH 算法( Diffie-Hellman 算法),本节介绍在 Java 中如何使用 DH 算法来交换共享密钥。
2.9.1 创建 DH 公钥和私钥
实例说明
DH 算法是建立在 DH 公钥和私钥的基础上的, A 需要和 B 共享密钥时, A 和 B 各自生成 DH 公钥和私钥,公钥对外公布而私钥各自秘密保存。本实例将介绍 Java 中如何创建并部署 DH 公钥和私钥,以便后面一小节利用它创建共享密钥。
编程思路:
在 2.7 节中使用了 KeyPairGenerator 类创建 RSA 公钥和私钥,本节也一样,只是其参数中指定“ DH ”,此外在初始化时需要为 DH 指定特定的参数。具体步骤如下:
( 1 ) 生成 DH 参数
DHParameterSpec DHP=
new DHParameterSpec(skip1024Modulus,skip1024Base);
分析:和 RSA 算法类似, DH 算法涉及到一些指数和取模运算, DH 参数指定 A、 B双方在创建 DH 密钥时所公用的基数和模, Java 中 DHParameterSpec 类可以定义 DH 参数,其构造器的第一个参数指定模,第二个参数指定基数。模和基数的取值在 Internet协议简单密钥管理( SKIP)标准中已经标准化,在安装 J2SDK1.4 后,计算机 C 盘中 C:/j2sdk-1_4_0-doc/docs/guide/security/jce/JCERefGuide.html 文件也包含了密钥长度为 1024 的 DH 密钥中模和基数的定义, 可以直接拷贝下来使用, 在 JCERefGuide.html 文件中查找“ 1024 bit Diffie-Hellman modulus”注释语句,将其下的 skip1024ModulusBytes[ ]数组以及 BigInteger 类型的 skip1024Modulus 和 skip1024Base 变量拷贝下来即可。在本小节的“代码与分析”中也给出了完整的代码。此外 DH 密钥长度也可以是 512 或 2048 位。
( 2 ) 创建密钥对生成器
KeyPairGenerator kpg= KeyPairGenerator.getInstance("DH");
分析:密钥对生成器即 KeyPairGenerator 类型的对象,和 2.7 节一样,通过其中预定义的一个静态方法 getInstance( )获取 KeyPairGenerator 类型的对象。 getInstance( )方法的参数指定为“ DH”。
( 3 ) 初始化密钥生成器
kpg.initialize(DHP);
分析:初始化时使用的参数即第 1 步中生成的参数。
( 4 ) 生成密钥对,获取公钥和私钥
KeyPair kp=kpg.genKeyPair();
PublicKey pbk=kp.getPublic( );
PrivateKey prk=kp.getPrivate( );
分析:和 2.7 节一样,使用使用 KeyPairGenerator 类的 genKeyPair( )方法生成密钥对,进而使用密钥对的 getPublic( ) 和 getPrivate( ) 获取公钥和私钥。
//文件: Key_DH.java
import java.io.*;
import java.math.*;
import java.security.*;
import java.security.spec.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import javax.crypto.interfaces.*;
public class Key_DH{
// The 1024 bit Diffie-Hellman modulus values used by SKIP
private static final byte skip1024ModulusBytes[] = {
(byte)0xF4, (byte)0x88, (byte)0xFD, (byte)0x58,
(byte)0x4E, (byte)0x49, (byte)0xDB, (byte)0xCD,
(byte)0x20, (byte)0xB4, (byte)0x9D, (byte)0xE4,
(byte)0x91, (byte)0x07, (byte)0x36, (byte)0x6B,
(byte)0x33, (byte)0x6C, (byte)0x38, (byte)0x0D,
(byte)0x45, (byte)0x1D, (byte)0x0F, (byte)0x7C,
(byte)0x88, (byte)0xB3, (byte)0x1C, (byte)0x7C,
(byte)0x5B, (byte)0x2D, (byte)0x8E, (byte)0xF6,
(byte)0xF3, (byte)0xC9, (byte)0x23, (byte)0xC0,
(byte)0x43, (byte)0xF0, (byte)0xA5, (byte)0x5B,
(byte)0x18, (byte)0x8D, (byte)0x8E, (byte)0xBB,
(byte)0x55, (byte)0x8C, (byte)0xB8, (byte)0x5D,
(byte)0x38, (byte)0xD3, (byte)0x34, (byte)0xFD,
(byte)0x7C, (byte)0x17, (byte)0x57, (byte)0x43,
(byte)0xA3, (byte)0x1D, (byte)0x18, (byte)0x6C,
(byte)0xDE, (byte)0x33, (byte)0x21, (byte)0x2C,
(byte)0xB5, (byte)0x2A, (byte)0xFF, (byte)0x3C,
(byte)0xE1, (byte)0xB1, (byte)0x29, (byte)0x40,
(byte)0x18, (byte)0x11, (byte)0x8D, (byte)0x7C,
(byte)0x84, (byte)0xA7, (byte)0x0A, (byte)0x72,
(byte)0xD6, (byte)0x86, (byte)0xC4, (byte)0x03,
(byte)0x19, (byte)0xC8, (byte)0x07, (byte)0x29,
(byte)0x7A, (byte)0xCA, (byte)0x95, (byte)0x0C,
(byte)0xD9, (byte)0x96, (byte)0x9F, (byte)0xAB,
(byte)0xD0, (byte)0x0A, (byte)0x50, (byte)0x9B,
(byte)0x02, (byte)0x46, (byte)0xD3, (byte)0x08,
(byte)0x3D, (byte)0x66, (byte)0xA4, (byte)0x5D,
(byte)0x41, (byte)0x9F, (byte)0x9C, (byte)0x7C,
(byte)0xBD, (byte)0x89, (byte)0x4B, (byte)0x22,
(byte)0x19, (byte)0x26, (byte)0xBA, (byte)0xAB,
(byte)0xA2, (byte)0x5E, (byte)0xC3, (byte)0x55,
(byte)0xE9, (byte)0x2F, (byte)0x78, (byte)0xC7
};
// The SKIP 1024 bit modulus
private static final BigInteger skip1024Modulus
= new BigInteger(1, skip1024ModulusBytes);
// The base used with the SKIP 1024 bit modulus
private static final BigInteger skip1024Base = BigInteger.valueOf(2);
public static void main(String args[ ]) throws Exception{
DHParameterSpec DHP=new DHParameterSpec(skip1024Modulus,skip1024Base);
KeyPairGenerator kpg= KeyPairGenerator.getInstance("DH");
kpg.initialize(DHP);
KeyPair kp=kpg.genKeyPair();
PublicKey pbk=kp.getPublic();
PrivateKey prk=kp.getPrivate();
FileOutputStream f1=new FileOutputStream(args[0]);
ObjectOutputStream b1=new ObjectOutputStream(f1);
b1.writeObject(pbk);
FileOutputStream f2=new FileOutputStream(args[1]);
ObjectOutputStream b2=new ObjectOutputStream(f2);
b2.writeObject(prk);
}
}
程序最后将公钥和私钥以对象流的形式保存在文件中,文件名通过命令行参数指定,第一个命令行参数对应的文件保存公钥,第二个命令行参数对应的文件保存私钥。
运行程序
建立两个目录 A 和 B ,模拟需要秘密通信的 A 、 B 双方,由于 DH 算法需要 A 和 B 各自生成 DH 公钥和私钥,因此在这两个目录下都拷贝编译后文件 Key_DH 。首先由 A 创建自己的公钥和私钥,即在 A 目录下输入“ java Key_DH Apub.dat Apri.dat ”运行程序,这时在目录 A 下将产生文件 Apub.dat 和 Apri.dat ,前者保存着 A 的公钥,后者保存着 A 的私钥。然后由 B 创建自己的公钥和私钥,即在 B 目录下输入“ java Key_DH Bpub.dat Bpri.dat ”运行程序,这时在目录 B 下将产生文件 Bpub.dat 和 Bpri.dat ,前者保存着 B 的公钥,后者保存着 B 的私钥。最后发布公钥, A 将 Apub.dat 拷贝到 B 目录, B 将 Bpub.dat 拷贝到 A 的目录。这样, A 、 B 双方的 DH 公钥和私钥已经创建并部署完毕。
2.9.2 创建共享密钥
实例说明
DH 算法中, A 可以用自己的密钥和 B 的公钥按照一定方法生成一个密钥, B 也可以用自己的密钥和 A 的公钥按照一定方法生成一个密钥,由于一些数学规律,这两个密钥完全相同。这样, A 和 B 间就有了一个共同的密钥可以用于各种加密。本实例介绍 Java 中在上一小节的基础上如何利用 DH 公钥和私钥各自创建共享密钥。
编程思路:
Java 中 KeyAgreement 类实现了密钥协定,它使用 init( ) 方法传入自己的私钥,使用 doPhase
( )方法传入对方的公钥,进而可以使用 generateSecret( ) 方法生成共享的信息具体步骤如下:
( 1 ) 读取自己的 DH 私钥和对方的 DH 公钥
FileInputStream f1=new FileInputStream(args[0]);
ObjectInputStream b1=new ObjectInputStream(f1);
PublicKey pbk=(PublicKey)b1.readObject( );
FileInputStream f2=new FileInputStream(args[1]);
ObjectInputStream b2=new ObjectInputStream(f2);
PrivateKey prk=(PrivateKey)b2.readObject( );
分析:和 2.3.1 小节类似,从文件中获取密钥。只是分为公钥和私钥两个文件,通过命令行参数传入公钥和私钥文件名,第一个命令行参数为对方的公钥文件名,第二个命令行参数为自己的私钥文件名。
( 2 ) 创建密钥协定对象
KeyAgreement ka=KeyAgreement.getInstance("DH");
分析:密钥协定对象即 KeyAgreement 类型的对象,和 KeyPairGenerator 类类似, KeyAgreement 类是一个工厂类,通过其中预定义的一个静态方法 getInstance( )获取 KeyAgreement 类型的对象。 getInstance( )方法的参数指定为“ DH”。
( 3 ) 初始化密钥协定对象
ka.init(prk);
分析:执行密钥协定对象的 init( )方法,传入第 1 步获得的自己的私钥,它在第 1 步中通过第 2 个命令行参数提供。
( 4 ) 执行密钥协定
ka.doPhase(pbk,true);
分析:执行密钥协定对象的 doPhase( )方法,其第一个参数中传入对方的公钥。在本实例中,只有 A、 B 两方需要共享密钥,因此对方只有一个,因此第二个参数设置为 true。如果有 A、 B、 C 三方需要共享密钥,则对方有两个, doPhase()方法要写两次,每次在第 1 个参数中传入一个公钥,第 2 个参数最初设置为 false,最后一次设置为 true。例如 C 方应该执行 ka.doPhase(pbk_of_A,false); ka.doPhase(pbk_of_B,true); 。一次类推,可以用密钥协定实现多方共享一个密钥。
( 5 ) 生成共享信息
byte[ ] sb=ka.generateSecret();
分析:执行密钥协定对象的 generateSecret( ) 方法,返回字节类型的数组。 A、 B双方得到的该数组的内容完全相同,用它创建密钥也各方完全相同。如可使用 SecretKeySpec k=new SecretKeySpec(sb,"DESede"); 创建密钥。
//文件: KeyAgree.java
import java.io.*;
import java.math.*;
import java.security.*;
import java.security.spec.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import javax.crypto.interfaces.*;
public class KeyAgree{
public static void main(String args[ ]) throws Exception{
FileInputStream f1=new FileInputStream(args[0]);
ObjectInputStream b1=new ObjectInputStream(f1);
PublicKey pbk=(PublicKey)b1.readObject( );
FileInputStream f2=new FileInputStream(args[1]);
ObjectInputStream b2=new ObjectInputStream(f2);
PrivateKey prk=(PrivateKey)b2.readObject( );
KeyAgreement ka=KeyAgreement.getInstance("DH");
ka.init(prk);
ka.doPhase(pbk,true);
byte[ ] sb=ka.generateSecret();
for(int i=0;i<sb.length;i++){
System.out.print(sb[i]+",");
}
SecretKeySpec k=new SecretKeySpec(sb,"DESede");
}
}
程序最后将共享信息打印了出来,以便直观地对比 A 和 B 得到的信息是否相同。
运行程序
将程序 KeyAgree 编译后分别拷贝在 A 和 B 两个目录,首先在 A 目录输入“ java KeyAgree
Bpub.dat Apri.dat ”运行程序,它使用文件 Bpub.dat 中对方的公钥和文件 Apri.dat 中自己的
私钥创建了一段共享的字节数组。然后在 B 目录输入“ java KeyAgree Apub.dat Bpri.dat ”运行程序,它使用文件 Apub.dat 中对方的公钥和文件 Bpri.dat 中自己的私钥创建了一段共享的字节数组。
可以看到 A 和 B 运行后得到的字节数组内容完全相同,因此使用它创建的密钥也将相同。由于 DH 算法内在的数学规律, A 和 B 在运行时只使用了对方可以公开的信息(公钥),而各自独立地得到了相同的密钥,完成了密钥分发工作。该部分介绍了对称加密和非对称加密的基本用法,并演示了使用密钥协定进行密钥分发。读者在掌握了其原理后,可以举一反三,互相组合,满足不同应用。出于实例的简洁,该部分的例子都使用文件来交换信息,读者也可以使用其它方式,如在密钥协定中可以使用 Socket 等多种方式在程序间传递信息。