基礎(chǔ)篇:Java.Security框架之簽名、加密、摘要及證書
本文轉(zhuǎn)載自微信公眾號「潛行前行」,作者cscw。轉(zhuǎn)載本文請聯(lián)系潛行前行公眾號。
前言
和前端進行數(shù)據(jù)交互時或者和第三方商家對接時,需要對隱私數(shù)據(jù)進行加密。單向加密,對稱加密,非對稱加密,其對應(yīng)的算法也各式各樣。java提供了統(tǒng)一的框架來規(guī)范(java.security)安全加密這類API。下面將一一介紹
- 加密算法概念及分類
- 秘鑰生成
- 摘要算法工具-MessageDigest
- 簽名算法工具-Signature
- 常用加密工具類-Cipher
- Certificate-證書的保存
- KeyStore-密鑰證書的實體類
- https證書加載
1 加密算法概念及分類
常用的加密算法類型有三種,如下:
- 單向加密:也就是不可逆的加密,例如MD5,SHA,HMAC
- 對稱加密:也就是加密方和解密方利用同一個秘鑰對數(shù)據(jù)進行加密和解密,例如DES,PBE等等
- 非對稱加密:非對稱加密分為公鑰和秘鑰,二者是非對稱的,例如用私鑰加密的內(nèi)容需要使用公鑰來解密,使用公鑰加密的內(nèi)容需要用私鑰來解密,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;
- }
- ------------輸出結(jié)果------------------
- 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;
- }
輸出結(jié)果
- 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和密鑰規(guī)格KeySpec的相互轉(zhuǎn)化
If the key is stored on a hardware device, its specification may contain information that helps identify the key on the device
KeySpec是一個接口,用來組成加密密鑰的密鑰內(nèi)容的(透明)規(guī)范。如果密鑰存儲在硬件設(shè)備上,則其規(guī)范可以包含有助于標識該設(shè)備上的密鑰的信息
❞KeySpec具有規(guī)范性,所以一般會根據(jù)外部參數(shù)生成KeySpec,再根據(jù)KeySpec生成對應(yīng)的Key(個人理解,如有高見,請說出你的見解)。SecretKeyFactory、KeyFactory的作用就是轉(zhuǎn)換Key與KeySpec
SecretKeyFactory:用于對稱加密的密鑰和密鑰規(guī)格之間的轉(zhuǎn)換,配合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);// 設(shè)置密鑰
- DESKeySpec keySpec = new DESKeySpec(DESKey);// 設(shè)置密鑰參數(shù)
- SecretKey key = keyFactory.generateSecret(keySpec);// 得到密鑰對象
- System.out.println(key);
- }
- ------------輸出結(jié)果------------------
- com.sun.crypto.provider.DESKey@18e49
KeyFactory:用于非對稱加密的密鑰和密鑰規(guī)格之間的轉(zhuǎn)換,配合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是上面示例提供的函數(shù)
- KeyPair keyPair = generatorRsaKey();
- System.out.println(keyPair);
- //PublicKey轉(zhuǎn)KeySpec;KeySpec再轉(zhuǎn)PublicKey
- X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(keyPair.getPublic().getEncoded());
- KeyFactory keyFactory = KeyFactory.getInstance("RSA");
- PublicKey pubKey = keyFactory.generatePublic(pubKeySpec);
- System.out.println(pubKey);
- //PrivateKey轉(zhuǎn)KeySpec;KeySpec再轉(zhuǎn)PrivateKey
- PKCS8EncodedKeySpec priKeySpec = new PKCS8EncodedKeySpec(keyPair.getPrivate().getEncoded());
- PrivateKey priKey = keyFactory.generatePrivate(priKeySpec);
- System.out.println(priKey);
- }
輸出結(jié)果
- 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它們會根據(jù)明文用哈希算法計算一個固定長度的摘要(哈希值),然后把明文和摘要發(fā)送給接收者,接收者根據(jù)同樣的算法計算出摘要,對比兩個摘要是否一樣即可驗證明文的正確性,它的應(yīng)用場景是:防止篡改和校驗數(shù)據(jù)
- MD5、SHA等算法是開源的,容易被試探出來。有沒有更安全的摘要算法呢?HMAC-帶密鑰(密碼)的hash函數(shù),用一個密鑰和一個明文消息作為輸入,生成一個消息摘要。密鑰一般使用KeyGenerator創(chuàng)建,相當(dāng)于一個密碼值,其被試探出的概率小
- 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())));
- ------------輸出結(jié)果------------------
- 0���G?�w
- 0���G?�w
MAC的示例
- public static void main(String[] args) throws Exception {
- // 初始化HmacMD5摘要算法的密鑰產(chǎn)生器
- KeyGenerator generator = KeyGenerator.getInstance("HmacMD5");
- // 產(chǎn)生密鑰
- 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)));
- }
- ------------輸出結(jié)果------------------
- true
4 簽名算法工具-Signature
- 簽名算法其實也是加密算法,它加密后的數(shù)據(jù)具有唯一標識性,就像一個人的簽名能代表一個人身份。簽名一般是指用非對稱加密算法的私鑰來加密明文的過程,生成的密文可以被持有公鑰的人識別解密,只要你的公鑰是準確對應(yīng)無誤的,就能保證你解密的數(shù)據(jù)是來自持有私鑰的一方
- 如何保證公鑰是正確無誤,沒被篡改的?1:一對一給你,2:獲取公鑰后通過權(quán)威機構(gòu)認證,相關(guān)過程可以看下之前寫的一篇文章網(wǎng)絡(luò)篇:朋友面試之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());
- //加解密數(shù)據(jù)
- byte[] data = "hello world".getBytes();
- //數(shù)據(jù)簽名
- signature.update(data);
- byte[] digest = signature.sign();
- //數(shù)據(jù)解密加驗證
- signature.initVerify(keyPair.getPublic());
- signature.update(data);
- System.out.println("驗證結(jié)果:"+signature.verify(digest));
- }
- ------------輸出結(jié)果------------------
- 驗證結(jié)果:true
5 常用加密工具類-Cipher
- 用于加密/解密數(shù)據(jù)。支持各種類型的算法:對稱加密(例如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");
- // 編碼前設(shè)定編碼方式及密鑰
- cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPrivate());
- //加解密數(shù)據(jù)
- byte[] data = "hello world".getBytes();
- //數(shù)據(jù)簽名
- byte[] enData = cipher.doFinal(data);
- //數(shù)據(jù)解密
- cipher.init(Cipher.DECRYPT_MODE, keyPair.getPublic());
- byte[] newData = cipher.doFinal(enData);
- System.out.println("驗證結(jié)果:"+new String(newData));
- }
- ------------輸出結(jié)果------------------
- 驗證結(jié)果:hello world
6 Certificate-證書存儲
- CertificateFactory:用于創(chuàng)建公鑰證書(Certificate)和證書吊銷列表(CRL)
- Certificate及其子類X509Certificate
- CertPath和CertPathBuilder:用于構(gòu)建證書鏈(也稱為證書路徑)
- 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里面)
- 公鑰:是一個詳細的實體的數(shù)字關(guān)聯(lián),并有意讓所有想同這個實體發(fā)生信任關(guān)系的其他實體知道.公共鑰匙用來檢驗簽名;
- 私鑰:是一些數(shù)字,私有和公共鑰匙存在所有用公共鑰匙加密的系統(tǒng)的鑰匙對中.公共鑰匙用來加密數(shù)據(jù),私有鑰匙用來計算簽名.公鑰加密的消息只能用私鑰解密,私鑰簽名的消息只能用公鑰檢驗簽名。
示例
- public static void main(String[] args) throws Exception {
- InputStream certificateStream = null;
- //根據(jù)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生成創(chuàng)建KeyManagerFactory和TrustManagerFactory
- KeyManagerFactory和TrustManagerFactory用來創(chuàng)建KeyManager和TrustManager
- 而KeyManager和TrustManager用來初始化SSLContext
- 然后使用SSLContext,創(chuàng)建實際實現(xiàn)SSL/TLS協(xié)議的對象(SSLSocketFactory、SSLSocket或者SSLEngine)
- SSLSocket和SSLEngine可以直接在通信對象中使用
- KeyManager和TrustManager作用:
- KeyManager負責(zé)向?qū)Φ榷孙@示使用的憑證(使用的密碼標準、加密算法、證書、公鑰、簽名等)
- TrustManager負責(zé)驗證從對等端收到的憑證,驗證憑證有多種方式:其中之一是創(chuàng)建CertPath對象,并讓JDK的內(nèi)置公鑰基礎(chǔ)結(jié)構(gòu)(PKI)框架處理驗證。在內(nèi)部,CertPath實現(xiàn)可能會創(chuàng)建一個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());
- //服務(wù)端信任證書
- KeyStore trustKeyStore = KeyStore.getInstance("jks");
- FileInputStream trustKeyStoreFile = new FileInputStream("/root/trustKeyStore.jks");
- trustKeyStore.load(trustKeyStoreFile, "password".toCharArray());
- //構(gòu)建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();
- //服務(wù)端信任證書
- 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]
- 關(guān)于keyGenerator,KeyPairGenerator,SecretKeyFactory的解析[5]
- JCA 實踐記錄——SecretKeyFactory[6]
- HttpClient 雙向認證[7]
- java內(nèi)置可用加密算法文檔[8]
- key解析[9]