引言
数字签名可以确保文件数据的完整性以及不可抵赖性。本次将使用Java语言来实现对文件的数字签名及验证,Java语言的JDK提供了丰富的密码学类库。本次采用了椭圆曲线ECDSA数字签名算法及SHA256散列算法,也可以通过简单的参数选取,使用SHA或其他签名算法。
环境及设备
Windows计算机一台,Java虚拟机 JDK 1.6 及以上版本。
步骤
- 将一个计算机中的文件中的所有内容读取到字节数组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);
}
- 使用椭圆曲线签名算法,需要先得到椭圆曲线签名算法 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);
- 从对象pair中获取公钥,以十六进制方式输出公钥的内容。
//从pair中获取公钥
PublicKey pubKey = pair.getPublic();
//输出公钥字节内容
byte[] bytePubKey = pubKey.getEncoded();
String strPubKey = DatatypeConverter.printHexBinary(bytePubKey);
System.out.printf("公钥:0x%s\n",strPubKey);
- 创建一个签名对象 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);
- 使用公钥 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("验证失败,签名不正确!");
- 若签名验证成功,则打印 “验证成功,签名正确!”,否则打印出 “验证失败,签名不正确!”。
实现
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
正在验证,请稍等------
验证成功,签名正确!