基礎篇:java.security框架之簽名、加密、摘要及證照

潛行前行發表於2021-03-14

前言

和前端進行資料互動時或者和第三方商家對接時,需要對隱私資料進行加密。單向加密,對稱加密,非對稱加密,其對應的演算法也各式各樣。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認證加密過程
  • 支援演算法: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;
}

歡迎指正文中錯誤

參考文章

相關文章