1. 程式人生 > 其它 >【Java學習筆記(一百二十三)】之Java安全 類載入器,安全管理器,數字簽名,加密

【Java學習筆記(一百二十三)】之Java安全 類載入器,安全管理器,數字簽名,加密

技術標籤:Java學習筆記# JavaEEjava安全

本文章由公號【開發小鴿】釋出!歡迎關注!!!


老規矩–妹妹鎮樓:

一. Java安全

(一) 概述

Java技術提供了三種確保安全的機制:

  1. 語言設計特性(陣列邊界檢查,型別轉換檢查)

  2. 訪問控制機制,用於控制程式碼能夠執行的操作(檔案訪問,網路訪問)

  3. 程式碼簽名,程式碼的作者能夠用標準的加密演算法來認證Java程式碼,檢查程式碼是否被修改過


(二) 類載入器

Java編譯器會為虛擬機器轉換源指令,虛擬機器程式碼儲存在.class為副檔名的類檔案中,每個類檔案都包含某個類或者介面的定義和實現程式碼。

1. 類載入過程

虛擬機器只加載程式執行時所需要的類檔案,首先讀取類檔案的內容,如果該類擁有另一個類的域或是超類,那麼這些類檔案也會被載入,載入某個類所依賴的所有類的過程稱為類的解析。接著,虛擬機器載入類中的main方法,如果main方法要用到更多的類,那麼就會載入這些類。

2. 類載入器的層次結構

每個Java程式至少有三個類載入器:引導類載入器,平臺類載入器,系統類載入器。

引導類載入器負責載入JDK內部模組中的平臺類,該載入器沒有對應的ClassLoader物件。Java9之前,Java平臺類位於rt.jar中,如今的Java平臺是模組化的,每個平臺模組都包含一個JMOD檔案,平臺類載入器會載入引導類載入器沒有載入的Java平臺中的所有類。系統類載入器會從模組路徑和類路徑中載入應用類。

類載入器有父子關係,除了引導類載入器外,每個類載入器都有一個父類載入器,每個父類載入器會首先載入,當父類載入器載入失敗時,它才會載入該給定的類。某些外掛類也是可以被載入的,使用URLClassLoader類的例項來載入外掛類,由於在URLClassLoader構造器中沒有指定父類載入器,因此pluginLoader的父親就是系統類載入器。
類的載入是由於其他的類需要它而被載入的,偶爾,我們也需要干涉和指定類載入器,如載入一個助手方法:

Class.forName(classNameString);

這個方法是從一個外掛類中呼叫的,classNameString指定的是一個包含在這個外掛中的JAR中的類,這個類通過Class.forName載入,使用的是系統類載入器,而對於它來說,外掛JAR中的類是不可見的,這種現象稱為類載入器倒置。因此,外掛類需要使用恰當的類載入器,將指定的類載入器設定為當前執行緒的上下文類載入器,每個執行緒都有一個對類載入器的引用,稱為上下文類載入器,主執行緒的上下文類載入器是系統類載入器,當新執行緒建立時,它的上下文類載入器會被設定為父執行緒的上下文類載入器。如下所示:

Thread t= Thread.currentThread();
t.setContextClassLoader(loader);

然後外掛類可以獲取這個上下文類載入器:

Thread t = Thread.currentThread();
ClassLoader loader = t.getContextClassLoader();
Class<?> cl = loader.loadClass(claaName);

3. 類載入器作為名稱空間

在同一個虛擬機器中,可以有兩個類,他們的類名和包名可以是相同的,類是由它的全名和類載入器來確定的,例如,應用伺服器會為每一個應用使用單獨的類載入器,這使得虛擬機器可以區分來自不同應用的類。

4. 位元組碼校驗

當類載入器將新載入的Java平臺類的位元組碼傳遞給虛擬機器時,這些位元組碼首先要接受校驗器的檢驗,校驗器負責檢查哪些指令無法執行的明顯有破壞性的操作,除了系統類外,所有的類都要被檢驗。如:

(1) 變數的初始化;

(2) 方法呼叫與物件引用型別之間的匹配;

(3) 對本地變數的訪問都落在執行時堆疊內;

(4) 執行時堆疊沒有溢位;


(三) 安全管理器

一旦類被載入到虛擬機器中,並且有檢驗器檢查過之後,Java平臺的第二種安全機制就會啟動,這個機制就是安全管理器。

1. 許可權檢查

安全管理器是一個負責控制具體操作是否允許執行的類,如退出虛擬機器等等,在執行Java應用程式時,預設的設定是不安裝安全管理器的,所有的操作都是默許的。我們可以設定一個策略,在遇到安全問題時通過策略中的對策來執行。但是,安全策略的完整性依賴於謹慎的編碼,標準類庫中系統服務的提供者,在試圖繼續任何敏感的操作之前,都必須與安全管理器進行協商。

2. Java平臺安全性

從Java1.2開始,Java平臺擁有了更加靈活的安全機制,它的安全策略建立了程式碼來源和訪問許可權集之間的對映關係。程式碼來源是由一個程式碼位置和一個證書集指定的,程式碼位置指定了程式碼的來源,如位於JAR包中的程式碼的程式碼位置是該檔案的URL。證書的目的是要由某一方來保證程式碼沒有被篡改過。許可權是指由安全管理器負責檢查的任何屬性,Java平臺支援許多訪問許可權類,每個類都封裝了特定許可權的詳細資訊。每個類都有一個保護域,它是一個用於封裝類的程式碼來源和許可權集合的物件,當SecurityManager類需要檢查某個許可權時,它要檢視當前位於呼叫堆疊上的所有方法的類,然後它要獲得所有類的保護域,並且詢問每個保護域,其許可權集合是否允許執行當前正在被檢查的操作。

3. 安全策略檔案

策略管理器要讀取相應的策略檔案,這些檔案包含了將程式碼來源對映為許可權的指令,下面是一個策略檔案:

grant codebase http://www.horstmann.com/classes
{
	permission java.io.FilePermission “/tmp/*”, “read, write”;
}

(四) 數字簽名

1. 訊息摘要

訊息摘要是資料塊的數字指紋,例如SHA1(安全雜湊演算法#1)可以將任何資料塊,無論其資料有多長,都壓縮為160位的序列。如果資料的1位或者幾位改變了,那麼訊息摘要也會改變。且擁有給定訊息的偽造者無法建立與原訊息具有相同摘要的假訊息。

MessageDigest類是用於建立封裝了指紋演算法的物件的工廠,它的靜態方法getInstance返回繼承了MessageDigest類的某個類的物件,獲取計算SHA指紋的物件如下所示:

MessageDigest alg = MessageDigest.getInstance(“SHA-1);

獲取到MessageDigest物件之後,可以通過反覆呼叫update方法,將資訊中的所有位元組都提供給該物件。

2. 訊息簽名

訊息摘要解決了訊息的鑑定問題,當訊息和訊息摘要是分開傳送的,接受者就可以檢查訊息是否被篡改過,但是如果訊息和訊息摘要同時被截獲了,然後對訊息進行修改,根據公開的訊息摘要演算法計算指紋,那麼接受者永遠不會知道訊息已經被篡改了。訊息簽名解決了這個問題。

公共金鑰加密技術是基於公共金鑰和私有金鑰這兩個概念的,公鑰可以公佈給世界,但是私鑰只能自己持有。幾乎不可能通過一個金鑰來推算出另一個金鑰,也就是說即使每個人都知道你的公鑰,但是不可能計算出你的私鑰。因此,當A想要傳送訊息給B時,A用自己的私鑰對訊息摘要簽名, 而B知道A的公鑰,B用A的公鑰對該簽名進行校驗,如果通過了校驗,則B知道原始訊息沒有被篡改,且該訊息是由A簽名的,因為它是私鑰的持有者。但是這也隱含了風險,如果A的私鑰被人偷走了,那麼A的身份就會其他人替代。

3. 校驗簽名

JDK配有一個keytool程式,該程式是一個命令列工具,用於生成和管理一組證書,以及管理金鑰庫,證書資料庫和私有/公有金鑰對,金鑰庫中的每一項都有一個別名,下面是Alice建立一個金鑰庫alice.certs並且用別名生成一個金鑰對:

keytool -genkeypair -keystore alice.certs -alias alice

建立完金鑰庫後,Alice可以匯出公鑰:

keytool -exportcert -keystore alice.certs -alias alice -file alice.cer

當接收者收到公鑰,並匯入金鑰庫:

keytool -importcert -keystore bob.certs -alias alice -file alice.cer

Alice就可以給接受則傳送簽過名的文件了,jarsigner工具負責對JAR檔案進行簽名和校驗,Alice只需要將文件新增到要簽名的JAR檔案中即可:

jar cvf document.jar document.txt

然後使用jarsigner工具將簽名新增到檔案中,指定要使用的金鑰庫,JAR檔案和金鑰的別名:

jarsigner -keystore alice.certs document.jar alice

當接收者收到JAR檔案後,使用jarsigner的-verify選項對檔案進行校驗:

jarsigner -verify -keystore bob.certs document.jar

4. 認證問題

我們之前解決的都是訊息的校驗問題,即訊息是否還是原來的訊息我們可以確定,但是還有傳送訊息者的身份問題的認證沒有解決。只有我們信任傳送訊息者的身份,我們才能夠認證他的訊息。其實就是一個信任問題,如果我們只信任A傳送的訊息,只要陌生人將公鑰傳送給A,A在公鑰的基礎上用私鑰簽名。我們持有A的公鑰,只需要用A的公鑰對A傳送來的訊息校驗,就可以確認A已經核實了陌生人的身份。

但是,通常情況下不會有這樣一個熟人身份,一般都是一個信任鏈條,我們可以信任鏈條中的任何一個人;或者是一個大家都信任的機構,提供認證服務。我們常常會遇到由負責擔保他人身份的一個或多個實體簽署的數字簽名,但是我們需要評估在多大程度上能夠信任這些機構。


5. 證書籤名

網際網路上的資訊傳遞是非常頻繁的,我們不可能對每一條資訊都進行校驗,那麼過於繁瑣,因此如果有一個我們信任的實體來校驗這些簽名是最好的。比如說Cindy信任ACME軟體部,這個部門負責證書授權(CA)的運作,首先我們建立一個金鑰庫acme.Certs,生成一個金鑰對並匯出公共金鑰,其中的公共金鑰被匯入到一個自簽名的證書中:

keytool -genkeypair -keystore acme.certs -alias acmeroot
keytool -exportcert -keystore acme.certs -alias acmeroot -file acmeroot.cer

如果Alice要傳送訊息給Cindy,他需要將自己的證書籤名,並提交給ACME,ACME將負責稽核Alice的身份,並且生成了簽名證書,Alice將該證書交給Cindy或者放在ACME公司的目錄上,該檔案包含了Alice的公鑰和ACME的宣告,證明該金鑰確實屬於Alice。這樣,Cindy將該證書匯入到她的金鑰庫中後,金鑰庫校驗該金鑰是由金鑰庫中已有的受信任的根金鑰簽名過的,Cindy就不需要對證書的指紋進行校驗了。一旦Cindy添加了根證書和經常給他傳送訊息的人的證書後,就不用擔心金鑰庫了。


(五) 加密

1. 概述

當資訊通過認證後,資訊本身是可見的,加密技術能夠對資訊進行加密,只能用匹配的金鑰進行解密。認證對於程式碼簽名是足夠的, 沒有必要將程式碼隱藏起來,而對於機密訊息,需要進行加密。

2. 對稱密碼

Java密碼擴充套件包含了一個Cipher類,該類是所有加密演算法的超類,呼叫getInstance方法獲得一個密碼物件:

Ciphre cipher = Cipher.getInstance(aligorithmName);

演算法通常有“DES”,或者“AES”等等,DES是資料加密標準,AES是高階加密標準,一旦獲得了一個密碼物件,就可以通過設定模式和金鑰來對它初始化。


3. 金鑰生成

為了加密,我們需要生成金鑰,每個密碼都有不同的用於金鑰的格式,我們需要確保金鑰的生成是隨機的,需要遵循以下的步驟:

(1) 為加密演算法獲取KeyGenerator;

(2) 用隨機源來初始化金鑰發生器;

(3) 呼叫generateKey方法;

如生成AES金鑰:

KeyGenerator keygen = KeyGenerator,getInstance(“AES”);
var random = new SecureRandom();
keygen.init(random);
Key key = keygen.generateKey();

如果要生成金鑰,必須用真正的隨機數,Random類中常規的隨機數發生器,是根據日期和時間來產生隨機數的,不夠隨機,因為計算機時鐘可以精確到0.1秒,每天最多儲存864000個種子,如果攻擊者知道釋出金鑰的日期,就可以生成那一天所有可能的種子。

SecureRandom類產生的隨機數更加安全,但是仍然要提供一個種子,以便在一個隨機點上開始生成數字序列。可以從硬體裝置或者隨機敲擊鍵盤獲得。

所有已知的公共金鑰演算法的速度都比對稱金鑰演算法(AES或DES)慢得多,使用公共金鑰演算法對大量資訊加密是不合適的,可以用公共金鑰演算法加密少量關鍵資料,用對稱金鑰演算法加密大量一般資料。