上一篇帖子,我们讲了MAC(消息认证码),它可以验证身份和防篡改。
它的机制是通过通信双方都持有相同的秘钥去实现,秘钥相同摘要才相同,没有秘钥就不能生成正确的摘要信息。
但是,它有个缺点,就是 通信双方必须持有相同的秘钥,解决方法就是使用数字签名
数字签名(又称公钥数字签名、电子签章)是一种类似写在纸上的普通的物理签名,但是使用了非对称加密领域的技术实现,用于鉴别数字信息的方法。
一套数字签名通常定义两种互补的运算, 私钥用于签名,公钥用于验证(验签)。
这样,就会生成四个文件,其中pkcs8_prikey.der、pubkey.der是给Java用的
执行之后,输出结果为:
输出结果:
在Java8中,输出结果如下:
它的机制是通过通信双方都持有相同的秘钥去实现,秘钥相同摘要才相同,没有秘钥就不能生成正确的摘要信息。
但是,它有个缺点,就是 通信双方必须持有相同的秘钥,解决方法就是使用数字签名
数字签名(又称公钥数字签名、电子签章)是一种类似写在纸上的普通的物理签名,但是使用了非对称加密领域的技术实现,用于鉴别数字信息的方法。
一套数字签名通常定义两种互补的运算, 私钥用于签名,公钥用于验证(验签)。
数字签名是非对称密钥加密技术与数字摘要技术的应用。
既然是非对称加密,就需要有一对秘钥,公钥和私钥
下面演示一下,用OpenSSL生成一对秘钥
#生成RSA私钥,默认是编码方式为PEM的PKCS#1格式
#PKCS#1格式是传统的私钥格式
openssl genrsa -out key.pem 1024
#从私钥中生成公钥,给OpenSSL验签用的
openssl rsa -in key.pem -out pub.pem -pubout
#把PEM编码格式的私钥转换成DER编码的私钥,同时进行PKCS#1转换成PKCS#8(Java默认只能处理PKCS#8的格式)
#-nocrypt 意思是不加密
#给Java用
openssl pkcs8 -topk8 -in key.pem -out pkcs8_prikey.der -inform PEM -outform DER -nocrypt
#从私钥中导出DER编码的公钥
#给Java用
openssl rsa -in key.pem -pubout -outform DER -out pubkey.der
这样,就会生成四个文件,其中pkcs8_prikey.der、pubkey.der是给Java用的
有了秘钥对之后,就可以对文件进行签名了
下面使用Java(1.8.0_144)演示计算apache-tomcat-8.5.23.zip文件的数字签名
package com.security.sign;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import org.bouncycastle.util.encoders.Hex;
public class SignatureTest {
public static KeyPair getKeyPair() throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] publicKeyData = Files.readAllBytes(Paths.get("c:/tmp/pubkey.der"));
byte[] privateKeyData = Files.readAllBytes(Paths.get("c:/tmp/pkcs8_prikey.der"));
X509EncodedKeySpec publicKeySpec= new X509EncodedKeySpec(publicKeyData);
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeyData);
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
return new KeyPair(publicKey, privateKey);
}
/**
* 用私钥生成签名
*
* Signature.getInstance(algorithm) 算法格式为 <digest>with<encryption>
* 支持的算法有:MD5withRSA、SHA256withRSA、SHA256withDSA等等
*
* 全部支持的算法见官方文档:
* https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#Signature
*/
public static byte[] sign(String signatureAlgorithm, PrivateKey privateKey, byte[] data) throws Exception {
Signature sign = Signature.getInstance(signatureAlgorithm);
sign.initSign(privateKey);
sign.update(data);
byte[] result = sign.sign();
return result;
}
/**
* 用公钥验签
*/
public static boolean verify(String signatureAlgorithm, PublicKey publicKey, byte[] data, byte[] signature) throws Exception {
Signature sign = Signature.getInstance(signatureAlgorithm);
sign.initVerify(publicKey);
sign.update(data);
return sign.verify(signature);
}
public static void main(String[] args) throws Exception {
KeyPair keyPair = getKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
String signatureAlgorithm = "SHA256withRSA";
//需要签名的数据
byte[] data = Files.readAllBytes(Paths.get("c:/tmp/apache-tomcat-8.5.23.zip"));
//数据+私钥签名
byte[] signatureData = sign(signatureAlgorithm, privateKey, data);
//把签名转换成十六进制的文本
System.out.println(Hex.toHexString(signatureData));
//数据+公钥+签名结果进行验证
boolean result = verify(signatureAlgorithm, publicKey, data, signatureData);
System.out.println(result);
}
}
执行之后,输出结果为:
a4a68c93f811192fe96f5746c486fa37db6746a6f71b482d7c6a371078b99a567220b3eaf5a984fe
7626dd35eb806adf4cbf63b6e081631172babe8f1785d6f56ddeb9ce5c809f921ac10332cb02c8be
2de304ac20d5ef1c0d9cf7a0874615d27defff751a1fd8dc13849aeeb4ddd0f1ba5d7766e96e9be64
7294ff4a3224033
true
可以看到,签名很长,输出true表示验证通过。
同样的,我们来使用OpenSSL来进行对apache-tomcat-8.5.23.zip进行数字签名
签名的命令:
openssl dgst -sign key.pem -sha256 -hex /tmp/apache-tomcat-8.5.23.zip
结果:
可以看到,和Java签名的结果是一致的
上面这是把签名以十六进制文本输出,下面来同时进行签名和验证
#把签名结果输出到sign.sig
openssl dgst -sign key.pem -sha256 -out sign.sig /tmp/apache-tomcat-8.5.23.zip
#验签
openssl dgst -verify pub.pem -sha256 -signature sign.sig /tmp/apache-tomcat-8.5.23.zip
输出结果:
验签通过
上面的,Signature.getInstance(algorithm) 参数algorithm可以支持的值除了参考官方文档,还可以通过如下代码得出
Security.getAlgorithms("Signature").forEach(System.out::println);
在Java8中,输出结果如下:
NONEWITHDSA
SHA384WITHECDSA
SHA224WITHDSA
SHA256WITHRSA
MD5WITHRSA
SHA1WITHRSA
SHA512WITHRSA
MD2WITHRSA
SHA256WITHDSA
SHA1WITHECDSA
MD5ANDSHA1WITHRSA
SHA224WITHRSA
NONEWITHECDSA
NONEWITHRSA
SHA256WITHECDSA
SHA224WITHECDSA
SHA384WITHRSA
SHA512WITHECDSA
SHA1WITHDSA