数字签名是手写签名在计算机软件应用中的一种体现,它同样起到了抗否认作用。手写签名应用于纸质文件,数字签名应用于数据。数字签名算法要求能够验证数据完整性、认证数据来源,并起到抗否认的作用。
非对称加密算法的密钥对既可以公钥加密,私钥解密(加密) 也可以 私钥加密,公钥解密(签名),公钥能够解密的一定是对应的私钥加密的。公钥解密对应于签名算法的验证,公开的公钥就是说持有公钥的都可以来验证;私钥加密对应于签名算法的签名,私有的私钥说明这个签名就是某人签名的起到抗否认作用。因此非对称加密算法成为数字签名算法的组成部分。
非对称加密算法速度非常慢,有无办法生成一条很短的数据来代替消息本身呢?消息摘要算法刚好符合要求。于是我们不必再对整条消息进行加密(签名),而是先用消息摘要函数求出消息的散列值,然后再对散列值进行加密(签名)就可以了。无论消息有多长,散列值永远都是这么短,因此提高了加密(签名)速度。
数字签名算法就是非对称加密算法与消息摘要算法的结合。但这里的非对称加密算法并不是要真的加密消息,加密的动作只是起到签名的目的,解密的动作只是起到验证签名的目的。
- Alice用消息摘要函数计算消息的散列值
- Alice用自己的私钥对散列值进行加密(签名)
- Alice将消息和密文(签名)发送给Bob
- Bob用Alice发布的公钥对收到的密文进行解密(验证)
- Bob将签名解密后得到的散列值与Alice直接发送的消息的散列值进行对比,相同则签名验证成功,不同则签名验证失败。
RSA数字签名算法主要分为MD系列和SHA系列:
- MD系列:MD2withRSA、MD5withRSA
- SHA系列:SHA1withRSA、SHA224withRSA、SHA256withRSA、SHA384withRSA、SHA512withRSA
除了RSA还有其他的数字签名算法:
- EIGamal
- DSA
- ECDSA
- Rabin
package com.fulcrum.encrypt;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
public class SignatureDemo {
private static final String RSA_ALGORITHM = "RSA";
private static final String SIGN_ALGORITHM = "SHA1withRSA";
private static final String PUBLIC_KEY = "PUBLIC_KEY";
private static final String PRIVATE_KEY = "PRIVATE_KEY";
public static void main(String[] args) throws Exception{
Map<String, byte[]> skMap = initKey();
byte[] bytesPublicKey = skMap.get(PUBLIC_KEY);
byte[] bytesPrivateKey = skMap.get(PRIVATE_KEY);
String str = "论一般五次方程没有求根公式";
byte[] sign = sign(str.getBytes(StandardCharsets.UTF_8), bytesPrivateKey);
boolean flag = verify(str.getBytes(StandardCharsets.UTF_8), bytesPublicKey, sign);
System.out.println("签名验证是否成功:"+flag);
}
public static byte[] sign(byte[] data, byte[] bytesPrivateKey) throws Exception{
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(bytesPrivateKey);
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
Signature signature = Signature.getInstance(SIGN_ALGORITHM);
signature.initSign(privateKey);
signature.update(data);
return signature.sign();
}
public static boolean verify(byte[] data, byte[] bytesPublicKey, byte[] sign) throws Exception{
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(bytesPublicKey);
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
Signature signature = Signature.getInstance(SIGN_ALGORITHM);
signature.initVerify(publicKey);
signature.update(data);
return signature.verify(sign);
}
public static Map<String, byte[]> initKey() throws Exception{
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA_ALGORITHM);
keyPairGenerator.initialize(2048);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
byte[] bytesPublicKey = keyPair.getPublic().getEncoded();
byte[] bytesPrivateKey = keyPair.getPrivate().getEncoded();
Map<String, byte[]> skMap = new HashMap<>();
skMap.put(PUBLIC_KEY, bytesPublicKey);
skMap.put(PRIVATE_KEY, bytesPrivateKey);
return skMap;
}
}
数字签名无法解决的问题:
用数字签名可以识别出篡改和伪装,还可以防止否认。然而要正确使用数字签名的一个大前提,就是用于验证签名的公钥必须属于真正的发送者。即使数字签名技术再强大,如果你得到的公钥是伪造的,那么数字签名也会失效。
要保证验证签名的公钥属于真正的发送者,我们又必须使用数字签名,于是我们陷入了无限套娃逻辑:使用数字签名技术要求公钥可靠,要想公钥可靠必须使用数字签名。
为了能够确认自己得到的公钥是否合法,我们需要使用数字证书。所谓数字证书,就是将公钥当作一条消息,由一个可信的第三方对其签名后所得到的公钥。
当然这样的方法只是把问题转移了而已。为了对证书上施加的数字签名进行验证,我们必定需要另一个公钥,那么如何才能构筑一个可信的数字签名链条呢?又由谁来颁发可信的证书呢?到这一步,我们已经从单纯的编程步入了社会学领域。我们需要让公钥以及数字签名技术成为一种社会性的基础设施,即公钥基础设施(Public Key Infrastructure,PKI)。