数字签名算法

328 阅读4分钟

数字签名是手写签名在计算机软件应用中的一种体现,它同样起到了抗否认作用。手写签名应用于纸质文件,数字签名应用于数据。数字签名算法要求能够验证数据完整性、认证数据来源,并起到抗否认的作用。

非对称加密算法的密钥对既可以公钥加密,私钥解密(加密) 也可以 私钥加密,公钥解密(签名),公钥能够解密的一定是对应的私钥加密的。公钥解密对应于签名算法的验证,公开的公钥就是说持有公钥的都可以来验证;私钥加密对应于签名算法的签名,私有的私钥说明这个签名就是某人签名的起到抗否认作用。因此非对称加密算法成为数字签名算法的组成部分。

非对称加密算法速度非常慢,有无办法生成一条很短的数据来代替消息本身呢?消息摘要算法刚好符合要求。于是我们不必再对整条消息进行加密(签名),而是先用消息摘要函数求出消息的散列值,然后再对散列值进行加密(签名)就可以了。无论消息有多长,散列值永远都是这么短,因此提高了加密(签名)速度。

数字签名算法就是非对称加密算法与消息摘要算法的结合。但这里的非对称加密算法并不是要真的加密消息,加密的动作只是起到签名的目的,解密的动作只是起到验证签名的目的。

  1. Alice用消息摘要函数计算消息的散列值
  2. Alice用自己的私钥对散列值进行加密(签名)
  3. Alice将消息和密文(签名)发送给Bob
  4. Bob用Alice发布的公钥对收到的密文进行解密(验证)
  5. 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;
    }
}

截屏2024-03-15 23.57.15.png

数字签名无法解决的问题

用数字签名可以识别出篡改和伪装,还可以防止否认。然而要正确使用数字签名的一个大前提,就是用于验证签名的公钥必须属于真正的发送者。即使数字签名技术再强大,如果你得到的公钥是伪造的,那么数字签名也会失效。

要保证验证签名的公钥属于真正的发送者,我们又必须使用数字签名,于是我们陷入了无限套娃逻辑:使用数字签名技术要求公钥可靠,要想公钥可靠必须使用数字签名。

为了能够确认自己得到的公钥是否合法,我们需要使用数字证书。所谓数字证书,就是将公钥当作一条消息,由一个可信的第三方对其签名后所得到的公钥。

当然这样的方法只是把问题转移了而已。为了对证书上施加的数字签名进行验证,我们必定需要另一个公钥,那么如何才能构筑一个可信的数字签名链条呢?又由谁来颁发可信的证书呢?到这一步,我们已经从单纯的编程步入了社会学领域。我们需要让公钥以及数字签名技术成为一种社会性的基础设施,即公钥基础设施(Public Key Infrastructure,PKI)。