加密服務總是關聯到一個特定的算法或類型,它既提供了密碼操作(如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")); } }
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; } }
這個自定義的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(); } }
在這個用例中,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); }
第一次調用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); } }
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; }
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); } }
到此,JCA部分的engine class已經大體上有個了解了。接下來就是要學習如何應用它們了。