Java Security:Java加密框架(JCA)簡要說明


      加密服務總是關聯到一個特定的算法或類型,它既提供了密碼操作(如Digital Signature或MessageDigest),生成或供應所需的加密材料(Key或Parameters)加密操作,也會以一個安全的方式生成數據對象(KeyStore或Certificate),封裝(壓縮)密鑰(可以用於加密操作)。

    Java Security API中,一個engine class就是定義了一種加密服務,不同的engine class提供不同的服務。下面就來看看有哪些engine class:

1)MessageDigest:對消息進行hash算法生成消息摘要(digest)。

2)Signature:對數據進行簽名、驗證數字簽名。

3)KeyPairGenerator:根據指定的算法生成配對的公鑰、私鑰。

4)KeyFactory:根據Key說明(KeySpec)生成公鑰或者私鑰。

5)CertificateFactory:創建公鑰證書和證書吊銷列表(CRLs)。

6)KeyStore:keystore是一個keys的數據庫。Keystore中的私鑰會有一個相關聯的證書鏈,證書用於鑒定對應的公鑰。一個keystore也包含其它的信任的實體。

7)AlgorithmParameters:管理算法參數。KeyPairGenerator就是使用算法參數,進行算法相關的運算,生成KeyPair的。生成Signature時也會用到。

8)AlgorithmParametersGenerator:用於生成AlgorithmParameters。

9)SecureRandom:用於生成隨機數或者偽隨機數。

10)CertPathBuilder:用於構建證書鏈。

11)CertPathValidator:用於校驗證書鏈。

12)CertStore:存儲、獲取證書鏈、CRLs到(從)CertStore中。

 

從上面這些engine class中,可以看出JCA(Java加密框架)中主要就是提供了4種服務:Digest、Key、Cert、Signature、Alogorithm。

1) 對消息內容使用某種hash算法就可以生成Digest。

2) 利用KeyFactory、KeyPairGenerator就可以生成公鑰、私鑰。

3) 證書中心使用公鑰就可生成Cert。

4) 可以使用私鑰和Digest就可以消息進行簽名Signature。

5) 不論是Digest、Key、Cert、Signature,都要使用到算法Algorithm。

 

JCA Core API

 

1)engine class的提供商Provider

 

從JCA的設計上來說,這些engine的實現都離不開Provider。

這個類繼承了Properties,提供了JCA中的engine class。每個engine class都有getInstance()方法,它們都是從provider中獲取相關實例的。所以說Provider是JCA engine class的提供商。

 

 

2)管理Provider的工具:Security

 

其實就是一個存放Provider的集合。如果你自定義了一個Provider,可以使用Java Security屬性文件配置provider,也可以直接使用Security采用編程的方式來添加Provider。然后就可以使用自定義的engine class了。

Java Security 屬性文件在Java Security Policy中已有提過。在安裝目錄下:

 

 

下面是一個自定義的Provider:

/**
 * @author fs1194361820@163.com
 */
public class XYZProvider extends Provider{
    public XYZProvider(){
        super("XYZ", 1.0, "XYZ Security Provider v1.0");
        put("MessageDigest.XYZ", XYZMessageDigest.class.getName());
    }
}

 

已經默認配置了下列Provider:

 

配置為:security.provider.11=com.fjn.security.XYZProvider 即可。

編碼方式就更加簡單了:Security.addProvider(new XYZProvider());

  

3)消息摘要服務:MessageDigest

    消息摘要服務其實就是使用hash算法將一段消息(可以是字符串、文件內容、html等)進行計算生成的一個byte[]。

常用加密算法MD5、SHA、SHA-1其實都是hash算法。

 

下面就給一個簡單的MD5算法工具:

package com.fjn.util;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
 * 
 * @author fs1194361820@163.com
 *
 */
public class MD5 {
    private static MessageDigest md5MsgDigest;
    
    static{
        try {
            md5MsgDigest=MessageDigest.getInstance("md5");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }
    
    // 轉字符串
    public static String byte2hex(byte[] b){ 
        String hs = "";
        String stmp = "";
        for (int n = 0; n < b.length; n++) {
            stmp = Integer.toHexString(b[n] & 0xFF);
            if (stmp.length() == 1)
                hs = hs + "0" + stmp;
            else
                hs = hs + stmp;
        }
        return hs.toUpperCase();
    }
    
    public static String getMD5(String srcMsg){
        if(srcMsg == null){
            throw new IllegalArgumentException("srcMsg is null.");
        }
        byte[] md5Bytes=md5MsgDigest.digest(srcMsg.getBytes());
        return byte2hex(md5Bytes);
    }
    
    public static void main(String[] args) {
        System.out.println(MD5.getMD5("hello"));
        System.out.println(MD5.getMD5("world"));
    }
}
View Code

 

Md5算法我並沒有去實現,因為在JDK中已經內置了md5算法。上面的代碼就是使用消息摘要服務,並使用md5算法,生成相應的摘要。 

下面來一個自定義的MessageDigest:

 

package com.fjn.security.messageDigest;

import java.security.MessageDigest;
/**
 * @author fs1194361820@163.com
 */
public class XYZMessageDigest extends MessageDigest{
    private int hash;
    private int store;
    private int count;
    
    public XYZMessageDigest(){
        super("XYZ");
        engineReset();
    }

    /**
     * 算法執行過程,每次執行{@link MessageDigest#update(byte)}時,都會調用這個方法,
     * 也就是使用這個算法對在已經計算的數據的基礎上再次計算,計算出最新的結果
     */
    @Override
    protected void engineUpdate(byte b) {
        switch (count) {
        case 0:
            store = (b << 24) & 0xff000000;
            break;
        case 1:
            store |= (b << 16) & 0x00ff0000;
            break;
        case 2:
            store |= (b << 8) & 0x0000ff00;
            break;
        case 3:
            store |= (b << 0) & 0x000000ff;
            break;
        }
        count++;
        if(count==4){
            hash = hash ^ store;
            count = 0;
            store = 0;
        }
    }

    @Override
    protected void engineUpdate(byte[] b, int offset, int length) {
        for (int i = 0; i < length; i++){
            engineUpdate(b[i + offset]);
        }
    }

    /**
     * 每次執行{@link MessageDigest#digest()}時,都會獲取之前計算好的結果。
     * 同時也會將數據置為初始狀態。
     */
    @Override
    protected byte[] engineDigest() {
        while (count != 0){
            engineUpdate((byte) 0);
        }
        byte b[] = new byte[4];
        b[0] = (byte) (hash >>> 24);
        b[1] = (byte) (hash >>> 16);
        b[2] = (byte) (hash >>>  8);
        b[3] = (byte) (hash >>>  0);
        engineReset();
        return b;
    }

    /**
     * 數據轉為初始狀態
     */
    @Override
    protected void engineReset() {
        hash = 0;
        store = 0;
        count = 0;        
    }

}
View Code

這個自定義的MessageDigest中備注的內容,其實就是MessageDigest的執行過程,所有的MessageDigest都要遵從這個過程的。 

 

測試用例:

 

package com.fjn.security.messageDigest;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.MessageDigest;
import java.security.Security;

public class XYZMessageDigestTest {
    private static String filename="MessageDigestTest.txt";
    static{
        Security.addProvider(new XYZProvider());
    }
    
    public static void main(String[] args) throws Exception {
        XYZMessageDigestTest test=new XYZMessageDigestTest();
        test.writeMessage();
        test.readMessage();
    }
    
    public void writeMessage() throws Exception {
        File file=new File(filename);
        file.deleteOnExit();
        file.createNewFile();
        FileOutputStream fos = new FileOutputStream(file);
        MessageDigest md = MessageDigest.getInstance("XYZ");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        String data = "This have I thought good to deliver thee, "+
            "that thou mightst not lose the dues of rejoicing " +
            "by being ignorant of what greatness is promised thee.";
        byte buf[] = data.getBytes();
        md.update(buf);
        oos.writeObject(data);  // original message
        oos.writeObject(md.digest()); // digest
        
        oos.close();
    }
    
    public void readMessage() throws Exception{
        File file=new File(filename);
        FileInputStream fis=new FileInputStream(file);
        ObjectInputStream ois=new ObjectInputStream(fis);
        
        
        Object o = ois.readObject(); // String data: original message
        if (!(o instanceof String)) {
            System.out.println("Unexpected data in file");
            System.exit(-1);
        }
        String data = (String) o;
        System.out.println("Got message : " + data);
        o = ois.readObject();   // byte[] : digest
        if (!(o instanceof byte[])) {
            System.out.println("Unexpected data in file");
            System.exit(-1);
        }
        byte origDigest[] = (byte []) o;
        MessageDigest md = MessageDigest.getInstance("XYZ");
        md.update(data.getBytes());
        if (MessageDigest.isEqual(md.digest(), origDigest))
            System.out.println("Message is valid");
        else 
            System.out.println("Message was corrupted");
        
        
        MessageDigest md2 = MessageDigest.getInstance("SHA");
        md2.update(data.getBytes());
        if (MessageDigest.isEqual(md2.digest(), origDigest))
            System.out.println("Message is valid");
        else 
            System.out.println("Message was corrupted");
        
        ois.close();
    }
}
View Code

在這個用例中,writeMessage()將一段字符串保存后並將生成的digest也保存。 

readMessage()將消息讀取后,使用MessageDigest.isEqual()方法進行比較,這樣可以知道文件是否被人改動過。

 

 

而實際上利用私鑰更新簽名信息時,就是使用MessageDigest#update()方法的。

4)Key 相關的服務

Key包括公鑰(PublicKey)、私鑰(PrivateKey)兩種。

 

4.1 KeyPairGenerator

這個服務用於生成PublicKey和PrivateKey。

  

獲取實例后,只需要根據上面4種initialize方法進行初始化后,就可以生成KeyPair了。

@Test
    public void generateKeyPair() throws Exception {
        // 算法名稱有規定的值,不能亂寫的
        KeyPairGenerator dsaKeyPairGenerator=KeyPairGenerator.getInstance("dsa");
        SecureRandom random=SecureRandom.getInstance("SHA1PRNG","SUN");
        random.setSeed(new byte[]{1,2,3,4});
        /**
         * jdk8: key必須在[512,1024]之間,並且是64的倍數。有的JDK版本要求是8的倍數,這要根據實際情況和需求設定
         */
        dsaKeyPairGenerator.initialize(576, random);
        
        KeyPair keyPair=dsaKeyPairGenerator.generateKeyPair();
        DSAPublicKey puk=(DSAPublicKey)keyPair.getPublic();
        DSAPrivateKey pik=(DSAPrivateKey)keyPair.getPrivate();
        
        System.out.println(puk.getFormat());
        System.out.println(pik.getFormat());
        
        System.out.println(puk);
        System.out.println(pik);
    }
View Code

第一次調用generateKeyPair()都會生成不同的KeyPair。KeyPairGenerator 每次生成的都是一個KeyPair。

 

4.2 KeyFactory

KeyFactory用於在Key與KeySpec之間轉換,即可以根據key獲取到KeySpec,也可以根據KeySpec獲取Key。

 

package com.fjn.security.key;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.DSAPublicKeySpec;

import org.junit.Test;

public class KeyFactoryTest {
    private static final String DSA="DSA";
    private static final String keyspecFile="keyspec.text";
    
    @Test
    public void genenatePublicKey() throws Exception{
        writeKeySpec();
        readKeySpec();
    }
    
    private void writeKeySpec() throws Exception {
        File file=new File(keyspecFile);
        file.deleteOnExit();
        file.createNewFile();
        
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance(DSA);
        keyGen.initialize(512, new SecureRandom());
        KeyPair keyPair=keyGen.generateKeyPair();
        
        KeyFactory factory=KeyFactory.getInstance(DSA);
        DSAPublicKeySpec keySpec=factory.getKeySpec(keyPair.getPublic(), DSAPublicKeySpec.class);
        FileOutputStream fos = new FileOutputStream(file);
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(keySpec.getY());
        oos.writeObject(keySpec.getP());
        oos.writeObject(keySpec.getQ());
        oos.writeObject(keySpec.getG());
        oos.flush();
        oos.close();
    }
    
    private void readKeySpec() throws Exception {
        KeyFactory factory=KeyFactory.getInstance(DSA);
        FileInputStream fis = new FileInputStream(keyspecFile);
        ObjectInputStream ois = new ObjectInputStream(fis);
        DSAPublicKeySpec keySpec = new DSAPublicKeySpec(
                    (BigInteger) ois.readObject(),
                    (BigInteger) ois.readObject(),
                    (BigInteger) ois.readObject(),
                    (BigInteger) ois.readObject());
        ois.close();
        PublicKey puk=factory.generatePublic(keySpec);
        System.out.println("Got private key:\n"+puk);
    }
}
View Code

 

 

5)Cert相關的服務

從上一篇的例子中知道,用戶使用的Public Key有可能被不法分子偷偷地竄改,這樣用戶就得不到應有的服務,也會受到不法分子的危害。如何保證public key不被竄改或者替換呢?認證服務就出現了。

 

5.1 CertificateFactory

用於生成Certificate或者CRL的。

 

 

FileInputStream fis = new FileInputStream(filename);
 BufferedInputStream bis = new BufferedInputStream(fis);

 CertificateFactory cf = CertificateFactory.getInstance("X.509");

 while (bis.available() > 0) {
    Certificate cert = cf.generateCertificate(bis);
    System.out.println(cert.toString());
 }

 

什么是CRL ? 

一個證書頒發機構需要證書吊銷其頒發的證書——也許是虛假的,或者證書的用戶已經使用證書從事非法行為。在這樣的情況下,證書的有效期不足保護;證書必須立即失效。

 

下面的這個例子就是在驗證完證書的有效性后,判斷這個證書是否是一個吊銷的證書。

public Certificate importCertificate(byte data[])
                                    throws CertificateException {
    X509Certificate c = null;
    try {
        CertificateFactory cf = CertificateFactory.getInstance("X509");
        ByteArrayInputStream bais = new ByteArrayInputStream(data);
        c = (X509Certificate) cf.generateCertificate(bais);
        Principal p = c.getIssuerDN();
        PublicKey pk = getPublicKey(p);
        c.verify(pk);
        InputStream crlFile = lookupCRLFile(p);
        cf = CertificateFactory.getInstance("X509CRL");
        X509CRL crl = (X509CRL) cf.generateCRL(crlFile);
        if (crl.isRevoked(c))
            throw new CertificateException("Certificate revoked");
    } catch (NoSuchAlgorithmException nsae) {
        throw new CertificateException("Can't verify certificate");
    } catch (NoSuchProviderException nspe) {
        throw new CertificateException("Can't verify certificate");
    } catch (SignatureException se) {
        throw new CertificateException("Can't verify certificate");
    } catch (InvalidKeyException ike) {
        throw new CertificateException("Can't verify certificate");
    } catch (CRLException ce) {
        // treat as no crl
    }
    return c;
}
View Code

 

5.2 CertPathBuilder構建證書鏈CertPath

 

CertPath就是之前說的證書鏈。其實就是一個Certificate的有序列表。在列表的最后的一個Cert是一個自簽名的Cert。

 

 

5.3 CertPathValidator驗證Cert鏈

 

CertPathValidator用於校驗Cert。

 

 

 

6)KeyStore

    一個KeyStore是一個key、cert的庫,里面存儲了PrivateKey, Aliases, Certs.

KeyStore將會有專門的說明。

 

7)Signature簽名

用私鑰簽名,用公鑰驗證:

public class SignatureTest {
    
    @Test
    public void test() throws Exception{
        KeyPairGenerator keyPairGen=KeyPairGenerator.getInstance(Message.alogthem);
        keyPairGen.initialize(1024);
        KeyPair keyPair= keyPairGen.generateKeyPair();
        PublicKey puk=keyPair.getPublic();
        PrivateKey pik=keyPair.getPrivate();
        
        String data="Hello, Java.";
        Signature signature=Signature.getInstance("SHA1withDSA");
        
        // private key sign
        signature.initSign(pik);
        signature.update(data.getBytes());
        byte[] signinfo=signature.sign();
        
        // public key resolve sign
        signature.initVerify(puk);
        boolean ok=signature.verify(signinfo);
        System.out.println(ok);
        
        signature.update(data.getBytes());
        ok=signature.verify(signinfo);
        System.out.println(ok);
        
    }
    
}
View Code

 

到此,JCA部分的engine class已經大體上有個了解了。接下來就是要學習如何應用它們了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM