【数据安全】一、数字签名、数字证书、数据加密

1 前言

本文介绍,在数据安全相关领域,常见的问题场景,以及相应的处理策略。涉及到三类算法:哈希算法、对称加密算法、非对称加密算法;以及两个场景:数字签名(数字证书也是数字签名一种处理方式)、内容加密。

2 三类算法

2.1 哈希算法

常见的hash算法包括“MD5、SHA-1、SHA-256、SHA-384、SHA-512”。不论消息内容有多长,使用hash算法,总能得到指定位数的一个序列。例如,采用SHA-1算法计算以下内容的消息摘要:
“Upon my death, my property shall be divided equally among my children; however, my son George shall receive nothing.”
得到的结果是:
“12 5F 09 03 E7 31 30 19 2E A6 E7 E4 90 43 84 B4 38 99 8F 67”
以下是代码示例:

package hash;

import java.io.*;
import java.nio.file.*;
import java.security.*;

/**
 * This program computes the message digest of a file.
 * @version 1.20 2012-06-16
 * @author Cay Horstmann
 */
public class Digest
{
   /** 
    * @param args args[0] is the filename, args[1] is optionally the algorithm 
    * (SHA-1, SHA-256, or MD5)
    * javac hash/Digest.java
    * java hash.Digest hash/input.txt
    * java hash.Digest hash/input.txt MD5
    */
   public static void main(String[] args) throws IOException, GeneralSecurityException
   {
      String algname = args.length >= 2 ? args[1] : "SHA-1";                     
      MessageDigest alg = MessageDigest.getInstance(algname);
      byte[] input = Files.readAllBytes(Paths.get(args[0]));
      byte[] hash = alg.digest(input);
      String d = "";
      for (int i = 0; i < hash.length; i++)
      {
         int v = hash[i] & 0xFF;
         if (v < 16) d += "0";
         d += Integer.toString(v, 16).toUpperCase() + " ";
      }
      System.out.println(d);
   }
}

如果使用idea开发工具可以打开teminal输入以下命令进行测试:
在这里插入图片描述
(1) javac hash/Digest.java : 编译指定java文件,注意在源文件的上一级目录进行编译(否则当Digest类引用其他自定义类的时候将报错)。
(2) java hash.Digest hash/input.txt :运行Digest类,并传入input.txt文件,默认将使用“SHA-1”哈希算法。
(3) java hash.Digest hash/input.txt MD5 : 运行Digest类,并传入input.txt 文件,使用MD5哈希算法。

2.2 对称加密算法

对称加密算法是应用较早的加密算法,又称为共享密钥加密算法。常见的对称加密算法包括DES、3DES、AES 等。
在 对称加密算法 中,使用的密钥只有一个,发送 和 接收 双方都使用这个密钥对数据进行 加密 和 解密。这就要求加密和解密方事先都必须知道加密的密钥。
数据加密过程:在对称加密算法中,数据发送方 将 明文 (原始数据) 和 加密密钥 一起经过特殊 加密处理,生成复杂的 加密密文 进行发送。
数据解密过程:数据接收方 收到密文后,若想读取原数据,则需要使用 加密使用的密钥 及相同算法的 逆算法 对加密的密文进行解密,才能使其恢复成可读明文。

package aes;

import java.io.*;
import java.security.*;
import javax.crypto.*;

/**
 * This program tests the AES cipher. Usage:<br>
 * javac aes/Util.java<br>
 * javac aes/AESTest.java<br>
 * java aes.AESTest -genkey secret.key<br>
 * java aes.AESTest -encrypt plaintext.txt encrypted.txt secret.key<br>
 * java aes.AESTest -decrypt encrypted.txt decrypted.txt secret.key<br>
 *
 * @author Cay Horstmann
 * @version 1.01 2012-06-10
 */
public class AESTest
{
   public static void main(String[] args) 
      throws IOException, GeneralSecurityException, ClassNotFoundException
   {
      if (args[0].equals("-genkey"))
      {
         KeyGenerator keygen = KeyGenerator.getInstance("AES");
         SecureRandom random = new SecureRandom();
         keygen.init(random);
         SecretKey key = keygen.generateKey();
         try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(args[1])))
         {
            out.writeObject(key);
         }
      }
      else
      {
         int mode;
         if (args[0].equals("-encrypt")) {
            mode = Cipher.ENCRYPT_MODE;
         }
         else {
            mode = Cipher.DECRYPT_MODE;
         }


         try (ObjectInputStream keyIn = new ObjectInputStream(new FileInputStream(args[3]));
            InputStream in = new FileInputStream(args[1]);
            OutputStream out = new FileOutputStream(args[2]))
         {
            Key key = (Key) keyIn.readObject();
            Cipher cipher = Cipher.getInstance("AES");
            cipher.init(mode, key);
            aes.Util.crypt(in, out, cipher);
         }
      }
   }
}

测试类中使用的公共类:

package aes;

import java.io.*;
import java.security.*;
import javax.crypto.*;

public class Util
{
   /**
    * Uses a cipher to transform the bytes in an input stream and sends the transformed bytes to an
    * output stream.
    * @param in the input stream
    * @param out the output stream
    * @param cipher the cipher that transforms the bytes
    */
   public static void crypt(InputStream in, OutputStream out, Cipher cipher) throws IOException,
         GeneralSecurityException
   {
      int blockSize = cipher.getBlockSize();
      int outputSize = cipher.getOutputSize(blockSize);
      byte[] inBytes = new byte[blockSize];
      byte[] outBytes = new byte[outputSize];

      int inLength = 0;
      boolean more = true;
      while (more)
      {
         inLength = in.read(inBytes);
         if (inLength == blockSize)
         {
            int outLength = cipher.update(inBytes, 0, blockSize, outBytes);
            out.write(outBytes, 0, outLength);
         }
         else more = false;
      }
      if (inLength > 0) outBytes = cipher.doFinal(inBytes, 0, inLength);
      else outBytes = cipher.doFinal();
      out.write(outBytes);
   }
}

2.3 非对称加密算法

非对称加密算法也有叫做“公共密钥加密技术”。非对称加密算法是基于“公共密钥”和“私有密钥”两个基本概念的。它的设计思想是你可以将公共密钥告诉任何人,但是只有自己才持有“私有密钥”,你需要保护你的“私有密钥”,不将其泄露给任何人。常见的非对称加密算法如DSA、RSA、ECC等。“公共密钥”和“私有密钥”是相对的,通过指定工具产生一组密钥,将其中任一个密钥作为公有密钥公布出去,另外一个即为私有密钥。
由于非对称加密算法更加复杂,加解密过程相对于对称加密会慢很多,所以当需要加密的信息内容很多的情况下,一般对正文内容会采用对称加密(会有一个密钥),然后对密钥进行非对称加密。以下代码,展示对一个对称加密的的密钥进行非对称加密的过程。

package rsa;

import java.io.*;
import java.security.*;
import javax.crypto.*;

/**
 * This program tests the RSA cipher. Usage:<br>
 * javac rsa/Util.java<br>
 * javac rsa/RSATest.java<br>
 * java rsa.RSATest -genkey publickey.key privatekey.key<br>
 * java rsa.RSATest -encrypt plaintext.txt rsaencrypted.txt publickey.key<br>
 * java rsa.RSATest -decrypt rsaencrypted.txt rsadecrypted.txt privatekey.key<br>
 * @author Cay Horstmann
 * @version 1.01 2012-06-10 
 */
public class RSATest
{
   private static final int KEYSIZE = 512;

   public static void main(String[] args) 
      throws IOException, GeneralSecurityException, ClassNotFoundException
   {
      if (args[0].equals("-genkey"))
      {
         //KeyPairGenerator产生一组公私钥对
         KeyPairGenerator pairgen = KeyPairGenerator.getInstance("RSA");
         //生成一个随机种子
         SecureRandom random = new SecureRandom();
         pairgen.initialize(KEYSIZE, random);
         KeyPair keyPair = pairgen.generateKeyPair();
         try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(args[1])))
         {
            out.writeObject(keyPair.getPublic());
         }
         try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(args[2])))
         {
            out.writeObject(keyPair.getPrivate());
         }
      }
      else if (args[0].equals("-encrypt"))
      {
         //生成一个AES对称加密的密钥
         KeyGenerator keygen = KeyGenerator.getInstance("AES");
         SecureRandom random = new SecureRandom();
         keygen.init(random);
         SecretKey key = keygen.generateKey();

         // 使用RSA公共密钥对AES密钥进行加密
         try (ObjectInputStream keyIn = new ObjectInputStream(new FileInputStream(args[3]));
            DataOutputStream out = new DataOutputStream(new FileOutputStream(args[2]));
            InputStream in = new FileInputStream(args[1]) )
         {
            Key publicKey = (Key) keyIn.readObject();
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.WRAP_MODE, publicKey);
            byte[] wrappedKey = cipher.wrap(key);
            out.writeInt(wrappedKey.length);
            out.write(wrappedKey);
         
            cipher = Cipher.getInstance("AES");
            cipher.init(Cipher.ENCRYPT_MODE, key);
            rsa.Util.crypt(in, out, cipher);
         }         
      }
      else
      {
         try (DataInputStream in = new DataInputStream(new FileInputStream(args[1]));
            ObjectInputStream keyIn = new ObjectInputStream(new FileInputStream(args[3]));
            OutputStream out = new FileOutputStream(args[2]))
         {
            int length = in.readInt();
            byte[] wrappedKey = new byte[length];
            in.read(wrappedKey, 0, length);

            // unwrap with RSA private key
            Key privateKey = (Key) keyIn.readObject();
   
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.UNWRAP_MODE, privateKey);
            Key key = cipher.unwrap(wrappedKey, "AES", Cipher.SECRET_KEY);
   
            cipher = Cipher.getInstance("AES");
            cipher.init(Cipher.DECRYPT_MODE, key);
   
            rsa.Util.crypt(in, out, cipher);
         }
      }
   }
}

代码中使用到的公共类如下:

package rsa;

import java.io.*;
import java.security.*;
import javax.crypto.*;

public class Util
{
   /**
    * Uses a cipher to transform the bytes in an input stream and sends the transformed bytes to an
    * output stream.
    * @param in the input stream
    * @param out the output stream
    * @param cipher the cipher that transforms the bytes
    */
   public static void crypt(InputStream in, OutputStream out, Cipher cipher) throws IOException,
         GeneralSecurityException
   {
      int blockSize = cipher.getBlockSize();
      int outputSize = cipher.getOutputSize(blockSize);
      byte[] inBytes = new byte[blockSize];
      byte[] outBytes = new byte[outputSize];

      int inLength = 0;
      ;
      boolean more = true;
      while (more)
      {
         inLength = in.read(inBytes);
         if (inLength == blockSize)
         {
            int outLength = cipher.update(inBytes, 0, blockSize, outBytes);
            out.write(outBytes, 0, outLength);
         }
         else more = false;
      }
      if (inLength > 0) outBytes = cipher.doFinal(inBytes, 0, inLength);
      else outBytes = cipher.doFinal();
      out.write(outBytes);
   }
}

3 数字签名

java核心技术卷2【core java】中有这样一个例子,一位亿万富翁生前留下遗嘱:“我死了之后,我的财产将由我的孩子们评分,但是,我的儿子George应该拿不到一分钱”。遗嘱的信息是公开的(不用保密),但是遗嘱的内容不能被篡改。那么数字签名的作用,就类似于指纹或者个人手写签名的作用。

数字签名是数据安全中的典型应用之一,主要作用是保证数据内容在传输过程中,没有被篡改。数字签名的使用方法包括:1、计算出消息摘要,2、对消息摘要加密,3、证书认证。

3.1 消息摘要(message digest)

消息摘要(message digest),即数据信息的数字指纹。消息摘要的主要特性:如果数据的1位或者几位改变了,那么消息摘要也将改变。
消息摘要是通过哈希算法计算得到的,如果消息的内容改变了,那么改变后的消息的摘要与原消息的摘要是不匹配的。如果消息的内容与消息的摘要分开传递的话,接受者就可以用摘要来检查消息的内容是否发生过改变。但是如果消息的内容和消息的摘要同时被截获了,对消息进行修改,再重新计算摘要,发送给接收方,那么接收方就无法判断内容是否被篡改过了。所以,仅靠消息摘要,也是无法完全保证消息内容不被篡改的。

这里就涉及到了数据签名的更加严谨的方式:计算出消息摘要后,再对消息摘要加密(即消息签名)。

扫描二维码关注公众号,回复: 10785449 查看本文章

3.2 数字签名

消息签名涉及到非对称加密算法。下面以Alice给Bob发送了一个邮件,采用数字签名的方式,实现的具体步骤:
1、Alice写了一个邮件;
2、使用hash算法,如MD5算法,计算得到该邮件内容的摘要(一个指定长度的hash字符串);
3、Alice用它的私钥对摘要加密;
4、将邮件以及加密后的摘要发送给Bob;
5、Bob用Alice的公钥解密邮件摘要,得到摘要A;
6、Bob采用同样的hash算法计算该邮件的摘要,得到摘要B;
结论:
1、上述第5步,如果使用Alice的公钥解开了摘要,那么就说明该邮件是Alice发送的;
2、上述第5步,第6步得到的摘要A=摘要B,则证明邮件在传输过程中没有被修改过。

进一步讨论,这里可能产生新的问题:公钥是公开的并且可以自行导入到电脑,如果George偷偷在Bob的电脑用自己公钥替换了Alice的公钥,然后用自己的私钥给Bob发送Email,这时Bob收到邮件其实是被George冒充的,但是他无法察觉。所以这里引申出来另外解决办法:数字证书。

3.3 数字证书

上面第2点描述的安全漏洞根源在哪?就是Alice的公钥很容易被替换!那么数字证书是怎么生成的呢?以及如何配合数字签名工作呢?

1、首先Alice去找"证书中心"(certificate authority,简称CA),为公钥做认证。证书中心用自己的私钥,对Alice的公钥和一些相关信息一起加密,生成"数字证书"(Digital Certificate):
在这里插入图片描述
2、A在邮件正文下方除了数字签名,另外加上这张数字证书
在这里插入图片描述
3、Bob收到Email后用CA的公钥解密这份数字证书,拿到Alice的公钥,然后验证数字签名,后面流程就和上一步的流程一样了,不再赘述。

和数字签名一样我在梳理这个流程时有下面几点疑惑:
(1) 假设数字证书被伪造了呢?
答案:是的,传输中数字证书有可能被篡改。因此数字证书也是经过数字签名的,是不是感觉很绕貌似陷入了“鸡生蛋蛋生鸡”,我保证这是最后一个蛋- - !上文说道数字签名的作用就是验证数据来源以及数据完整性!B收到邮件后可以先验证这份数字证书的可靠性,通过后再验证数字签名。

(2) 要是有1万个人要给B发邮件,难道Bob要保存1万份不同的CA公钥吗?
答案:不需要,CA认证中心给可以给Bob一份“根证书”,里面存储CA公钥来验证所有CA分中心颁发的数字证书。CA中心是分叉树结构,类似于公安部->省公安厅->市级派出所,不管A从哪个CA分支机构申请的证书,Bob只要预存根证书就可以验证下级证书可靠性。

(3) 如何验证根证书可靠性?
答案:无法验证。根证书是自验证证书,CA机构是获得社会绝对认可和有绝对权威的第三方机构,这一点保证了根证书的绝对可靠。如果根证书都有问题那么整个加密体系毫无意义。

4 内容加密

上面内容介绍的都是对数字签名进行认证,确保信息传递过程中的不被篡改。那么数据安全的另外一个方面,就是数据内容加密。并不是所有信息都需要加密,加密后的信息内容是不可见的,在用户账户,用户密码,个人身份等敏感信息传递过程中使用。

  • 对于内容加密,有两种处理方式,对称加密(对称加密算法,对称密码)和非对称加密(非对称加密算法,公共密钥,私有密钥)。
  • 对称加密,采用同一密码,运算快,安全度低;非对称加密,采用公私钥加密,运算慢,安全度高。当数据内容量大的情况下,我们采用对称加密,同时将对称密码采用非对称加密。(详细见2.3代码的实现方式)

本文源码:https://gitee.com/muziye/core-java.git请查看v2ch09章节部分
推荐阅读:
1、阮一峰老师的博文:【数据安全】一、数字签名是什么?
2、【数据安全】一、通俗理解数字签名,数字证书和https

发布了28 篇原创文章 · 获赞 28 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/xujinggen/article/details/105479092