专栏首页潜行前行基础篇:java.security框架之签名、加密、摘要及证书

基础篇:java.security框架之签名、加密、摘要及证书

前言

和前端进行数据交互时或者和第三方商家对接时,需要对隐私数据进行加密。单向加密,对称加密,非对称加密,其对应的算法也各式各样。java提供了统一的框架来规范(java.security)安全加密这类API。下面将一一介绍

  • 加密算法概念及分类
  • 秘钥生成
  • 摘要算法工具-MessageDigest
  • 签名算法工具-Signature
  • 常用加密工具类-Cipher
  • Certificate-证书的保存
  • KeyStore-密钥证书的实体类
  • https证书加载

关注公众号,一起交流;微信搜一搜: 潜行前行

1 加密算法概念及分类

常用的加密算法类型有三种,如下:

  • 单向加密:也就是不可逆的加密,例如MD5,SHA,HMAC
  • 对称加密:也就是加密方和解密方利用同一个秘钥对数据进行加密和解密,例如DES,PBE等等
  • 非对称加密:非对称加密分为公钥和秘钥,二者是非对称的,例如用私钥加密的内容需要使用公钥来解密,使用公钥加密的内容需要用私钥来解密,DSA,RSA

2 秘钥生成

对称加密密钥的生成

  • KeyGenerator用于生成对称秘钥(可逆加密),或者一个密码性秘钥
  • 支持算法:AES、ARCFOUR、DES、DESede、HmacMD5、HmacSHA1、HmacSHA224、HmacSHA256、HmacSHA384、HmacSHA512、RC2
public static final KeyGenerator getInstance(String algorithm, String provider)
public static final KeyGenerator getInstance(String algorithm)
public final void init(int keysize)
public final void init(int keysize, SecureRandom random)
public final void init(SecureRandom random)
public final void init(AlgorithmParameterSpec params, SecureRandom random)
public final SecretKey generateKey()
  • 示例
public static void main(String[] args) throws  Exception {
    SecretKey secretKey = generatorDesKey();
    System.out.println(secretKey);
}
public static SecretKey generatorDesKey() throws NoSuchAlgorithmException {
    KeyGenerator keyGen = KeyGenerator.getInstance("DES");
    SecureRandom random = new SecureRandom();
    random.nextBytes(new byte[128]);
    keyGen.init(56,random);
    SecretKey key = keyGen.generateKey();
    return key;
}
------------输出结果------------------
com.sun.crypto.provider.DESKey@185c3

非对称加密秘钥的生成

  • KeyPairGenerator用于生成非对称加密算法的密钥对KeyPair,KeyPair会包括一个公钥和私钥
  • 支持算法:DiffieHellman、DSA、RSA、RSASSA-PSS、EC
//KeyPairGenerator.java
public static KeyPairGenerator getInstance(String algorithm)
public static KeyPairGenerator getInstance(String algorithm, String provider)
public void initialize(int keysize, SecureRandom random)
public void initialize(AlgorithmParameterSpec params, SecureRandom random)
public final KeyPair genKeyPair() 
//KeyPair.java
public PublicKey getPublic()
public PrivateKey getPrivate()
  • 示例
public static void main(String[] args) throws Exception {
    KeyPair keyPair = generatorRsaKey();
    System.out.println(keyPair);
}
public static KeyPair generatorRsaKey() throws Exception {
    KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
    SecureRandom random = new SecureRandom();
    random.nextBytes(new byte[516]);
    keyGen.initialize(516,random);
    KeyPair keyPair = keyGen.genKeyPair();
    System.out.println(keyPair.getPrivate());
    System.out.println(keyPair.getPublic());
    return keyPair;
}
  • 输出结果
SunRsaSign RSA private CRT key, 516 bits
  params: null
  modulus: 126519853979546358862851378153247782379894323767375778571361894186790679401365500006956495592162216057219204240578435837612184688685910973224797092901015673
  private exponent: 84346569319697572575234252102165188253262882511583852380907929457860452934243188047935652497010382336410866699832067872276413297543254894848799721123249067
Sun RSA public key, 516 bits
  params: null
  modulus: 126519853979546358862851378153247782379894323767375778571361894186790679401365500006956495592162216057219204240578435837612184688685910973224797092901015673
  public exponent: 3
java.security.KeyPair@5010be6

密钥Key和密钥规格KeySpec的相互转化

❝If the key is stored on a hardware device, its specification may contain information that helps identify the key on the device ❞

❝KeySpec是一个接口,用来组成加密密钥的密钥内容的(透明)规范。如果密钥存储在硬件设备上,则其规范可以包含有助于标识该设备上的密钥的信息 ❞

  • KeySpec具有规范性,所以一般会根据外部参数生成KeySpec,再根据KeySpec生成对应的Key(个人理解,如有高见,请说出你的见解)。SecretKeyFactory、KeyFactory的作用就是转换Key与KeySpec

SecretKeyFactory:用于对称加密的密钥和密钥规格之间的转换,配合KeyGenerator使用

  • 支持算法:AES、ARCFOUR、DES、DESede、PBEWithMD5AndDES、PBEWithHmacSHA256AndAES_128、PBKDF2WithHmacSHA256
public static final SecretKeyFactory getInstance(String algorithm)
public static final SecretKeyFactory getInstance(String algorithm, String provider)
public final SecretKey translateKey(SecretKey key)
public final SecretKey generateSecret(KeySpec keySpec)
public final KeySpec getKeySpec(SecretKey key, Class<?> keySpec)
  • 示例
public static void main(String[] args) throws Exception {
    SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
    byte[] DESKey = "helloWWW".getBytes(StandardCharsets.UTF_8);// 设置密钥
    DESKeySpec keySpec = new DESKeySpec(DESKey);// 设置密钥参数
    SecretKey key = keyFactory.generateSecret(keySpec);// 得到密钥对象
    System.out.println(key);
}
------------输出结果------------------
com.sun.crypto.provider.DESKey@18e49

KeyFactory:用于非对称加密的密钥和密钥规格之间的转换,配合KeyPairGenerator使用

  • 支持算法:DiffieHellman、DSA、RSA、RSASSA-PSS、EC
//KeyFactory.java
public static KeyFactory getInstance(String algorithm)
public static KeyFactory getInstance(String algorithm, String provider)
public final PublicKey generatePublic(KeySpec keySpec)
public final PrivateKey generatePrivate(KeySpec keySpec)
public final <T extends KeySpec> T getKeySpec(Key key, Class<T> keySpec)
  • 示例
public static void main(String[] args) throws Exception {
    //生成RSA秘钥对;generatorRsaKey是上面示例提供的函数
    KeyPair keyPair = generatorRsaKey();
    System.out.println(keyPair);
    //PublicKey转KeySpec;KeySpec再转PublicKey
    X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(keyPair.getPublic().getEncoded());
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    PublicKey pubKey = keyFactory.generatePublic(pubKeySpec);
    System.out.println(pubKey);
    //PrivateKey转KeySpec;KeySpec再转PrivateKey
    PKCS8EncodedKeySpec priKeySpec = new PKCS8EncodedKeySpec(keyPair.getPrivate().getEncoded());
    PrivateKey priKey = keyFactory.generatePrivate(priKeySpec);
    System.out.println(priKey);
}
  • 输出结果
java.security.KeyPair@78e03bb5
Sun RSA public key, 1024 bits
  params: null
  modulus: 94134923375030889337699664145116176095803777687781162111756914700229869014912695784710407302811615186395818803402552376808400599961548587586207216709744471870318354813036696801675648731428269930963470277811176883827680414539855481218813862408748594430021606927061565116386180650249935749556615770533203721821
  public exponent: 65537
SunRsaSign RSA private CRT key, 1024 bits
  params: null
  modulus: 94134923375030889337699664145116176095803777687781162111756914700229869014912695784710407302811615186395818803402552376808400599961548587586207216709744471870318354813036696801675648731428269930963470277811176883827680414539855481218813862408748594430021606927061565116386180650249935749556615770533203721821
  private exponent: 67868152791098303572124282937222322055125020915630253288684471666171190487123683962152169691286583419399765605089805755591451063493647416931630849589322449230367252892862038338916192807582203337302166911147185956153147905653905702289234855039234840869874793012808454810161546053566242403672442319692325665473

3 摘要算法-MessageDigest和javax.crypto.Mac(HMAC)

  • 单向加密是不可逆的,MD5、SHA、MAC都是属于单向加密算法的一种,也称之为摘要算法
  • MD5、SHA它们会根据明文用哈希算法计算一个固定长度的摘要(哈希值),然后把明文和摘要发送给接收者,接收者根据同样的算法计算出摘要,对比两个摘要是否一样即可验证明文的正确性,它的应用场景是:防止篡改和校验数据
  • MD5、SHA等算法是开源的,容易被试探出来。有没有更安全的摘要算法呢?HMAC-带密钥(密码)的hash函数,用一个密钥和一个明文消息作为输入,生成一个消息摘要。密钥一般使用KeyGenerator创建,相当于一个密码值,其被试探出的概率小
  • MessageDigest支持的算法:MD2、MD5、SHA-1、SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256
  • javax.crypto.Mac支持的算法:HmacMD5、HmacSHA1、HmacSHA224、HmacSHA256、HmacSHA384、HmacSHA512、PBEWithHmacSHA1
  • MD5的示例
MessageDigest digest = MessageDigest.getInstance("MD5");
System.out.println(new String(digest.digest("hello world!".getBytes())));
System.out.println(new String(digest.digest("hello world!".getBytes())));
------------输出结果------------------
0���G?�w
0���G?�w
  • MAC的示例
public static void main(String[] args) throws Exception {
    // 初始化HmacMD5摘要算法的密钥产生器
    KeyGenerator generator = KeyGenerator.getInstance("HmacMD5");
    // 产生密钥
    SecretKey secretKey = generator.generateKey();
    //SecretKeySpec继承于SecretKey和KeySpec,因此可直接用SecretKeySpec初始化Mac
    //SecretKey secretKey = new SecretKeySpec("password".getBytes(), "HmacMD5");
    Mac mac = Mac.getInstance("HmacMD5");
    mac.init(secretKey);
    //计算摘要
    String data = "hello world";
    byte[] result1 = mac.doFinal(data.getBytes());
    byte[] result2 = mac.doFinal(data.getBytes());
    System.out.println(new String(result1).equals(new String(result2)));
}
------------输出结果------------------    
true

4 签名算法工具-Signature

  • 签名算法其实也是加密算法,它加密后的数据具有唯一标识性,就像一个人的签名能代表一个人身份。签名一般是指用非对称加密算法的私钥来加密明文的过程,生成的密文可以被持有公钥的人识别解密,只要你的公钥是准确对应无误的,就能保证你解密的数据是来自持有私钥的一方
  • 如何保证公钥是正确无误,没被篡改的?1:一对一给你,2:获取公钥后通过权威机构认证,相关过程可以看下之前写的一篇文章网络篇:朋友面试之https认证加密过程[1]
  • 支持算法:NONEwithRSA、MD2withRSA、MD5withRSA、SHA512/224withRSA、SHA512/256withRSA、RSASSA-PSS、NONEwithDSA、SHA512withDSA、NONEwithECDSA、SHA512withECDSA、MD5withRSAandMGF1(太多了,选择列举几个)
  • Signature.API示例,配合KeyPairGenerator使用
public static void main(String[] args) throws Exception {
    KeyPair keyPair = generatorRsaKey();
    Signature signature = Signature.getInstance("MD5withRSA");
    signature.initSign(keyPair.getPrivate());
    //加解密数据
    byte[] data = "hello world".getBytes();
    //数据签名
    signature.update(data);
    byte[] digest = signature.sign();
    //数据解密加验证
    signature.initVerify(keyPair.getPublic());
    signature.update(data);
    System.out.println("验证结果:"+signature.verify(digest));
}
------------输出结果------------------
验证结果:true

5 常用加密工具类-Cipher

  • 用于加密/解密数据。支持各种类型的算法:对称加密(例如AES),非对称加密(例如RSA)
  • 支持算法:AES、AESWrap、ARCFOUR、Blowfish、DES、DESede、DESedeWrap、ECIES、RSA(太多了,选择列举几个)
  • 示例
public static void main(String[] args) throws Exception {
    KeyPair keyPair = generatorRsaKey();
    Cipher cipher = Cipher.getInstance("RSA");
    // 编码前设定编码方式及密钥
    cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPrivate());
    //加解密数据
    byte[] data = "hello world".getBytes();
    //数据签名
    byte[] enData = cipher.doFinal(data);
    //数据解密
    cipher.init(Cipher.DECRYPT_MODE, keyPair.getPublic());
    byte[] newData = cipher.doFinal(enData);
    System.out.println("验证结果:"+new String(newData));
}
------------输出结果------------------
验证结果:hello world

6 Certificate-证书存储

  • CertificateFactory:用于创建公钥证书(Certificate)和证书吊销列表(CRL)
  • Certificate及其子类X509Certificate
  • CertPath和CertPathBuilder:用于构建证书链(也称为证书路径)
  • CertPathValidator:用于验证证书链
  • CRL:证书吊销列表
  • CertStore:用于存储检索证书和CRL
  • CertificateFactory和Certificate的示例
  • 示例
//certificateStream是证书的输入流
public static PublicKey getPublicKeyByCer(InputStream certificateStream) throws Exception{
    CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
    Certificate certificate = certificateFactory.generateCertificate(certificateStream);
    return certificate.getPublicKey();
}

7 KeyStore-密钥证书的实体类

  • KeyStore用于存储私钥和证书(公钥在证书Certificate里面)
  • 公钥:是一个详细的实体的数字关联,并有意让所有想同这个实体发生信任关系的其他实体知道.公共钥匙用来检验签名;
  • 私钥:是一些数字,私有和公共钥匙存在所有用公共钥匙加密的系统的钥匙对中.公共钥匙用来加密数据,私有钥匙用来计算签名.公钥加密的消息只能用私钥解密,私钥签名的消息只能用公钥检验签名。
  • 示例
public static void main(String[] args) throws Exception {
    InputStream certificateStream = null;
    //根据Certificate生成KeyStore
    CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
    KeyStore keyStore = KeyStore.getInstance("PKCS12");
    keyStore.load(null);
    keyStore.setCertificateEntry("certificate", certificateFactory.generateCertificate(certificateStream));
    //加载jks文件,并生成KeyStore
    KeyStore trustKeyStore = KeyStore.getInstance("jks");
    FileInputStream trustKeyStoreFile = new FileInputStream("/root/trustKeyStore.jks");
    trustKeyStore.load(trustKeyStoreFile, "password".toCharArray());
}

8 java.https加载证书的API

  • KeyManagerFactory、TrustManagerFactory => KeyManager、TrustManager => SSLContext => SSLEngine、SSLSocketFactory、SSLSocket

一般的证书加载过程

  • 用Certificate、KeyStore生成创建KeyManagerFactory和TrustManagerFactory
  • KeyManagerFactory和TrustManagerFactory用来创建KeyManager和TrustManager
  • 而KeyManager和TrustManager用来初始化SSLContext
  • 然后使用SSLContext,创建实际实现SSL/TLS协议的对象(SSLSocketFactory、SSLSocket或者SSLEngine)
  • SSLSocket和SSLEngine可以直接在通信对象中使用
  • KeyManager和TrustManager作用:
    • KeyManager负责向对等端显示使用的凭证(使用的密码标准、加密算法、证书、公钥、签名等)
    • TrustManager负责验证从对等端收到的凭证,验证凭证有多种方式:其中之一是创建CertPath对象,并让JDK的内置公钥基础结构(PKI)框架处理验证。在内部,CertPath实现可能会创建一个Signature对象,并使用它来验证证书链中的每个签名
  • 示例:生成SSLContext,并使用SSLContext初始化apache-httpClient
public static String postWithSSL(String url, String jsonBody) throws Exception {
    SSLContext sslContext = getSslContext();
    SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
            sslContext, new String[]{"TLSv1.2", "TLSv1.1", "TLSv1"}, null,
            SSLConnectionSocketFactory.getDefaultHostnameVerifier());
    RequestConfig config = RequestConfig.custom()
            .setConnectTimeout(3000)
            .setSocketTimeout(3000)
            .build();
    CloseableHttpClient client = HttpClients.custom()
            .setSSLSocketFactory(sslConnectionSocketFactory)
            .setDefaultRequestConfig(config).build();
    HttpPost httpPost = new HttpPost(url);
    //httpPost.setHeaders(headers);
    httpPost.setHeader("Content-Type", "application/json; charset=utf-8");
    httpPost.setHeader("Accept", "application/json");
    httpPost.setEntity(new StringEntity(jsonBody, StandardCharsets.UTF_8));
    HttpResponse response = client.execute(httpPost);
    HttpEntity responseEntity = response.getEntity();
    String result = EntityUtils.toString(responseEntity, "UTF-8");
    return result;
}
//双向加密 SSLContext
private static SSLContext getSslContext() throws Exception {
    //自身私钥
    KeyStore identityKeyStore = KeyStore.getInstance("jks");
    FileInputStream identityKeyStoreFile = new FileInputStream("/root/myServer.jks");
    identityKeyStore.load(identityKeyStoreFile, "password1".toCharArray());
    //服务端信任证书
    KeyStore trustKeyStore = KeyStore.getInstance("jks");
    FileInputStream trustKeyStoreFile = new FileInputStream("/root/trustKeyStore.jks");
    trustKeyStore.load(trustKeyStoreFile, "password".toCharArray());
    //构建SSLContexts
    return SSLContexts.custom()
            .loadKeyMaterial(identityKeyStore, "password1".toCharArray()) // load identity keystore
            .loadTrustMaterial(trustKeyStore, null) // load trust keystore
            .build();
}
//双向加密 SSLContext 方式二
private static SSLContext getSslContext2() throws Exception{
    //自身私钥
    KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    KeyStore keystore = KeyStore.getInstance("jks");
    keystore.load(new FileInputStream(new File("/root/myServer.jks")), "password".toCharArray());
    keyFactory.init(keystore, "password".toCharArray());
    KeyManager[] keyManagers = keyFactory.getKeyManagers();
    //服务端信任证书
    TrustManagerFactory trustFactory = TrustManagerFactory.getInstance("SunX509");
    KeyStore tsStore = KeyStore.getInstance("jks");
    tsStore.load(new FileInputStream(new File("/root/trustKeyStore.jks")), "password".toCharArray());
    trustFactory.init(tsStore);
    TrustManager[] trustManagers = trustFactory.getTrustManagers();
    //初始化SSLContext
    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(keyManagers, trustManagers, null);
    return sslContext;
}

欢迎指正文中错误

参考文章

  • JCA-Java加密框架[2]
  • Java加密框架(JCA)简要说明[3]
  • Java加密解密之MAC[4]
  • 关于keyGenerator,KeyPairGenerator,SecretKeyFactory的解析[5]
  • JCA 实践记录——SecretKeyFactory[6]
  • HttpClient 双向认证[7]
  • java内置可用加密算法文档[8]
  • key解析[9]

Reference

[1]

网络篇:朋友面试之https认证加密过程: https://juejin.cn/post/6918203360315179022

[2]

JCA-Java加密框架: https://www.jianshu.com/p/a8194c237363

[3]

Java加密框架(JCA)简要说明: https://www.cnblogs.com/libin6505/p/10442194.html

[4]

Java加密解密之MAC: https://blog.csdn.net/x_san3/article/details/80613605

[5]

关于keyGenerator,KeyPairGenerator,SecretKeyFactory的解析: https://www.cnblogs.com/jeremy-blog/p/5289731.html

[6]

JCA 实践记录——SecretKeyFactory: https://www.jianshu.com/p/f66a0b93b919

[7]

HttpClient 双向认证: https://blog.csdn.net/xintonghanchuang/article/details/103298943

[8]

java内置可用加密算法文档: https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#KeyGenerator

[9]

key解析: https://www.cnblogs.com/littlefishxu/p/3969180.html

本文分享自微信公众号 - 潜行前行(qianxingcsc),作者:cscw

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-01-21

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Akka-CQRS(13)- SSL/TLS for gRPC and HTTPS:自签名证书产生和使用

    到现在,我们已经完成了POS平台和前端的网络集成。不过,还是那句话:平台系统的网络安全是至关重要的。前一篇博客里我们尝试实现了gRPC ssl/tls网络连...

    用户1150956
  • 【译】用Java创建你的第一个区块链-part2:可交易

    区块链是分布式数据存储、点对点传输、共识机制、加密算法等计算机技术的新型应用模式。所谓共识机制是区块链系统中实现不同节点之间建立信任、获取权益的数学算法 。

    凯哥Java
  • TLS握手:回顾1.2、迎接1.3

    HTTPS或者说SSL or TLS现在都是老生常谈的东西了,为什么还要写这篇文章?

    FB客服
  • 苹果强制使用HTTPS传输后APP开发者必须知道的事

    作者asher,加入腾讯多年,主要负责腾讯CDN和云业务的HTTPS开发和运营工作。

    WeTest质量开放平台团队
  • 图解 | 数字签名和数字证书的前世今生

    这是 2020 年一个平平无奇的周末,小北在家里刷着 B 站,看着喜欢的 up 主视频。

    帅地
  • ​Android抓包总结-HTTPS单向认证&双向认证突破

    在被问到抓包时的一些问题:证书、单向认证、双向认证怎么处理,以及绕过背后的原理时,一时很难说清个大概,于是整理了下思绪,将这些知识进行总结和整理,末尾再对一个某...

    辞令
  • 图说“数字签名”

    虽然很久以前就了解了数字签名,但之前突然被一个非程序员朋友问起什么是数字签名时,依然解释得很费力。近日找图片素材看到Peggy_Marco大神的众...

    林喜东
  • HTTPS科普扫盲帖

    本文作者:IMWeb 陈映平 原文出处:IMWeb社区 未经同意,禁止转载 为什么需要https HTTP是明文传输的,也就意味着,介于发送端、接收端...

    IMWeb前端团队
  • 最详细的 HTTPS 科普扫盲帖

    HTTP是明文传输的,也就意味着,介于发送端、接收端中间的任意节点都可以知道你们传输的内容是什么。这些节点可能是路由器、代理等。

    哲洛不闹

扫码关注云+社区

领取腾讯云代金券