JDK中的Security技術
前段時間再看關于JDK算法相關的知識時,看到很多與jdk中security包下的模塊有一定關聯(lián),之前對這塊一直沒有關注,趁此機會先做個簡單的了解。
本文旨在深入探討Java的Security技術,包括其核心概念、關鍵模塊以及具體應用。通過詳細分析,希望幫助讀者更好地理解如何在Java應用程序中實現(xiàn)安全防護,提高系統(tǒng)的可靠性和穩(wěn)定性。
主要功能包括授權、訪問控制、數(shù)據(jù)加密、身份驗證等。
核心模塊
- 授權與訪問控制:Java Security提供了一套完整的授權和訪問控制機制,包括基于角色的訪問控制、基于聲明的訪問控制、自定義訪問控制等。這些機制可以有效地保護系統(tǒng)資源,防止未經授權的訪問。
- 數(shù)據(jù)加密與解密:Java Security提供了豐富的加密算法,如AES、RSA等,用于對敏感數(shù)據(jù)進行加密和解密。通過使用這些算法,我們可以保護數(shù)據(jù)的安全性,防止數(shù)據(jù)泄露。
- 身份驗證與授權:身份驗證是確認用戶身份的過程,授權則是確定用戶是否有權執(zhí)行某項任務。Java Security提供了各種身份驗證和授權機制,如用戶名密碼驗證、數(shù)字證書驗證等,以滿足不同場景的需求。
- 安全套接字層(SSL)與傳輸層安全(TLS):SSL和TLS是用于在通信過程中保證數(shù)據(jù)傳輸安全的協(xié)議。Java Security提供了SSL和TLS的實現(xiàn),可用于構建安全的網(wǎng)絡通信。
授權與訪問控制
Java提供了對敏感信息的訪問控制功能,比如本地文件,類方法等的訪問約束,以此來組建安全的代碼框架
先看一個文件寫入的示例:
(1) 定義policy
grant {
permission com.sucl.blog.security.jdk.control.UserResourcePermission "read";
}
(2) 編寫測試類
public class FileAccessController {
static {
// -Djava.security.manager
System.setSecurityManager( new SecurityManager() );
}
public static void main(String[] args) throws IOException {
SecurityManager securityManager = System.getSecurityManager();
if( securityManager == null ){
System.out.println("執(zhí)行文件寫入1");
writeHello("hello");
}else{
System.out.println("執(zhí)行文件寫入2");
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
writeHello("world");
} catch (IOException e) {
throw new RuntimeException(e);
}
return null;
}
}, AccessController.getContext());
}
}
private static void writeHello(String text) throws IOException {
FileOutputStream fos = new FileOutputStream("e:\\home\\file.txt", true);
PrintWriter pw = new PrintWriter(fos);
pw.println(text);
pw.close();
fos.close();
}
}
(3) 測試 cd 到target/classes
java -D"java.security.policy=path\file.policy" com.sucl.blog.security.jdk.control.file.FileAccessController
上面的例子中如果沒有定義policy時,如果調用文件寫入方法,此時會拋出異常
java.security.AccessControlException: access denied ("java.io.FilePermission" "e:\home\file.txt" "write")
這樣通過自定義polic編寫權限策略,則可以實現(xiàn)對資源訪問控制的效果。
如果看spring的源代碼,你會發(fā)現(xiàn)有很多類似AccessController.doPrivileged這樣的寫法,不知道當時有沒考慮過其目的,雖然在調試時發(fā)現(xiàn)其根本沒有執(zhí)行
數(shù)據(jù)加密與解密
Java Security API提供了可互操作的算法和安全服務的實現(xiàn)。服務以provider的形式實現(xiàn),可以以插件的形式植入應用程序中。 程序員可以透明地使用這些服務,如此使得程序員可以集中精力在如何把安全組件集成到自己的應用程序中,而不是去實現(xiàn)這些安全功能。 此外,除了Java提供的安全服務外,用戶可以編寫自定義的security provider,按需擴展Java的security平臺。
1.算法
- Message digest algorithms 【信息摘要算法, 如:MD5】
- Digital signature algorithms 【數(shù)字簽名算法,DSA】
- Symmetric bulk encryption 【對稱塊加密, 如:DES】
- Symmetric stream encryption 【對稱流加密, 如:RC4】
- Asymmetric encryption 【非對稱加密, 如:RSA】
- Password-based encryption (PBE) 【密碼加密】
- Elliptic Curve Cryptography (ECC) 【橢圓曲線加密】
- Key agreement algorithms 【key協(xié)議算法】
- Key generators 【key生成器】
- Message Authentication Codes (MACs) 【消息認證碼】
- (Pseudo-)random number generators 【偽隨機數(shù)生成器】
2.示例
Java內置的Provider提供了許多通用的密碼算法,比如:RSA, DSA, ECDSA等簽名算法、DES, AES, ARCFOUR等加密算法、MD5, SHA-1, SHA-256等 信息摘要算法、還有Diffie-Hellman和ECDH這樣的密鑰協(xié)商算法。下面簡單的實現(xiàn)了MD5、DES、RSA幾個我們常用的算法
(1) MD5
public class MD5 {
private static final Charset DEFAULT_CHARSET = Charset.defaultCharset();
static MessageDigest messageDigest;
static {
try {
messageDigest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
}
(2) DES
public class DES {
private static final String DES = "DES";
private static final int DEFAULT_ENCRYPT_CHUNK = 245;
private static final int DEFAULT_DECRYPT_CHUNK = 256;
private String salt;
private SecretKey secretKey;
public DES(String salt){
this.salt = salt;
initKey();
}
public void initKey(){
try {
KeyGenerator keyGenerator = KeyGenerator.getInstance(DES);
SecureRandom secureRandom = new SecureRandom(salt.getBytes());
keyGenerator.init(secureRandom);
this.secretKey = keyGenerator.generateKey();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
public String encrypt(String text){
try {
Cipher cipher = Cipher.getInstance(DES);
cipher.init(Cipher.ENCRYPT_MODE, this.secretKey);
byte[] codes = text.getBytes();
int length = codes.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int check = 0;
byte[] cache ;
while (check < length){
int chunk = Math.min(DEFAULT_ENCRYPT_CHUNK, length-check);
cache = cipher.doFinal(codes, check, chunk);
check += chunk;
out.write(cache, 0, cache.length);
}
return Base64.getEncoder().encodeToString(out.toByteArray());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public String decrypt(String text){
try {
Cipher cipher = Cipher.getInstance(DES);
cipher.init(Cipher.DECRYPT_MODE, this.secretKey);
byte[] base64Text = Base64.getDecoder().decode(text);
int length = base64Text.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int check = 0;
byte[] cache ;
while (check < length){
int chunk = Math.min(DEFAULT_DECRYPT_CHUNK, length-check);
cache = cipher.doFinal(base64Text, check, chunk);
check += chunk;
out.write(cache, 0, cache.length);
}
return new String(out.toByteArray());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
(3) RSA
@Slf4j
public class RSA {
private static final String KEY_ALGORITHM = "RSA";
private static final int KEY_PAIR_SIZE = 2048;
/**
* 不大于245
*/
private static final int DEFAULT_ENCRYPT_CHUNK = 245;
/**
* 不大于256,改成其他值時:Decryption error
*/
private static final int DEFAULT_DECRYPT_CHUNK = 256;
/**
* NONEwithRSA
* MD5withRSA
* SHA256withRSA
*/
private static final String SIGN_ALGORITHM = "SHA256withRSA";
private static final String CIPHER_ALGORITHM = "RSA/ECB/PKCS1Padding";
private KeyPair keyPair;
public RSA() {
this.keyPair = getKeyPair();
}
/**
* 生成公鑰、私鑰
* @param key
* @return
*/
private KeyPair getKeyPair(){
try {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
keyPairGen.initialize(KEY_PAIR_SIZE);
return keyPairGen.generateKeyPair();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 獲取公鑰
* @param base64PublicKey base64編碼的值 基于KeyPair公鑰
* @return
*/
private PublicKey getPublicKey(String base64PublicKey){
try {
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
KeySpec keySpec = new X509EncodedKeySpec(hexToBytes(base64PublicKey)); //
PublicKey publicKey = keyFactory.generatePublic(keySpec);
return publicKey;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 獲取私鑰
* @param base64PrivateKey base64編碼的值 基于KeyPair私鑰
* @return
*/
private PrivateKey getPrivateKey(String base64PrivateKey){
try {
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
// Security.addProvider(BouncyCastleProviderSingleton.getInstance());
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(hexToBytes(base64PrivateKey));
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
return privateKey;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public String getPublicKey(){
return bytesToHex(keyPair.getPublic().getEncoded());
}
public String getPrivateKey(){
return bytesToHex(keyPair.getPrivate().getEncoded());
}
/**
* 通過私鑰簽名
* @param data
* @param privateKeyStr
* @return
*/
public String sign(byte[] data){
PrivateKey privateKey = getPrivateKey(getPrivateKey());
try {
Signature sig = Signature.getInstance(SIGN_ALGORITHM);
sig.initSign(privateKey);
sig.update(data);
return bytesToHex(sig.sign());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 校驗簽名
* @param data
* @param sign
* @param publicKeyStr
* @return
*/
public boolean verify(byte[] data, String sign){
PublicKey publicKey = getPublicKey(getPublicKey());
try {
Signature sig = Signature.getInstance(SIGN_ALGORITHM);
sig.initVerify(publicKey);
sig.update(data);
return sig.verify(hexToBytes(sign));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 通過公鑰加密
* @param text
* @param publicKey
* @return
*/
public String encrypt(String text){
byte[] codes = text.getBytes();
PublicKey publicKey = getPublicKey(getPublicKey());
try {
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
int length = codes.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int check = 0;
byte[] cache ;
while (check < length){
int chunk = Math.min(DEFAULT_ENCRYPT_CHUNK, length-check);
cache = cipher.doFinal(codes, check, chunk);
check += chunk;
out.write(cache, 0, cache.length);
}
return bytesToHex(out.toByteArray());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 通過私鑰解密
* @param text 加密內容
* @param privateKeyStr
* @return
*/
public String decrypt(String text){
PrivateKey privateKey = getPrivateKey(getPrivateKey());
try {
byte[] base64Text = hexToBytes(text);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
int length = base64Text.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int check = 0;
byte[] cache ;
while (check < length){
int chunk = Math.min(DEFAULT_DECRYPT_CHUNK, length-check);
cache = cipher.doFinal(base64Text, check, chunk);
check += chunk;
out.write(cache, 0, cache.length);
}
return new String(out.toByteArray());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public byte[] hexToBytes(String str){
return Base64.getDecoder().decode(str);
}
public String bytesToHex(byte[] bytes){
return Base64.getEncoder().encodeToString(bytes);
}
}
身份驗證與授權
客戶端向服務器發(fā)送身份驗證請求。 服務器隨機生成一個挑戰(zhàn)參數(shù),發(fā)送給客戶端。 客戶端使用挑戰(zhàn)參數(shù)和密碼計算出響應參數(shù),并將響應參數(shù)發(fā)送給服務器。 服務器使用挑戰(zhàn)參數(shù)、用戶名和密碼計算出期望的響應參數(shù),并將其與客戶端發(fā)送的響應參數(shù)進行比較,以驗證客戶端的身份。
身份認證:
import javax.security.auth.login.LoginContext;
public class App {
public static void main(String[] args) {
URL url = ClassLoader.getSystemClassLoader().getResource("jaas.config");
System.setProperty("java.security.auth.login.config", url.getPath());
Subject subject = new Subject();
subject.getPrincipals().add(new User("admin"));
LoginContext loginContext = new LoginContext("app", subject);
loginContext.login();
}
}
AppLoginModule:
@Slf4j
public class AppLoginModule implements LoginModule {
@Override
public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) {
log.info("initialize");
// 基于配置構建認證環(huán)境
}
@Override
public boolean login() throws LoginException {
log.info("login");
return true;
}
@Override
public boolean commit() throws LoginException {
log.info("commit");
return true;
}
@Override
public boolean abort() throws LoginException {
log.info("abort");
return false;
}
@Override
public boolean logout() throws LoginException {
log.info("logout");
return false;
}
}
jaas.config,放到classpath即可:
app {
com.sucl.blog.security.jdk.auth.AppLoginModule required
useTicketCache=true
doNotPrompt=true;
};
相關這塊知識在網(wǎng)上找了一圈,發(fā)現(xiàn)很少有人講到,在編寫示例準備讓文心一言給出點提示,發(fā)現(xiàn)它只會傻傻的坑我,由于時間原因這塊內容也沒有過多的深入, 通過關鍵接口的實現(xiàn),可以看到,在很多成熟的項目中都有被用到,可能jdk本身的模塊更多是應用在構建基礎系統(tǒng)架構中吧。
常用工具類
java.security 包是 Java 安全框架的核心部分,上面說到它提供了各種類和接口來支持加密、解密、簽名、驗證等安全操作。下面簡單羅列了常用的工具類及其使用場景。
(1) KeyPairGenerator 和 KeyPair:
- KeyPairGenerator 是用于生成公鑰和私鑰對的類。它提供了生成 RSA、DSA 等算法的密鑰對的接口。
- KeyPair 是由公鑰和私鑰組成的鍵對類。它包含公鑰和私鑰的信息,并提供了操作密鑰的方法。
- 使用場景:用于實現(xiàn)基于公鑰加密和數(shù)字簽名的安全通信,保證數(shù)據(jù)傳輸?shù)陌踩院蜕矸蒡炞C的準確性。
(2) Certificate 和 CertificateFactory:
- Certificate 是表示證書的類。它提供了證書的基本信息,如頒發(fā)者、持有者、有效期等。
- CertificateFactory 是用于處理證書的類。它提供了解析證書的接口,可以將證書從各種格式轉換為 Certificate 對象。
- 使用場景:用于驗證遠程身份,確保數(shù)據(jù)傳輸?shù)陌踩?。常用?HTTPS 等安全協(xié)議中驗證服務器證書。
(3) AlgorithmParameters 和 AlgorithmParameterGenerator:
- AlgorithmParameters 是用于處理加密算法參數(shù)的類。它提供了獲取和設置加密算法參數(shù)的方法。
- AlgorithmParameterGenerator 是用于生成加密算法參數(shù)的類。它提供了生成特定加密算法參數(shù)的接口。
- 使用場景:用于增強加密算法的強度,提供更多的安全選項。例如,可以用于生成 RSA 加密算法中的密鑰長度和填充方式。
(4) SecureRandom:
- SecureRandom 是用于生成隨機數(shù)的類。它提供了加密強度的隨機數(shù)生成器,生成的隨機數(shù)更加安全可靠。
- 使用場景:用于生成隨機數(shù)驗證碼、隨機密鑰等,提高密碼安全性。還可以用于初始化加密算法中的隨機數(shù)生成器。
(5) KeyStore:
- KeyStore 是用于管理密鑰和證書存儲的類。它提供了存儲和管理私鑰、公鑰證書等安全憑據(jù)的方法。
- 使用場景:用于管理應用程序的安全憑據(jù),如私鑰、公鑰證書等??梢杂糜诖鎯凸芾碛脩舻拿荑€對,保證數(shù)據(jù)的安全性。
除了上述模塊,java.security 包還包含其他與安全相關的類和接口,如權限、策略等。這些模塊提供了更細粒度的安全控制和配置選項,適用于各種安全場景。 要了解全面的安全功能和用法,請參考 Java 官方文檔或相關資源。
總結
Java Security技術提供了全面的安全防護機制,包括授權、訪問控制、數(shù)據(jù)加密、身份驗證等。通過深入了解和掌握這些技術, 我們可以構建更加安全、可靠的Java應用程序。本文通過詳細介紹Java Security的核心模塊和代碼演示,旨在幫助讀者更好地理解和應用這些技術。 未來,隨著技術的不斷發(fā)展,Java Security將會不斷完善和改進,為我們的應用程序提供更加安全、可靠的保障。