從事Android工作4年以來,只有前1年不到的時間是用C++在開發東西(主要是開發DLNA組件,目前我已將它們全部開源,參考http://blog.csdn.net/innost/article/details/40216763),后面的工作幾近都在用Java。自以為Java相干的東西都見過了,可前段時間有個朋友給我花了1個多小時講授他們某套系統的安全部系結構,其中觸及到很多專業術語,比如Message Digest(消息摘要)、Digital Signature(數字簽名)、KeyStore(恕我不知道翻譯成甚么好,還是用英文原稱吧)、CA(Certificate Authority)等。我當時腦袋就大了,尼瑪弄Java這么久,歷來沒接觸過啊。為此,我特地到AndroidFramework代碼中查詢了下,Android平臺里與之相干的東西還有1個KeyChain。
原來,上述內容都屬于Java世界中1個早已存在的知識模塊,那就是JavaSecurity。Java Security包括很多知識點,常見的有MD5,DigitalSignature等,而Android在Java Seurity以外,拓展了1個android.security包,此包中就提供了KeyChain。
本文將介紹Java Security相干的基礎知識,然后介紹下Android平臺上與之相干的使用處景。
實際上,在1些金融,銀行,電子支付方面的利用程序中,JavaSecurity使用的地方非常多。
https://code.csdn.net/Innost/androidsecuritydemo 目前已公然。
Java Security實際上是Java平臺中1個比較獨立的模塊。除軟件實現上內容外,它實際上對應了1系列的規范。從Java2開始,Java Security包括主要3個重要的規范:
在上述3個子模塊或規范中,JCE是JavaSecurity的大頭,其他兩個子模塊JSSE和JAAS都依賴于它,比如SSL/TLS在工作進程中需要使用密鑰對數據進行加解密,那末密鑰的創建和使用就依托JCE子模塊了。
另外,既然和安全相干,那末對安全敏感的相干部門或政府肯定會有所干涉。Java是在美國被發明的,所以美國政府對Java Security方面的出口(比如哪些模塊,哪些功能能給其他國家使用)有相干的限制。例如,不允許出口的JCE(從軟件實現上看,可能就是從Java官網上下載到的幾個Jar包文件)支持1些高級的加解密功能(比如在密鑰長度等方面有所限制)。
注意,Java Security包括的內容非常多,而本文將重點關注Java SecurityAPI與其使用方法方面的知識。對Java Security其他細節感興趣的同學可在瀏覽完本文后,再瀏覽參考文獻[1]。
介紹JCE之前,先來說解下JCE的設計架構。JCE在設計時重點關注兩個問題:
對獨立性而言,1個最通用的做法就是把定義和實現通過抽象類或基類的方式進行解耦合。在JCE中:
注意,可互操作性是指Provider A實現的MD5值,能被Provider B辨認。明顯,這個要求是公道的。
圖1所示為JCE中1些類的定義:
圖1 JCE中1些類的定義
圖1展現了JCE中的1些類定義。大家先關注左下角的Provider和Security類:
注意,由于歷史緣由,JCE的Service分散定義在好些個Package中:
上述這兩個package都屬于JCE的內容。從使用者的角度來看,其實不太需要關注它們到底定義在哪一個Package中(代碼中通過1句import xxx就能夠把對應的包引入,后續編寫代碼時候,直接使用相干類就行了)。
BTW,個人感覺Java類定義非常多,而且有些類和它們所在包的關系仿佛有些混亂。
前面已說過,JCE框架對使用者提供的是基礎類(抽象類或接口類),而具體實現需要有地方注冊到JCE框架中。所以,我們來看看Android平臺中,JCE框架都注冊了哪些Provider:
[-->Security.java]
static {
boolean loaded = false;
try {
//從資源文件里搜索是不是有security.properties定義,Android平臺是沒有這個文件的
InputStreamconfigStream =
Security.class.getResourceAsStream("security.properties");
InputStream input = newBufferedInputStream(configStream);
secprops.load(input);
loaded = true;
configStream.close();
}......
if (!loaded) {//注冊默許的Provider
registerDefaultProviders();
}
......
}
private static void registerDefaultProviders() {
/*
JCE對Provider的管理非常簡單,就是將Provider類名和對應的優先級存在屬性列表里
比以下面的:OpenSSLProvider,它對應的優先級是1.
優先級是甚么意思呢?前面說過,不同的Provider可以實現同1個功能集合,比如都實現
MessageDigestSpi。那末用戶在創建MessageDigest的實例時,如果沒有指明Provider名,
JCE默許從第1個(依照優先級,由1到n)Provider開始搜索,直到找到第1個實現了
MessageDigestSpi的Provider(比如Provider X)為止。然后,MessageDigest的實例
就會由Provider X創建。圖2展現了1個例子
*/
secprops.put("security.provider.1",
"com.android.org.conscrypt.OpenSSLProvider");
secprops.put("security.provider.2",
"org.apache.harmony.security.provider.cert.DRLCertFactory");
secprops.put("security.provider.3",
"com.android.org.bouncycastle.jce.provider.BouncyCastleProvider");
secprops.put("security.provider.4",
"org.apache.harmony.security.provider.crypto.CryptoProvider");
//和JSSE有關
secprops.put("security.provider.5",
"com.android.org.conscrypt.JSSEProvider");
}
圖2展現了Provider優先級使用的例子:
圖2 Provider優先級示例
圖2中:
注意,圖2中的SHA⑴/256和MD5都是MessageDigest的1種算法,本文后面會介紹它們。
Android平臺中,每一個利用程序啟動時都會注冊1個類型為“AndroidKeyStoreProvider”的對象。這是在ActivityThread中完成的,代碼以下所示:
[-->ActivityThread.java::main]
public static void main(String[] args) {
......
Security.addProvider(new AndroidKeyStoreProvider());
......
}
來看看AndroidKeyStoreProvider是甚么,代碼以下所示:
[-->AndroidKeyStoreProvider::AndroidKeyStoreProvider]
public class AndroidKeyStoreProvider extends Provider {
public static final StringPROVIDER_NAME = "AndroidKeyStore";
publicAndroidKeyStoreProvider() {
super(PROVIDER_NAME, 1.0,"Android KeyStore security provider");
put("KeyStore." + AndroidKeyStore.NAME,
AndroidKeyStore.class.getName());
put("KeyPairGenerator.RSA",AndroidKeyPairGenerator.class.getName());
}
}
AndroidKeyStoreProvider很簡單,但是看上去還是不明白它是干甚么的?其實,這個問題的更深層次的問題是:Provider是干甚么的?
固然,Provider的內容和功能比這要復雜,不過我們對Provider的實現沒甚么興趣,大家只要知道它存儲了1系列的屬性key和value就能夠了。JCE會根據情況去查詢這些key和對應的value。
來個例子,看看Android系統上都有哪些Provider:
[-->DemoActivity.java::testProvider]
void testProvider(){
e(TAG, "***Begin TestProviders***");
//獲得系統所有注冊的Provider
Provider[] providers = Security.getProviders();
for(Providerprovider:providers){
//輸出Provider名
e(TAG,"Provider:" + provider+" info:");
//前面說了,provider其實就是包括了1組key/value值。下面將打印每一個Provider的
//這些信息
Set<Entry<Object,Object>>allKeyAndValues = provider.entrySet();
Iterator<Entry<Object, Object>> iterator =allKeyAndValues.iterator();
while(iterator.hasNext()){
Entry<Object,Object> oneKeyAndValue =iterator.next();
Object key = oneKeyAndValue.getKey();
Object value =oneKeyAndValue.getValue();
//打印Key的類型和值
e(TAG,"===>" + "Keytype="+key.getClass().getSimpleName()+"
Key="+key.toString());
//打印Value的類型和值
e(TAG,"===>" + "Valuetype="+value.getClass().getSimpleName()+"
Value="+value.toString());
}
}
e(TAG, "***End TestProviders*** ");
}
在控制臺中,通過adb logcat | grep ASDemo就能夠顯示testProvider的輸出信息了,如圖3所示:
圖3 testProvider輸出示例
圖3打出了AndroidOpenSSLprovider的信息:
了解完JCE框架后,我們分別來介紹JCE中的1些重要Service。
談到安全,大家第1想到的就是密鑰,即Key。那末大家再仔細想一想下面這兩個問題:
圖4解釋了上述問題:
圖4 Key示意
圖4中:
在安全領域中,Key分為兩種:
圖5所示為JCE中Key相干的類和繼承關系。
圖5 JCE Key相干類
圖5中:
先來看對稱key的創建和導入(也就是把Key的書面表達導入到程序中并生成Key對象)
[-->DemoActivity.java::testKey]
{//對稱key即SecretKey創建和導入
//假定雙方約定使用DES算法來生成對稱密鑰
e(TAG,"==>secret key: generated it using DES");
KeyGeneratorkeyGenerator = KeyGenerator.getInstance("DES");
//設置密鑰長度。注意,每種算法所支持的密鑰長度都是不1樣的。DES只支持64位長度密鑰
//(或許是算法本身的限制,或是不同Provider的限制,或是政府管制的限制)
keyGenerator.init(64);
//生成SecretKey對象,即創建1個對稱密鑰
SecretKey secretKey = keyGenerator.generateKey();
//獲得2進制的書面表達
byte[] keyData =secretKey.getEncoded();
//平常使用時,1般會把上面的2進制數組通過Base64編碼轉換成字符串,然后發給使用者
String keyInBase64 =Base64.encodeToString(keyData,Base64.DEFAULT);
e(TAG,"==>secret key: encrpted data ="+ bytesToHexString(keyData));
e(TAG,"==>secrety key:base64code=" + keyInBase64);
e(TAG,"==>secrety key:alg=" + secretKey.getAlgorithm());
//假定對方收到了base64編碼后的密鑰,首先要得到其2進制表達式
byte[] receivedKeyData =Base64.decode(keyInBase64,Base64.DEFAULT);
//用2進制數組構造KeySpec對象。對稱key使用SecretKeySpec類
SecretKeySpec keySpec =new SecretKeySpec(receivedKeyData,”DES”);
//創建對稱Key導入用的SecretKeyFactory
SecretKeyFactorysecretKeyFactory = SecretKeyFactory.getInstance(”DES”);
//根據KeySpec還原Key對象,即把key的書面表達式轉換成了Key對象
SecretKey receivedKeyObject = secretKeyFactory.generateSecret(keySpec);
byte[]encodedReceivedKeyData = receivedKeyObject.getEncoded();
e(TAG,"==>secret key: received key encoded data ="
+bytesToHexString(encodedReceivedKeyData));
如果1切正常的話,紅色代碼和綠色代碼打印出的2進制表示應當完全1樣。此測試的結果如圖6所示:
圖6 SecretKey測試結果
此處有幾點說明:
注意,本文會討論太多算法相干的內容。
再來看KeyPair的用例:
[-->DemoActivity.java::KeyPair測試]
{//public/private key test
e(TAG, "==>keypair: generated it using RSA");
//使用RSA算法創建KeyPair
KeyPairGeneratorkeyPairGenerator = KeyPairGenerator.getInstance("RSA");
//設置密鑰長度
keyPairGenerator.initialize(1024);
//創建非對稱密鑰對,即KeyPair對象
KeyPair keyPair =keyPairGenerator.generateKeyPair();
//獲得密鑰對中的公鑰和私鑰對象
PublicKey publicKey =keyPair.getPublic();
PrivateKey privateKey =keyPair.getPrivate();
//打印base64編碼后的公鑰和私鑰值
e(TAG,"==>publickey:"+bytesToHexString(publicKey.getEncoded()));
e(TAG, "==>privatekey:"+bytesToHexString(privateKey.getEncoded()));
/*
現在要斟酌如何把公鑰傳遞給使用者。雖然可以和對稱密鑰1樣,把2進制數組取出來,但是
對非對稱密鑰來講,JCE不支持直接通過2進制數組來還原KeySpec(多是算法不支持)。
那該怎樣辦呢?前面曾說了,除直接還原2進制數組外,還可以通過具體算法的參數來還原
RSA非對稱密鑰就得使用這類方法:
1 首先我們要獲得RSA公鑰的KeySpec。
*/
//獲得RSAPublicKeySpec的class對象
Class spec = Class.forName("java.security.spec.RSAPublicKeySpec");
//創建KeyFactory,并獲得RSAPublicKeySpec
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
RSAPublicKeySpecrsaPublicKeySpec =
(RSAPublicKeySpec)keyFactory.getKeySpec(publicKey, spec);
//對RSA算法來講,只要獲得modulus和exponent這兩個RSA算法特定的參數就能夠了
BigInteger modulus =rsaPublicKeySpec.getModulus();
BigInteger exponent =rsaPublicKeySpec.getPublicExponent();
//把這兩個參數轉換成Base64編碼,然后發送給對方
e(TAG,"==>rsa pubkey spec:modulus="+
bytesToHexString(modulus.toByteArray()));
e(TAG,"==>rsa pubkey spec:exponent="+
bytesToHexString(exponent.toByteArray()));
//假定接收方收到了代表modulus和exponent的base64字符串并得到了它們的2進制表達式
byte[] modulusByteArry =modulus.toByteArray();
byte[] exponentByteArry =exponent.toByteArray();
//由接收到的參數構造RSAPublicKeySpec對象
RSAPublicKeySpecreceivedKeySpec = new RSAPublicKeySpec(
newBigInteger(modulusByteArry),
new BigInteger(exponentByteArry));
//根據RSAPublicKeySpec對象獲得公鑰對象
KeyFactoryreceivedKeyFactory = keyFactory.getInstance("RSA");
PublicKey receivedPublicKey =
receivedKeyFactory.generatePublic(receivedKeySpec);
e(TAG, "==>received pubkey:"+
bytesToHexString(receivedPublicKey.getEncoded()));
}
如果1切正常的話,上述代碼中紅色和黑色代碼段將輸出完全1樣的公鑰2進制數據。如圖7所示:
圖7 KeyPair測試示意圖
在Android平臺的JCE中,非對稱Key的經常使用算法有“RSA”、“DSA”、“Diffie?Hellman”、“Elliptic Curve (EC)”等。
我自己在學習Key的時候,最迷惑的就是前面提到的兩個問題:
Key是甚么?雖然“密鑰”這個詞常常讓我聯想到實際生活中的鑰匙,但是在學習JavaSecurity之前,我1直不知道在代碼中(或編程時),它究竟是個甚么玩意。并且,它到底怎樣創建。
創建Key以后,我怎樣把它傳遞給其他人。就好比鑰匙1樣,你總得給個實物給人家吧?
現在來看這兩個問題的總結性回答:):
不理解上述內容的同學,請把實例代碼再仔細看看!
JCE中,Certificates(是復數喔!)是證書之意。“證書”也是1個平常生活中經常使用的詞,但是在JCE中,或說在Java Security中,它有甚么用呢?
這個問題的答案還是和Key的傳遞有關系。前面例子中我們說,創建密鑰的人1般會把Key的書面表達情勢轉換成Base64編碼后的字符串發給使用者。使用者再解碼,然后還原Key就能夠用了。
上面這個流程本身沒有甚么隱患,但是,是否是隨便1個人(1個組織,1個機構等等等等)給你發1個key,你就敢用呢?簡單點說,你怎樣判斷是不是該信任給你發Key的某個人或某個機構呢?
好吧,這就好比現實生活中有個人說自己是警察,那你肯定得要他取出警官證或甚么別的東西來證明他是警察。這個警官證的作用就是證書的作用。
1般而言,我們會把Key的2進制表達式放到證書里,證書本身再填上其他信息(比如此證書是誰簽發的,甚么時候簽發的,有效期多久,證書的數字簽名等等)。
初看起來,好像證書比直接發base64字符串要正規點,1方面它包括了證書簽發者的信息,而且還有數字簽名以避免內容被篡改。
但是,證書解決了信任的問題嗎?很明顯是沒有的。由于證書是誰都可以制作的。既然不是人人都可以相信,那末,也不是隨意甚么證書都可以被信任。
怎樣辦?先來看現實生活中是怎樣解決信任問題的。
現實生活中也有很多證書,大到房產證、身份證,小到離職證明、介紹信。對方怎樣判斷你拿的這些證是真實有效的呢?
對頭,看證書是誰/或哪一個機構的“手墨”!比如,結婚證首先要看是否是民政局的章。那....民政局是不是應當被信任呢???
好吧。關于民政局是不是應當被信任的這個問題在技術上基本無解,它是1個死循環。由于,即便能找到另外1個機構證明民政局合法有效,但這個問題仍然會順流而上,說民政局該被信任的那個機構其本身是不是能被信任呢?....此問題終究會沒完沒了的問下去。
那怎樣辦?沒甚么好辦法。只要大家公認民政局能被信任,就能夠了。
同理,對證書的是不是可信任問題而言:
客戶拿到證書a后,首先要檢查下:
......唧唧歪歪半天,其實關于證書的核心問題就1個:
證書背后常常是1個證書鏈。
.......
為了方便,系統(PC,Android,乃至閱讀器)等都會把1些頂級CA(也叫Root CA,即根CA)的證書默許集成到系統里。這些RootCA用作自己身份證明的證書(包括該CA的公鑰等信息)叫根證書。根證書理論上是需要被信任的。以Android為例,它在libcore/luni/src/main/files/cacerts下放了150多個根證書(以Android 4.4為例),如圖8所示:
圖8 Android自帶的根證書文件
我們隨意打開1個根證書文件看看,以下所示:
[某證書文件的內容,用記事本打開便可]
-----BEGIN CERTIFICATE-----
MIID5TCCAs2gAwIBAgIEOeSXnjANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UEBhMC
VVMxFDASBgNVBAoTC1dlbGxzIEZhcmdvMSwwKgYDVQQLEyNXZWxscyBGYXJnbyBD
ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEvMC0GA1UEAxMmV2VsbHMgRmFyZ28gUm9v
dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDAxMDExMTY0MTI4WhcNMjEwMTE0
MTY0MTI4WjCBgjELMAkGA1UEBhMCVVMxFDASBgNVBAoTC1dlbGxzIEZhcmdvMSww
KgYDVQQLEyNXZWxscyBGYXJnbyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEvMC0G
A1UEAxMmV2VsbHMgRmFyZ28gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDVqDM7Jvk0/82bfuUER84A4n13
5zHCLielTWi5MbqNQ1mXx3Oqfz1cQJ4F5aHiidlMuD+b+Qy0yGIZLEWukR5zcUHE
SxP9cMIlrCL1dQu3U+SlK93OvRw6esP3E48mVJwWa2uv+9iWsWCaSOAlIiR5NM4O
JgALTqv9i86C1y8IcGjBqAr5dE8Hq6T54oN+J3N0Prj5OEL8pahbSCOz6+MlsoCu
ltQKnMJ4msZoGK43YjdeUXWoWGPAUe5AeH6orxqg4bB4nVCMe+ez/I4jsNtlAHCE
AQgAFG5Uhpq6zPk3EPbg3oQtnaSFN9OH4xXQwReQfhkhahKpdv0SAulPIV4XAgMB
AAGjYTBfMA8GA1UdEwEB/wQFMAMBAf8wTAYDVR0gBEUwQzBBBgtghkgBhvt7hwcB
CzAyMDAGCCsGAQUFBwIBFiRodHRwOi8vd3d3LndlbGxzZmFyZ28uY29tL2NlcnRw
b2xpY3kwDQYJKoZIhvcNAQEFBQADggEBANIn3ZwKdyu7IvICtUpKkfnRLb7kuxpo
7w6kAOnu5+/u9vnldKTC2FJYxHT7zmu1Oyl5GFrvm+0fazbuSCUlFLZWohDo7qd/
0D+j0MNdJu4HzMPBJCGHHt8qElNvQRbn7a6U+oxy+hNH8Dx+rn0ROhPs7fpvcmR7
nX1/Jv16+yWt6j4pf0zjAFcysLPp7VMX2YuyFA4w6OXVE8Zkr8QA1dhYJPz1j+zx
x32l2w8n0cbyQIjmH/ZhqPRCyLk306m+LFZ4wnKbWV01QIroTmMatukgalHizqSQ
33ZwmVxwQ023tqcZZE6St8WRPH9IFmV7Fv3L/PvZ1dZPIWU7Sn9Ho/s=
-----END CERTIFICATE-----
Certificate: #下面是證書的明文內容
Data:
Version: 3 (0x2)
Serial Number:971282334 (0x39e4979e)
Signature Algorithm: sha1WithRSAEncryption
Issuer: C=US, O=Wells Fargo, OU=Wells Fargo CertificationAuthority, CN=Wells Fargo Root Certificate Authority
Validity
Not Before: Oct11 16:41:28 2000 GMT
Not After : Jan14 16:41:28 2021 GMT
Subject: C=US, O=Wells Fargo, OU=Wells Fargo CertificationAuthority, CN=Wells Fargo Root Certificate Authority
Subject Public Key Info:#Public Key的KeySpec表達式
Public Key Algorithm: rsaEncryption #PublicKey的算法
Public-Key: (2048 bit)
Modulus:
00:d5:a8:33:3b:26:f9:34:ff:cd:9b:7e:e5:04:47:
ce:00:e2:7d:77:e7:31:c2:2e:27:a5:4d:68:b9:31:
ba:8d:43:59:97:c7:73:aa:7f:3d:5c:40:9e:05:e5:
a1:e2:89:d9:4c:b8:3f:9b:f9:0c:b4:c8:62:19:2c:
45:ae:91:1e:73:71:41:c4:4b:13:fd:70:c2:25:ac:
22:f5:75:0b:b7:53:e4:a5:2b:dd:ce:bd:1c:3a:7a:
c3:f7:13:8f:26:54:9c:16:6b:6b:af:fb:d8:96:b1:
60:9a:48:e0:25:22:24:79:34:ce:0e:26:00:0b:4e:
ab:fd:8b:ce:82:d7:2f:08:70:68:c1:a8:0a:f9:74:
4f:07:ab:a4:f9:e2:83:7e:27:73:74:3e:b8:f9:38:
42:fc:a5:a8:5b:48:23:b3:eb:e3:25:b2:80:ae:96:
d4:0a:9c:c2:78:9a:c6:68:18:ae:37:62:37:5e:51:
75:a8:58:63:c0:51:ee:40:78:7e:a8:af:1a:a0:e1:
b0:78:9d:50:8c:7b:e7:b3:fc:8e:23:b0:db:65:00:
70:84:01:08:00:14:6e:54:86:9a:ba:cc:f9:37:10:
f6:e0:de:84:2d:9d:a4:85:37:d3:87:e3:15:d0:c1:
17:90:7e:19:21:6a:12:a9:76:fd:12:02:e9:4f:21:
5e:17
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 BasicConstraints: critical
CA:TRUE
X509v3Certificate Policies:
Policy:2.16.840.1.114171.903.1.11
CPS:http://www.wellsfargo.com/certpolicy
Signature Algorithm: sha1WithRSAEncryption #數字簽名,以后再講
d2:27:dd:9c:0a:77:2b:bb:22:f2:02:b5:4a:4a:91:f9:d1:2d:
be:e4:bb:1a:68:ef:0e:a4:00:e9:ee:e7:ef:ee:f6:f9:e5:74:
a4:c2:d8:52:58:c4:74:fb:ce:6b:b5:3b:29:79:18:5a:ef:9b:
ed:1f:6b:36:ee:48:25:25:14:b6:56:a2:10:e8:ee:a7:7f:d0:
3f:a3:d0:c3:5d:26:ee:07:cc:c3:c1:24:21:87:1e:df:2a:12:
53:6f:41:16:e7:ed:ae:94:fa:8c:72:fa:13:47:f0:3c:7e:ae:
7d:11:3a:13:ec:ed:fa:6f:72:64:7b:9d:7d:7f:26:fd:7a:fb:
25:ad:ea:3e:29:7f:4c:e3:00:57:32:b0:b3:e9:ed:53:17:d9:
8b:b2:14:0e:30:e8:e5:d5:13:c6:64:af:c4:00:d5:d8:58:24:
fc:f5:8f:ec:f1:c7:7d:a5:db:0f:27:d1:c6:f2:40:88:e6:1f:
f6:61:a8:f4:42:c8:b9:37:d3:a9:be:2c:56:78:c2:72:9b:59:
5d:35:40:8a:e8:4e:63:1a:b6:e9:20:6a:51:e2:ce:a4:90:df:
76:70:99:5c:70:43:4d:b7:b6:a7:19:64:4e:92:b7:c5:91:3c:
7f:48:16:65:7b:16:fd:cb:fc:fb:d9:d5:d6:4f:21:65:3b:4a:
7f:47:a3:fb
SHA1 Fingerprint=93:E6:AB:22:03:03:B5:23:28:DC:DA:56:9E:BA:E4:D1:D1:CC:FB:65
關于證書文件,還有1些容易混淆的事情要交代:
下面是1些常見的證書文件格式,1般用文件后綴名標示。
上一篇 技能樹之旅: 計算點數與從這開始
下一篇 J2EE--JDBC