数字安全终极武器之数字证书

数字证书

  • 1. 数字证书概念
  • 2. 数字证书编码格式
  • 3. 模型分析
  • 4. 证书管理
    • 4.1 KeyTool证书管理
      • 4.1.1 构建自签名证书
      • 4.1.2 导出数字证书
      • 4.1.3 打印数字证书
      • 4.1.4 构建CA签发证书
    • 4.2 OpenSSL证书管理
      • 4.2.1 openssl安装
      • 4.2.2 构建根证书
      • 4.2.3 构建服务器证书
      • 4.2.4 构建客户端证书
    • 4.3 证书使用
      • 4.3.1 证书算法实现
      • 4.3.2 测试代码
      • 4.3.3 运行结果

1. 数字证书概念

网络OSI模型中安全服务建立在大量密码学算法之上:消息摘要算法用于验证数据完整性服务,对称加密算法和非对称加密算法用于保证数据保密性服务,数字签名算法用于抗否认性服务。

网络安全向来都不是一个简单的事情,单一的防御手段,很难达到期望的安全强度!安全的网络平台,需要多重密码学算法有机结合!

数字证书正是多种密码算法结合的产物:自身带有公钥信息,可以完成数据的加密/解密操作; 同时,携带的数字i签名,可以鉴别消息来源; 自身带有的消息摘要信息,可验证证书完整性; 由于证书本身含有用户身份信息,因而具有认证性。

数字证书为发布公钥提供了一种简单的途径,成为加密算法以及公钥的载体。数字证书也叫做电子证书,类似我们生活中的身份证。数字证书由数字证书颁发机构(Certificate Authority, CA)签发, 只有经过CA签发的证书在网络中才具备认证性。

国际权威数字证书颁发认证机构三巨头: VeriSign, GeoTrust, Thawte,以VeriSign签发的电子商务用数字证书应用最为广泛。通常这三大机构签发证书时需要收费,而且价格不菲。当然我们可以选择申请免费的数字证书,例如CAcert(http://www.cacert.org/)这个数字证书国际组织。

证书签发过程实际是对申请数字证书的公钥做数字签名,证书的验证过程实际是对数字证书公钥做验证签名,其中包含证书有效期验证。

CA机构颁发给自己的证书叫做 “根证书”

通过使用CA颁发的数字证书,我们可以对网络上传输的数据进行加密/解密 和签名/验证操作,确保数据的机密性、完整性和抗否认性。数字证书中包含的用户信息,保证了认证性、真实性。

实际上,数字证书采用了公钥基础设施(Public Key Infrastructure, PKI),使用了相应的加密算法确保网络应用的安全性:

  • 非对称加密算法用于对数据加密/解密操作,确保数据的机密性。
  • 数字签名算法用于对数据进行签名/验证操作,确保数据的完整性和抗否认性。
  • 消息摘要算法用于对数字证书本身做摘要处理,确保数字证书完整性。

目前,数字证书中最为常用的非对称加密算法是RSA算法,与之配套使用的签名算法是SHA1withRSA算法,而最为常用的消息摘要算法是SHA1算法。

除了RSA算法外,还可以使用DSA算法。DSA算法证书不包含加密/解密功能。

2. 数字证书编码格式

数字证书有多种文件编码格式,主要包含cer编码、der编码等:

  • cer (Canonical Encoding Rules, 规范编码格式), 是ber(Basic Encoding Rules,基本编码格式)的一个变种,比ber规范。
  • der (Distinguished Encoding Rule, 卓越编码格式)同样是ber的一个变种, 区别:der使用定长模式,cer使用变长模式

所有证书符合公钥基础设施(PKI)制定的ITU-T X509国际标准(X.509标准),目前包含3个版本。

  • PKCS (Public-Key Cryptography Standards,公钥加密标准)由RSA实验室等制定的标准。
    PKCS常用标准
公钥加密标准 描述信息 文件后缀
PKCS#7 密码消息语法标准 .p7b、 .p7c、.spc
PKCS#10 证书请求语法标准 .p10、.csr
PKCS#12 个人信息交换语法标准 .p12、.pfx

以上标准主要用于证书的申请和更新等操作,例如,PKCS#10文件用于证书签发申请, PKCS#12文件可作为Java中密钥库或信任库直接使用。

通常使用Base64编码格式作为数字证书存储格式,如下图所示

在这里插入图片描述
实际应用中,很多数字证书属于自签名证书,即证书申请者为自己的证书签名。

3. 模型分析

证书签发
数字安全终极武器之数字证书_第1张图片

加密交互
数字安全终极武器之数字证书_第2张图片

4. 证书管理

证书通常是向权威CA机构申请,任何机构和个人都可以申请。常用数字证书管理工具是KeyTool(Java领域)和OpenSSL。OpenSSL比KeyTool强大很多。

获取数字证书流程:我们使用数字证书管理工具构建CSR(Certification Siging Request,数字证书签发申请),交由CA机构签发,形成最终数字证书。

自签名证书:自己也可以给自己签名,这种签名后的证书叫做自签名证书。
根证书:用于给证书签名的证书叫做根证书。

4.1 KeyTool证书管理

KeyTool是Java中数字证书管理工具,用于数字证书的申请、导入、导出和撤销操作。
KeyTool与本地密钥库相关联,私钥存于密钥库,公钥以数字证书输出。

4.1.1 构建自签名证书

~/certificationTest$ keytool -genkeypair -keyalg RSA -keysize 2048 -sigalg SHA1withRSA -validity 36000 -alias www.calvin.com -keystore calvin.keystore

参数含义:

-genkeypair 表示生成密钥
-keyalg 表示密钥算法,这里指定为RSA算法
-keysize 指定密钥长度,默认1024,这里指定2048位。
-sigalg 指定数字签名算法,这里指定为SHA1withRSA算法。
-validity 指定证书有效期,这里指定为36000天。
-keystore 指定密钥库存储位置,且DSA算法为默认算法。

执行上述命令,会要求交互式输入密码、组织、域名等信息, 域名也可以使用带通配符"*"的泛域名,如*.calvin.com标识用户身份。
可以使用 -storepass 指定密码, 使用-dname指定用户信息。完整命令如下:

~/certificationTest$ keytool -genkeypair -keyalg RSA -keysize 2048 -sigalg SHA1withRSA -validity 36000 -alias www.calvin.com -keystore calvin.keystore -storepass 123456 -dname "CN=www.calvin.com, OU=calvin, O=calvin, L=hz, ST=hz, C=CN"

4.1.2 导出数字证书

上述操作后,密钥库中已经创建了数字证书。虽然没有经过CA机构认证,但是仍然可以使用。下面是导出证书命令。

keytool -exportcert -alias www.calvin.com -keystore calvin.keystore -file calvin.cer -rfc

参数含义:
-exportcert 表示证书导出操作
-alias 指定别名,这里是www.calvin.com
-keystore 指定密钥库文件,这里是calvin.keystore
-file 指定导出文件路径,这里是calvin.cer
-rfc 指定以Base64编码格式输出

执行上述命令,会要求输入密码,可以使用-storepass 参数指定密码。

执行结果如下图所示
在这里插入图片描述

4.1.3 打印数字证书

查看证书命令如下:

~certificationTest$ keytool -printcert -v -file calvin.cer

参数说明:
-printcert 表示证书打印操作
-file 指定要打印的证书文件
-v 详细信息
执行结果如下图所示
数字安全终极武器之数字证书_第3张图片
上述的calvin.cer 证书是一个自签名的X.509 v3类型的根证书,并以Base64编码保存。
在使用自签名证书时,我们需要将其导入系统。

4.1.4 构建CA签发证书

要想获取CA机构认证的数字证书,需要将数字证书签发申请(CSR)导出,经由CA机构认证并颁发,同时将认证后的证书导入本地密钥库和信任库。

导出数字证书签发申请命令如下

~certificationTest$ keytool -certreq -alias www.calvin.com -keystore calvin.keystore -file calvin.csr -v -storepass 123456

参数说明:
-certreq 表示数字证书申请操作。
-alias 指定别名
-file 指定导出文件路径
-keystore 指定密钥库文件,这里是calvin.keystore
-v 详细信息
执行结果如下图所示
在这里插入图片描述
calvin.csr 文件是一个PKCS#10编码格式的数字证书签发申请文件。

CA证书签发机构

  • VeriSign 收费
  • GeoTrust 收费
  • Thawte 收费
  • CAcert 免费

获取到签发的数字证书后,将其导入信任库,导入数字证书命令如下:

~certificationTest$ keytool -importcert -trustcacerts -alias www.calvin.com -file calvin.cer -keystore calvin.keystore -storepass 123456

参数说明:
-importcert 表示数字证书导入操作。
-alias 指定别名
-file 指定要导入的证书文件路径
-trustcacerts 表示将数字证书导入信任库
-keystore 指定密钥库文件,这里是calvin.keystore
-storepass 密钥库密码

查看导入的数字证书, 命令如下:

~certificationTest$ keytool -list -alias www.calvin.com -keystore calvin.keystore -storepass 123456 -v

执行结果如下图所示:
在这里插入图片描述
参数说明:
-list 表示导入的数字证书列表
-alias 指定别名
-keystore 指定密钥库文件,这里是calvin.keystore

上述命令中,可以加入“-v” 或 “-rfc” 参数查看证书详细信息。

数字安全终极武器之数字证书_第4张图片

4.2 OpenSSL证书管理

OpenSSL是 ssl/tls协议的开源实现,也是常用的证书管理工具, 可用于根证书、服务器证书、客户端证书的管理,功能远胜Keytool。

4.2.1 openssl安装

请到openssl官网下载安装,openssl官网下载地址:https://www.openssl.org/source/
作者的电脑是Ubuntu,所以安装的是linux版本。

首先做一些准备工作,构建一些目录,用于存放证书、密钥, 构建一些文件用于证书生成过程。
工作目录在

certificationTest$

在该目录下依次键入下面的命令

#构建已发行证书存放目录 certs
mkdir certs 
#构建新证书存放目录 newcerts
mkdir newcerts
#构建私钥存放目录
mkdir private
#构建证书吊销列表存放目录crl
mkdir crl
# 构建索引文件
echo 0>index.txt
#构建序列号文件 serial
echo 01>serial

接下来,我们可以进行证书构建和签发工作了。

4.2.2 构建根证书

#构建随机数文件, 需要root权限
openssl rand -out private/.rand 1000
#构建根证书私钥 private/ca.key.pem
openssl genrsa -aes256 -out private/ca.key.pem 2048

openssl genrsa -aes256 -out private/ca.key.pem 2048 命令参数解释如下:

genrsa 产生RSA密钥命令
-aes256 使用AES算法(256位密钥)对产生的私钥加密。可选算法DES、DESede、IDEA和AES
-out 指定私钥输出路径

上述命令需要交互式输入密钥。

完成密钥构建后,下一步是生成根证书签发申请文件(ca.csr), 命令如下:

#生成根证书签发申请
openssl req -new -key private/ca.key.pem -out private/ca.csr -subj"/C=CN/ST=zj/L=hz/O=calvin/OU=calvin/CN=*.calvin.com"

参数含义如下:
req 产生证书签发申请命令
-new 表示新请求
-key 密钥
-out 证书申请文件输出路径
-subj 指定用户信息

得到根证书申请文件后,我们可以发给CA机构签发,也可以自行签发根证书。

签发根证书命令如下:

#签发根证书private/ca.cer
openssl x509 -req -days 10000 -sha1 -extensions v3_ca -signkey private/ca.key.pem -in private/ca.csr -out certs/ca.cer

参数含义:
x509 签发X.509格式证书命令
-req 表示证书输入请求
-days 表示有效天数
-sha1 证书摘要算法,这里为SHA1算法
-extensions 表示按OpenSSL配置文件v3_ca项添加扩展
-signkey 表示自签名密钥,这里是private/ca.key.pem
-in 表示输入文件
-out 表示输出文件

OpenSSL产生的数字证书不能在Java环境下直接属于,需要转成PKCS#12编码格式,转换命令如下:

#证书转换
openssl pkcs12 -export -cacerts -inkey private/ca.key.pem -in certs/ca.cer -out certs/ca.p12

参数含义

pkcs12 PKCS#12编码格式证书命令
-export 导出证书
-cacerts 仅导出CA证书
-inkey 表示输入密钥
-in 表示输入密钥
-out 表示输出文件

certs/ca.p12是PKCS#12格式的文件,可以作为密钥库或可信任库使用, 使用keytool工具可以查看其详细信息。
查看命令如下:

keytool -list -keystore certs/ca.p12 -storetype pkcs12 -v -storepass 123456

查询结果如下图所示:
数字安全终极武器之数字证书_第5张图片
现在,我们已经有了根证书(ca.cer), 可以使用根证书签发服务器证书和客户证书了。

4.2.3 构建服务器证书

构建服务器证书的命令同根证书命令几乎一致

  1. 构建服务器私钥
openssl genrsa -aes256 -out private/server.key.pem 2048

执行结果如下图所示:
数字安全终极武器之数字证书_第6张图片

  1. 生成服务器签发申请
openssl req -new -key private/server.key.pem -out private/server.csr -subj "/C=CN/ST=HZ/L=HZ/O=calvin/OU=calvin/CN=www.calvin.com"
  1. 签发服务器证书
openssl x509 -req -days 3650 -sha1 -extensions v3_req -CA certs/ca.cer -CAkey private/ca.key.pem -CAserial ca.srl -CAcreateserial -in private/server.csr -out certs/server.cer
  1. 证书转换为p12格式
 openssl pkcs12 -export -cacerts -inkey private/server.key.pem -in certs/server.cer -out certs/server.p12

4.2.4 构建客户端证书

客户证书构建与服务器证书构建基本一致,这里就省略相关构建流程。

至此, 所有证书文件构建完成,目录结构如下图所示
数字安全终极武器之数字证书_第7张图片

4.3 证书使用

以keytool生成的证书文件为例,演示证书的操作,包括加密/解密以及签名/验证等。
密钥库可以看作私钥的操作入口, 数字证书则可以看作公钥匙操作入口。

4.3.1 证书算法实现

package com.calvin.android.demo2.secrity;

import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

import javax.crypto.Cipher;

/**
 * Author:cl
 * Email:[email protected]
 * Date:20-10-26
 */
public class CertificateCoder {
    public static final String CERT_TYPE = "X.509";

    /**
     * 由KeyStore获得私钥
     * @param keyStorePath 密钥库路径
     * @param alias 别名
     * @param password 密码
     * @return PrivateKey 私钥
     * @throws Exception 异常
     */
    private static PrivateKey getPrivateKeyByKeyStore(String keyStorePath, String alias, String password) throws Exception {
        //获取密钥库
        KeyStore ks = getKeyStore(keyStorePath, password);
        //获得私钥
        return (PrivateKey) ks.getKey(alias, password.toCharArray());
    }


    private static PublicKey getPublicKeyByCertificate(String certificatePath) throws Exception{
        //获得证书
        Certificate certificate = getCertificate(certificatePath);
        //获得公钥
        return certificate.getPublicKey();
    }

    private static Certificate getCertificate(String certificatePath) throws Exception {
        //实例化证书工厂
        CertificateFactory certificateFactory = CertificateFactory.getInstance(CERT_TYPE);
        //取得证书文件流
        FileInputStream in = new FileInputStream(certificatePath);
        Certificate certificate = certificateFactory.generateCertificate(in);
        in.close();
        return certificate;
    }

    /**
     * 获得Certificate
     * @param keyStorePath 密钥库路径
     * @param alias 别名
     * @param password 密码
     * @return Certificate 证书
     * @throws Exception 异常
     */
    private static Certificate getCertificate(String keyStorePath, String alias, String password) throws  Exception {
        //获取密钥库
        KeyStore ks = getKeyStore(keyStorePath, password);
        //获得证书
        return ks.getCertificate(alias);
    }

    /**
     * 获取KeyStore
     * @param keyStorePath 密钥库路径
     * @param password 密码
     * @return KeyStore 密钥库
     * @throws Exception 异常
     */
    private static KeyStore getKeyStore(String keyStorePath, String password) throws Exception{
        String ksType = KeyStore.getDefaultType();
        System.out.println("getKeyStore, ksType = "+ksType);
        //实例化密钥库
        KeyStore ks = KeyStore.getInstance(ksType);
        //获取密钥库文件流
        FileInputStream is = new FileInputStream(keyStorePath);
        //加载密钥库
        ks.load(is, password.toCharArray());
        //关闭密钥库文件流
        is.close();
        return ks;
    }

    /**
     * 私钥加密
     * @param data 待加密数据
     * @param keyStorePath 密钥库路径
     * @param alias 别名
     * @param password 密码
     * @return byte[] 加密数据
     * @throws Exception
     */
    public static byte[] encryptByPrivateKey(byte[] data, String keyStorePath, String alias, String password) throws Exception {
        //取得私钥
        PrivateKey privateKey = getPrivateKeyByKeyStore(keyStorePath, alias, password);
        //对数据加密
        Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, privateKey);
        return cipher.doFinal(data);
    }

    /**
     * 私钥解密
     * @param data 待解密数据
     * @param keyStorePath 密钥库路径
     * @param alias 别名
     * @param password 密码
     * @return byte[] 解数据
     * @throws Exception
     */
    public static byte[] decryptByPrivateKey(byte[] data, String keyStorePath, String alias, String password) throws Exception {
        //取得私钥
        PrivateKey privateKey = getPrivateKeyByKeyStore(keyStorePath, alias, password);
        //对数据解密
        Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return cipher.doFinal(data);
    }



    /**
     * 公钥加密
     * @param data 待加密数据
     * @param certificatePath 证书路径
     * @return byte[] 加密数据
     * @throws Exception
     */
    public static byte[] encryptByPublicKey(byte[] data, String certificatePath) throws Exception {
        //取得公钥
        PublicKey publicKey = getPublicKeyByCertificate(certificatePath);
        //对数据加密
        Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        return cipher.doFinal(data);
    }

    /**
     * 公钥解密
     * @param data 待解密数据
     * @param certificatePath 证书路径
     * @return byte[] 解数据
     * @throws Exception
     */
    public static byte[] decryptByPublicKey(byte[] data, String certificatePath) throws Exception {
        //取得私钥
        PublicKey publicKey = getPublicKeyByCertificate(certificatePath);
        //对数据解密
        Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, publicKey);
        return cipher.doFinal(data);
    }

    /**
     * 亲名
     * @param data byte[] 待签名数据
     * @param keyStorePath 密钥库路径
     * @param alias 别名
     * @param password 密码
     * @return byte[] 签名后的数据
     * @throws Exception
     */
    public static byte[] sign(byte[] data, String keyStorePath, String alias, String password) throws Exception {
        X509Certificate x509Certificate = (X509Certificate) getCertificate(keyStorePath, alias, password);
        Signature signature = Signature.getInstance(x509Certificate.getSigAlgName());
        PrivateKey privateKey = getPrivateKeyByKeyStore(keyStorePath, alias, password);

        signature.initSign(privateKey);
        signature.update(data);
        return signature.sign();
    }

    /**
     * 验签
     * @param data 数据
     * @param sign 签名
     * @param certificatePath 证书路径
     * @return boolean 验证通过为true
     * @throws Exception
     */
    public static boolean verify(byte[] data, byte[] sign, String certificatePath) throws Exception {
        //获取证书
        X509Certificate x509Certificate = (X509Certificate) getCertificate(certificatePath);

        Signature signature = Signature.getInstance(x509Certificate.getSigAlgName());
        //由证书初始化签名,实际上是使用了证书中的公钥
        signature.initVerify(x509Certificate);
        signature.update(data);
        return signature.verify(sign);


    }
}

4.3.2 测试代码

private String password = "123456";
    private String alias = "www.calvin.com";
    private String certificatePath = Environment.getExternalStorageDirectory().getAbsolutePath()+ File.separator+"calvin.cer";

    private String keyStorePath = Environment.getExternalStorageDirectory().getAbsolutePath()+ File.separator+"calvin.bks";

    //java.io.IOException: Wrong version of key store.
    //at com.android.org.bouncycastle.jcajce.provider.keystore.bc.BcKeyStoreSpi.engineLoad(BcKeyStoreSpi.java:811)
    //at java.security.KeyStore.load(KeyStore.java:1247)
    // 该异常解决办法链接
    // http://shaoyance.com/2020/04/20/35-https%E5%8F%8C%E5%90%91%E8%AE%A4%E8%AF%81%E5%AE%9E%E8%B7%B5/

    //java.io.IOException: Wrong version of key store.的解决记录
    //http://www.xidige.com/785
     @Test
    public void certificateTest() throws Exception {
        System.out.println("公钥加密-私钥解密");
        String inputStr = "数字证书";
        byte[] data = inputStr.getBytes();
        //公钥加密
        byte[] encrypt = CertificateCoder.encryptByPublicKey(data, certificatePath);
        //私钥解密
        byte[] decrypt = CertificateCoder.decryptByPrivateKey(encrypt, keyStorePath, alias, password);
        String outputStr = new String(decrypt);
        System.out.println("加密前:\n"+inputStr);
        System.out.println("解密后:\n"+outputStr);
        //验证一致性
        assertArrayEquals(data, decrypt);
    }
     @Test
    public void certificateTest2() throws Exception {
        System.out.println("私钥加密-公钥解密");
        String inputStr = "数字证书";
        byte[] data = inputStr.getBytes();
        //私钥加密
        byte[] encrypt = CertificateCoder.encryptByPrivateKey(data, keyStorePath, alias, password);
        //公钥解密
        byte[] decrypt = CertificateCoder.decryptByPublicKey(encrypt, certificatePath);
        String outputStr = new String(decrypt);
        System.out.println("加密前:\n"+inputStr);
        System.out.println("解密后:\n"+outputStr);
        //验证一致性
        assertEquals(inputStr, outputStr);
    }
     @Test
    public void certificateSignTest() throws Exception {
        String inputStr = "签名";
        byte[] data = inputStr.getBytes();
        System.out.println("私钥签名-公钥验证");
        //产生签名
        byte[] sign = CertificateCoder.sign(data, keyStorePath, alias, password);
        System.out.println("签名:\n"+Hex.toHexString(sign));
        //验证签名
        boolean status = CertificateCoder.verify(data, sign,certificatePath);
        System.out.println("状态: \n"+status);
        assertTrue(status);
    }

4.3.3 运行结果

2020-10-27 11:40:17.427 24007-24022/com.calvin.android.demo2 I/System.out: 公钥加密-私钥解密
2020-10-27 11:40:17.456 24007-24022/com.calvin.android.demo2 I/System.out: getKeyStore, ksType = BKS
2020-10-27 11:40:17.652 24007-24022/com.calvin.android.demo2 I/System.out: 加密前:
2020-10-27 11:40:17.652 24007-24022/com.calvin.android.demo2 I/System.out: 数字证书
2020-10-27 11:40:17.653 24007-24022/com.calvin.android.demo2 I/System.out: 解密后:
2020-10-27 11:40:17.653 24007-24022/com.calvin.android.demo2 I/System.out: 数字证书
2020-10-27 11:41:07.447 24088-24103/com.calvin.android.demo2 I/System.out: 私钥加密-公钥解密
2020-10-27 11:41:07.448 24088-24103/com.calvin.android.demo2 I/System.out: getKeyStore, ksType = BKS
2020-10-27 11:41:07.671 24088-24103/com.calvin.android.demo2 I/System.out: 加密前:
2020-10-27 11:41:07.671 24088-24103/com.calvin.android.demo2 I/System.out: 数字证书
2020-10-27 11:41:07.671 24088-24103/com.calvin.android.demo2 I/System.out: 解密后:
2020-10-27 11:41:07.671 24088-24103/com.calvin.android.demo2 I/System.out: 数字证书
2020-10-27 11:41:50.918 24168-24183/com.calvin.android.demo2 I/System.out: 私钥签名-公钥验证
2020-10-27 11:41:50.918 24168-24183/com.calvin.android.demo2 I/System.out: getKeyStore, ksType = BKS
2020-10-27 11:41:50.982 24168-24183/com.calvin.android.demo2 I/System.out: getKeyStore, ksType = BKS
2020-10-27 11:41:51.127 24168-24183/com.calvin.android.demo2 I/System.out: 签名:
2020-10-27 11:41:51.127 24168-24183/com.calvin.android.demo2 I/System.out: 9734edf6f2bf4b62583f633505576085db3474df3835af8b331c5c6f09e6ee2f20ac163badd5dc6800b33ac437d28704c80092e3620d4c474948da48180a5d1115f46027ca51b03810732933c4db8aaa22dd1943ad4b9a098256691a99289ac7f149d3c53a5a3fc560652b0efdd1b7bcd28e2d5cc2768f1bc4a7b59c8a2a8d681293b944496e6f088d373ad6f07920e2f27ae1eb210597206a65c317bd72e0c41c1af6c67b15e85bb5f4b374771102177b01e20f8f5e7d42da2f674aacb3b14c7d370d3c570dd8f5954d98b6427530cf7cd5dac9b94aecc4bb6bcf8c97f21e401a90c8d0a0789e52295585c37bfa3836d9c82e32fbb0c915d38fab8e7841bae0
2020-10-27 11:41:51.153 24168-24183/com.calvin.android.demo2 I/System.out: 状态: 
2020-10-27 11:41:51.153 24168-24183/com.calvin.android.demo2 I/System.out: true

你可能感兴趣的:(加密安全)