数字签名应用

  • 引言

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

  • 环境及设备

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

  • 步骤

    1. 将一个计算机中的文件中的所有内容读取到字节数组bytes中,需要保证计算机中存在这个文件。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      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);
      }
    2. 使用椭圆曲线签名算法,需要先得到椭圆曲线签名算法 EC 的生成密钥类 KeyPairGenerator 的一个实例 keyPairGen, 然后初始化 keyPairGen,对于椭圆曲线算法,密钥长度最低为112,生成一对密钥,其中包括了公钥和私钥,存入对象 pair 中,并以十六进制方式输出私钥的内容。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      //使用椭圆曲线签名算法,需要得到椭圆曲线算法的密钥生成一个实例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);
    3. 从对象pair中获取公钥,以十六进制方式输出公钥的内容。

      1
      2
      3
      4
      5
      6
      //从pair中获取公钥
      PublicKey pubKey = pair.getPublic();
      //输出公钥字节内容
      byte[] bytePubKey = pubKey.getEncoded();
      String strPubKey = DatatypeConverter.printHexBinary(bytePubKey);
      System.out.printf("公钥:0x%s\n",strPubKey);
    4. 创建一个签名对象 sign。使用 SHA256 算法作为散列函数,椭圆曲线签名算法 ECDSA 作为签名算法。用私钥 pairKry 来初始化签名对象 sign。然后使用签名对象 sign 的 update 方法加载需要签名的字节数组的内容 bytes,再使用 sign 方法生成签名;以十六进制字节方法打印 bytes 的签名值。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      //创建一个签名对象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);
    5. 使用公钥 pubKey 对签名进行验证。创建一个签名对象 veriSign ,使用 SHA256 算法作为散列函数,椭圆曲线签名算法 ECDSA 作为签名算法。使用公钥 pubKey 来初始化签名对象 VeriSign ,签名对象VeriSign 加载需要验证签名的字节数组内容 bytes。VeriSign 使用公钥 pubKey 对 bytes 进行验证。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      //使用公钥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("验证失败,签名不正确!");
    6. 若签名验证成功,则打印 “验证成功,签名正确!”,否则打印出 “验证失败,签名不正确!”。

  • 实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    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("验证失败,签名不正确!");
    }
    }
  • 实现结果

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