这篇Java教程基于JDK1.8。教程中的示例和实践不会使用未来发行版中的优化建议。
验证数字签名
如果你拥有一个已经生成好的数字签名,你就可以验证该数字签名的真实性。要做到这一点,你需要
- 数据
- 数字签名
- 用于对数据签名的私钥对应的公钥
在该例中你将通过VerSign程序来验证通过GenSign程序生成的数字签名。为验证数字签名的真实性需要遵循一些步骤。
VerSig导入一个公钥和一个指定数据文件的签名,然后验证签名的真实性。通过命令行参数来指定公钥,签名文件和数据文件的名字。
创建VerSig示例程序来导入文件并验证签名的步骤如下所示:
1. 准备初始化程序结构
创建一个名为VerSig.java的文本文件。输入初始程序结构(import语句、类名、main方法等)。
这是本课接下来的部分中创建的VerSig程序的基本结构。将这个程序结构放在一个名为VerSig.java的文件中。
import java.io.*;
import java.security.*;
import java.security.spec.*;
class VerSig {
public static void main(String[] args) {
/* Verify a DSA signature */
if (args.length != 3) {
System.out.println("Usage: VerSig " +
"publickeyfile signaturefile " + "datafile");
}
else {
try {
// the rest of the code goes here
} catch (Exception e) {
System.err.println("Caught exception " + e.toString());
}
}
}
}
注意:
- 验证数据的方法都在java.security包中,所以该例将整个包都导入进来。该程序也导入了java.io包是因为需要读取文件中用来验证签名的数据,导入java.security.spec包是为了导入X509EncodedKeySpec类。
- 该例需要三个参数,分别指定公钥、签名和待验证的文件。
- 该例中接下来步骤中的代码块将位于try catch之间
2.输入并转换被编码的公钥字节数组
从命令行第一个参数指定的文件中读取被编码的公钥字节并将它转换为PublicKey对象。
接下来,VerSig需要从指定为第一个命令行参数的文件中导入已编码的公钥字节,并将其转换为公钥。需要公钥,因为签名initVerify方法需要公钥来初始化签名对象以进行验证。
首先,读取已编码的公钥字节。
FileInputStream keyfis = new FileInputStream(args[0]);
byte[] encKey = new byte[keyfis.available()];
keyfis.read(encKey);
keyfis.close();
现在字节数组encKey包含已编码的公钥字节。
可以使用KeyFactory从已编码的公钥字节中实例化一个DSA的公钥Key。KeyFactory类提供不透明秘钥(类型为Key)和秘钥规范之间的转换,后者是底层秘钥材料的表示。使用不透明密钥,可以获得算法名称、格式名称和已编码的密钥字节,但不能获得密钥材料,例如,密钥材料可能由密钥本身和用于计算密钥的算法参数组成。(注意,由于它扩展了Key,所以PublicKey本身就是一个Key。)
因此,首先需要一个秘钥规范。假设密钥是按照X.509标准编码的,例如,如果密钥是由SUN提供的内置DSA密钥对生成器生成的,则可以通过以下方法获得密钥:
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(encKey);
现在需要一个KeyFactory对象来执行转换。该对象必须是一个与DSA密钥一起工作的对象。
KeyFactory keyFactory = KeyFactory.getInstance(“DSA”, “SUN”);
最后,可以使用KeyFactory对象根据密钥规范生成公钥。
PublicKey pubKey = keyFactory.generatePublic(pubKeySpec);
3. 输入签名数组
从命令行第二个参数指定的文件中读取签名字节。
接下来,从第二个命令行参数指定的文件中读取签名字节。
FileInputStream sigfis = new FileInputStream(args[1]);
byte[] sigToVerify = new byte[sigfis.available()];
sigfis.read(sigToVerify);
sigfis.close();
现在字节数组sigToVerify包含签名字节。
4.验证签名
获取一个Signature对象并用公钥初始化它,以验证签名。将第三个命令行参数指定的文件提供给Signature对象并验证其签名。
现在已经向VerSig程序添加了如下代码:
- 读取已编码的密钥字节,并将其转换为名为pubKey的公钥
- 将签名字节读取到名为sigToVerify的字节数组
现在可以执行验证了。
1). 初始化用来验证的签名对象
与签名生成一样,签名验证是通过使用签名类的实例来完成的。需要创建一个生成签名使用相同签名算法的签名对象。GenSig程序使用的算法来自SUN提供的SHA1withDSA算法。
Signature sig = Signature.getInstance(“SHA1withDSA”, “SUN”);
接下来,需要初始化签名对象。验证的初始化方法需要公钥。
sig.initVerify(pubKey);
2). 向签名对象提供要验证的数据
现在需要向Signature对象提供生成签名的数据。数据存在于第三个命令行参数所代表的文件中。与签名时一样,每次读取一个缓冲区中的数据,并通过调用update方法将其提供给Signature对象。
FileInputStream datafis = new FileInputStream(args[2]);
BufferedInputStream bufin = new BufferedInputStream(datafis);
byte[] buffer = new byte[1024];
int len;
while (bufin.available() != 0) {
len = bufin.read(buffer);
sig.update(buffer, 0, len);
};
bufin.close();
3). 验证签名
一旦向Signature对象提供了所有数据,就可以验证该数据的数字签名并生成结果。回想一下,所谓的签名被读入一个名为sigToVerify的字节数组。
boolean verifies = sig.verify(sigToVerify);
System.out.println("signature verifies: " + verifies);
如果所谓的签名(sigToVerify)是与公钥pubKey对应的私钥生成的指定数据文件的实际签名,那么verify值为true。
5.编译并运行程序
这里是VerSig.java程序的所有源码。
编译并运行该示例程序。记住,你需要在命令行中指定三个参数:
- 包含已编码公钥字节文件的名字
- 包含签名字节的名字
- 源数据文件的名字(生成数字签名的文件)
如果你测试了GenSig程序的输出,那么应该使用的文件名应该是
- suepk
- sig
- data
这是一个样本运行;粗体表示你键入的内容。
java VerSig suepk sig data
signature verifies: true