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

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 com.galen.v2ch09.hash;

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

/**
 * This program computes the message digest of a file.
 *
 * @author Cay Horstmann
 * @version 1.20 2012-06-16
 */
public class Digest {
    /**
     * @param args args[0] is the filename, args[1] is optionally the algorithm
     *             (SHA-1, SHA-256, or MD5)
     *             javac com/galen/v2ch09/hash/Digest.java
     *             java com.galen.v2ch09.hash.Digest input.txt
     *             java com.galen.v2ch09.hash.Digest 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 com/galen/v2ch09/hash/Digest.java : 编译指定java文件。
(2) java com.galen.v2ch09.hash.Digest input.txt:运行Digest类,并传入input.txt文件,默认将使用“SHA-1”哈希算法。
(3) java com.galen.v2ch09.hash.Digest input.txt MD5 : 运行Digest类,并传入input.txt 文件,使用MD5哈希算法。

2.2 对称加密算法

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

package com.galen.v2ch09.aes;

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

/**
 * This program tests the AES cipher. Usage:<br>
 * javac com/galen/v2ch09/aes/Util.java<br>
 * javac com/galen/v2ch09/aes/AESTest.java<br>
 * java com.galen.v2ch09.aes.AESTest -genkey secret.key<br>
 * java com.galen.v2ch09.aes.AESTest -encrypt plaintext.txt encrypted.txt secret.key<br>
 * java com.galen.v2ch09.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();
            System.out.print(key.toString());
            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);
                Util.crypt(in, out, cipher);
            }
        }
    }
}

测试类中使用的公共类:

package com.galen.v2ch09.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 com.galen.v2ch09.rsa;

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

/**
 * This program tests the RSA cipher. Usage:<br>
 * javac com/galen/v2ch09/rsa/Util.java<br>
 * javac com/galen/v2ch09/rsa/RSATest.java<br>
 * java com.galen.v2ch09.rsa.RSATest -genkey publickey.key privatekey.key<br>
 * java com.galen.v2ch09.rsa.RSATest -encrypt plaintext.txt rsaencrypted.txt publickey.key<br>
 * java com.galen.v2ch09.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 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")) {
            KeyGenerator keygen = KeyGenerator.getInstance("AES");
            SecureRandom random = new SecureRandom();
            keygen.init(random);
            SecretKey key = keygen.generateKey();

            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);
                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);

                com.galen.v2ch09.rsa.Util.crypt(in, out, cipher);
            }
        }
    }
}

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

package com.galen.v2ch09.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位或者几位改变了,那么消息摘要也将改变。
消息摘要是通过哈希算法计算得到的,如果消息的内容改变了,那么改变后的消息的摘要与原消息的摘要是不匹配的。如果消息的内容与消息的摘要分开传递的话,接受者就可以用摘要来检查消息的内容是否发生过改变。但是如果消息的内容和消息的摘要同时被截获了,对消息进行修改,再重新计算摘要,发送给接收方,那么接收方就无法判断内容是否被篡改过了。所以,仅靠消息摘要,也是无法完全保证消息内容不被篡改的。

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

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代码的实现方式)

5、源码说明

(1) 当使用javac命令编译源码时,需要将teminal定位到source folder目录(一般是source目录)的下一级目录,然后进行编译或者运行。否则,引用其他类时,会出现“错误:找不到符号”。如:
在这里插入图片描述
(2) source folder目录可以在project structure中看到:
在这里插入图片描述
(3) 代码中使用到的文件,统一放在 source folder(本例中为src)目录下,这样可以直接通过文件名获取到。
在这里插入图片描述
本文源码:https://gitee.com/muziye/core-java.git请查看v2ch09章节部分
推荐阅读:
1、阮一峰老师的博文:【数据安全】一、数字签名是什么?
2、【数据安全】一、通俗理解数字签名,数字证书和https

©️2020 CSDN 皮肤主题: 精致技术 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值