引言

数字签名可以确保文件数据的完整性以及不可抵赖性。本次将使用Java语言来实现对文件的数字签名及验证,Java语言的JDK提供了丰富的密码学类库。本次采用了椭圆曲线ECDSA数字签名算法及SHA256散列算法,也可以通过简单的参数选取,使用SHA或其他签名算法。

环境及设备

Windows计算机一台,Java虚拟机 JDK 1.6 及以上版本。

步骤

  1. 将一个计算机中的文件中的所有内容读取到字节数组bytes中,需要保证计算机中存在这个文件。
byte[] bytes = {};
try {
	//获取计算机中文件名 d:\\test.txt的路径
	Path path = Paths.get("d:\\test.txt");
			
	//从文件读取内容到字节数组byte[]中
	bytes = Files.readAllBytes(path);
}catch(Exception e){
	System.out.println("文件读取错误" + e);
}
  1. 使用椭圆曲线签名算法,需要先得到椭圆曲线签名算法 EC 的生成密钥类 KeyPairGenerator 的一个实例 keyPairGen, 然后初始化 keyPairGen,对于椭圆曲线算法,密钥长度最低为112,生成一对密钥,其中包括了公钥和私钥,存入对象 pair 中,并以十六进制方式输出私钥的内容。
//使用椭圆曲线签名算法,需要得到椭圆曲线算法的密钥生成一个实例keyPairGen
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("EC");
		
//初始化密钥对生成器,对椭圆曲线算法,参数最低为112
keyPairGen.initialize(112);
		
//生成一对密钥,其中包括公钥和私钥,存入对象pair中;
KeyPair pair = keyPairGen.generateKeyPair();
		
//从pair中获取私钥
PrivateKey privKey = pair.getPrivate();
		
//输出私钥内容
byte[] bytePrivKey = privKey.getEncoded();
String strPrivKey = DatatypeConverter.printHexBinary(bytePrivKey);
System.out.printf("私钥:0x%s\n",strPrivKey);
  1. 从对象pair中获取公钥,以十六进制方式输出公钥的内容。
//从pair中获取公钥
PublicKey pubKey = pair.getPublic();
//输出公钥字节内容
byte[] bytePubKey = pubKey.getEncoded();
String strPubKey = DatatypeConverter.printHexBinary(bytePubKey);
System.out.printf("公钥:0x%s\n",strPubKey);
  1. 创建一个签名对象 sign。使用 SHA256 算法作为散列函数,椭圆曲线签名算法 ECDSA 作为签名算法。用私钥 pairKry 来初始化签名对象 sign。然后使用签名对象 sign 的 update 方法加载需要签名的字节数组的内容 bytes,再使用 sign 方法生成签名;以十六进制字节方法打印 bytes 的签名值。
//创建一个签名对象sign,使用SHA256算法作为散列函数,椭圆曲线签名算法ECDSA作为签名算法
Signature sign = Signature.getInstance("SHA256withECDSA");
		
//用私钥privKey来初始化签名对象sign
sign.initSign(privKey);
		
//签名对象sign加载需要签名的字节数组内容bytes
sign.update(bytes);
		
//生成签名
byte[] signature = sign.sign();
		
//以十六进制字节方式打印出bytes的签名值
String strSign = DatatypeConverter.printHexBinary(signature);
System.out.printf("签名内容:0x%s\n",strSign);
  1. 使用公钥 pubKey 对签名进行验证。创建一个签名对象 veriSign ,使用 SHA256 算法作为散列函数,椭圆曲线签名算法 ECDSA 作为签名算法。使用公钥 pubKey 来初始化签名对象 VeriSign ,签名对象VeriSign 加载需要验证签名的字节数组内容 bytes。VeriSign 使用公钥 pubKey 对 bytes 进行验证。
//使用公钥pubKey对签名进行验证
System.out.println("正在验证,请稍等------");
		
//创建一个签名对象veriSign,使用SHA256算法作为散列函数,椭圆曲线签名算法ECDSA作为签名算法
Signature veriSign = Signature.getInstance("SHA256withECDSA");
		
//使用公钥pubKey来初始化签名对象VeriSign
veriSign.initVerify(pubKey);
		
//签名对象veriSign加载需要验证签名的字节数组的内容bytes
veriSign.update(bytes);;
		
//签名对象veriSign使用公钥pubKey对bytes的签名signature进行验证
boolean ok = veriSign.verify(signature);
if(ok)
	System.out.println("验证成功,签名正确!");
else
	System.out.println("验证失败,签名不正确!");
  1. 若签名验证成功,则打印 “验证成功,签名正确!”,否则打印出 “验证失败,签名不正确!”。

实现

package digitalsignature;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import javax.xml.bind.DatatypeConverter;

/** 
* @author renhongchang
* @version 创建时间:2021年5月31日 下午2:57:08 
* @blog https://rhc-rgb.github.io
* 
* 随计算机中的一个文件使用椭圆曲线算法生成数字签名
* 然后对该文件的数字签名进行验证
*/
public class DigitalSignature {

	public static void main(String[] args) throws Exception {
		byte[] bytes = {};
		try {
			//获取计算机中文件名 d:\\test.txt的路径
			Path path = Paths.get("d:\\test.txt");
			
			//从文件读取内容到字节数组byte[]中
			bytes = Files.readAllBytes(path);
		}catch(Exception e){
			System.out.println("文件读取错误" + e);
		}
		
		
		//使用椭圆曲线签名算法,需要得到椭圆曲线算法的密钥生成一个实例keyPairGen
		KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("EC");
		
		//初始化密钥对生成器,对椭圆曲线算法,参数最低为112
		keyPairGen.initialize(112);
		
		//生成一对密钥,其中包括公钥和私钥,存入对象pair中;
		KeyPair pair = keyPairGen.generateKeyPair();
		
		//从pair中获取私钥
		PrivateKey privKey = pair.getPrivate();
		
		//输出私钥内容
		byte[] bytePrivKey = privKey.getEncoded();
		String strPrivKey = DatatypeConverter.printHexBinary(bytePrivKey);
		System.out.printf("私钥:0x%s\n",strPrivKey);
		
		
		//从pair中获取公钥
		PublicKey pubKey = pair.getPublic();
		//输出公钥字节内容
		byte[] bytePubKey = pubKey.getEncoded();
		String strPubKey = DatatypeConverter.printHexBinary(bytePubKey);
		System.out.printf("公钥:0x%s\n",strPubKey);
		
		
		//创建一个签名对象sign,使用SHA256算法作为散列函数,椭圆曲线签名算法ECDSA作为签名算法
		Signature sign = Signature.getInstance("SHA256withECDSA");
		
		//用私钥privKey来初始化签名对象sign
		sign.initSign(privKey);
		
		//签名对象sign加载需要签名的字节数组内容bytes
		sign.update(bytes);
		
		//生成签名
		byte[] signature = sign.sign();
		
		//以十六进制字节方式打印出bytes的签名值
		String strSign = DatatypeConverter.printHexBinary(signature);
		System.out.printf("签名内容:0x%s\n",strSign);
		
		//使用公钥pubKey对签名进行验证
		System.out.println("正在验证,请稍等------");
		
		//创建一个签名对象veriSign,使用SHA256算法作为散列函数,椭圆曲线签名算法ECDSA作为签名算法
		Signature veriSign = Signature.getInstance("SHA256withECDSA");
		
		//使用公钥pubKey来初始化签名对象VeriSign
		veriSign.initVerify(pubKey);
		
		//签名对象veriSign加载需要验证签名的字节数组的内容bytes
		veriSign.update(bytes);;
		
		//签名对象veriSign使用公钥pubKey对bytes的签名signature进行验证
		boolean ok = veriSign.verify(signature);
		if(ok)
			System.out.println("验证成功,签名正确!");
		else
			System.out.println("验证失败,签名不正确!");
	}
}

实现结果

私钥:0x302C020100301006072A8648CE3D020106052B8104000604153013020101040EAE0831C7D9E7F76ADF8A0B08D1DF
公钥:0x3032301006072A8648CE3D020106052B81040006031E0004050A0BCE147FF9BFDDA2FD06BD748B983B600DD605296822BD59E317
签名内容:0x3020020E3CBFF953DC7C15041DDC01A56704020E51949B5868C2123534E39ABA2F03
正在验证,请稍等------
验证成功,签名正确!