Java安全——提供者相關的體系架構

尊淵發表於2016-08-01

標籤(空格分隔): Java 安全


[toc]


安全提供者

Java的安全軟體特性,是通過安全軟體包的概念透出的。換句話說,安全領域常見的鑑別、加密、簽名等概念,在Java中的支援是通過安全軟體包來實現的。Java對於安全軟體包的定義其實是一組抽象介面。Sun作為Java的作者,提供了一組實現。而安全軟體包是由安全提供者、演算法和引擎組成的。引擎可以理解為一組操作,演算法定義了操作如何執行,而安全提供者則負責實現這兩個抽象概念。

比如說,訊息摘要是一個引擎,它是程式設計師能執行的一個操作。訊息摘要的思想與如何計算訊息摘要沒有關係,所有的訊息摘要具備同樣的特性,因此抽象出來的介面就是引擎。而實現訊息摘要可以有MD5和SHA等演算法,演算法由具體類實現。而安全提供者就是二者的橋樑,用來管理引擎和演算法。安全提供者的目的就是提供一個簡單的機制,從而可以方便的改變或替換演算法及其實現。因此,通過安全提供者,程式設計師只需要使用引擎的介面,而不需要關係具體哪個類實現了演算法,演算法由哪個安全提供者提供。

體系結構

Java安全軟體包的體系結構可以總結為四個部分:

引擎

JVM提供引擎類,是Java核心API的一部分。

演算法

針對每一種引擎,都會有一組演算法實現。Java提供了一組預設的演算法實現(由Sun提供),第三方的機構可以提供其他實現。

提供者

演算法類是由提供者來管理的,提供者知道如何將演算法與實現的具體類對應起來。

安全類

安全類儲存提供者列表,可以通過安全類檢視有哪些提供者,以及它們提供的演算法支援有哪些。

安全提供者體系的一個流程如下:

業務類->引擎: 呼叫某個介面
引擎->安全類: 詢問
安全類->提供者: 找到提供者
提供者->演算法: 找到對應演算法
演算法->業務類:返回計算結果

以MessageDigest的getInstance()方法為例,發現其實是呼叫Security.getImpl()方法實現的,而內部又是通過

GetInstance.getInstance
                (type, getSpiClass(type), algorithm, params, provider).toArray();

這樣的語句來做的。而這個GetInstance會返回一個Instance物件,其類宣告如下:

public static final class Instance {
        public final Provider provider;
        public final Object impl;

        private Instance(Provider arg0, Object arg1) {
            this.provider = arg0;
            this.impl = arg1;
        }

        public Object[] toArray() {
            return new Object[] {this.impl, this.provider};
        }
    }

可見就是一個Provider和Object(具體演算法)的封裝。

提供者選擇

JVM在啟動時,會去$JREHOME/lib/security/java.security中註冊提供者。以我個人電腦中的檔案為例:

#
# List of providers and their preference orders (see above):
#
security.provider.1=sun.security.provider.Sun
security.provider.2=sun.security.rsa.SunRsaSign
security.provider.3=sun.security.ec.SunEC
security.provider.4=com.sun.net.ssl.internal.ssl.Provider
security.provider.5=com.sun.crypto.provider.SunJCE
security.provider.6=sun.security.jgss.SunProvider
security.provider.7=com.sun.security.sasl.Provider
security.provider.8=org.jcp.xml.dsig.internal.dom.XMLDSigRI
security.provider.9=sun.security.smartcardio.SunPCSC
security.provider.10=apple.security.AppleProvider

JVM啟動時會將這些provider註冊進去,其實就是Security
初始化時會讀這個檔案。開發自定義的安全提供者,需要將類放到系統類路徑下。其實看看Provider類的程式碼,就知道Provider本質上是一個Properties檔案,裡面的kv儲存著引擎名和具體演算法類的實現。

可以通過下面的程式示例檢視具體的提供者引擎和演算法:

package com.taobao.cd.security;

import java.security.Provider;
import java.security.Security;
import java.util.Enumeration;

public class ProviderTest {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Provider[] providers = Security.getProviders();
        for (int i = 0; i < providers.length; i++) {
            System.out.println("" + (i + 1) + ":" + providers[i]);
            for (Enumeration e = providers[i].keys(); e.hasMoreElements();) {
                System.out.println("	" + e.nextElement());
            }
        }
    }

}

其輸出如下,(擷取部分)

1:SUN version 1.8
    Alg.Alias.Signature.SHA1/DSA
    Alg.Alias.Signature.1.2.840.10040.4.3
    Alg.Alias.Signature.DSS
    SecureRandom.SHA1PRNG ImplementedIn
    KeyStore.JKS
    Alg.Alias.MessageDigest.SHA-1
    MessageDigest.SHA
    ...

引擎類的結構設計

值得一提的是引擎類的結構設計。如上所說,引擎類除了給業務開發人員提供介面,還有一個任務就是要為第三方提供者使用。引擎為提供者提供了一個介面——SPI(security provider interface)。

還是以MessageDigest為例(這是訊息摘要引擎)。MessageDigest繼承了MessageDigestSpi。MessageDigestSpi抽象類定義了訊息摘要引擎要做的事情。MessageDigest內部持有一個Delegate委託類,MessageDigest的核心方法就是getInstance()獲取類的例項,實現見下:

public static MessageDigest getInstance(String algorithm, String provider)
        throws NoSuchAlgorithmException, NoSuchProviderException
    {
        if (provider == null || provider.length() == 0)
            throw new IllegalArgumentException("missing provider");
        Object[] objs = Security.getImpl(algorithm, "MessageDigest", provider);
        if (objs[0] instanceof MessageDigest) {
            MessageDigest md = (MessageDigest)objs[0];
            md.provider = (Provider)objs[1];
            return md;
        } else {
            MessageDigest delegate =
                new Delegate((MessageDigestSpi)objs[0], algorithm);
            delegate.provider = (Provider)objs[1];
            return delegate;
        }
    }

通過Security.getImpl()反射獲取到對應的provider和演算法實現類,並通過型別判斷和else委託邏輯保證返回一個MessageDigest例項。

再具體一點,以Sun security提供的MD5演算法實現為例。雖然MD5並沒有直接實現MessageDigestSpi,但MD5的父類DigestBase繼承了MessageDigestSpi。所以實際上還是滿足這個架構的設計。DigestBase做了訊息摘要通用的實現,留了三個abstract介面:

 abstract void implCompress(byte[] arg0, int arg1);

    abstract void implDigest(byte[] arg0, int arg1);

    abstract void implReset();

這幾個程式碼在MD5中具體實現:(擷取部分)

void implDigest(byte[] arg0, int arg1) {
        long arg2 = this.bytesProcessed << 3;
        int arg4 = (int) this.bytesProcessed & 63;
        int arg5 = arg4 < 56 ? 56 - arg4 : 120 - arg4;
        this.engineUpdate(padding, 0, arg5);
        ByteArrayAccess.i2bLittle4((int) arg2, this.buffer, 56);
        ByteArrayAccess.i2bLittle4((int) (arg2 >>> 32), this.buffer, 60);
        this.implCompress(this.buffer, 0);
        ByteArrayAccess.i2bLittle(this.state, 0, arg0, arg1, 16);
    }

最後補充一個類圖說明下類的結構:

%5bMessageDigestSpi%7bbg%3awheat%7d%5d%5


相關文章