前言
和前端進行數據交互時或者和第三方商家對接時,需要對隱私數據進行加密。單向加密,對稱加密,非對稱加密,其對應的算法也各式各樣。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;
}