基於 Java 的 Web 服務和無線 Java 開發是 JavaOne 2002 的兩個最突出的主題。它們代表普及計算領域中未來的後端和前端 Java 技術。
Web 服務是基於標准 XML 通信協議的松耦合的可互操作的軟件組件。Web 服務的使用使供應商能夠在其核心競爭力所在的特定市場中提供服務。然後,客戶可以根據其不同需要,從多個供應商選擇采購服務。這種便利意味著 Web 服務完全適合於為無線前端提供服務。無線信息設備的便利和動態本質允許移動用戶利用模塊化的且動態可重新配置的後端服務。
Java 平台可以在無線 Web 服務應用程序開發中扮演幾個重要角色。在無線端,Java 2 Micro Edition(J2ME)為所有無線設備(從蜂窩電話到復雜的家用無線信息家電)提供了跨設備的兼容性、高級語言功能和大量庫。J2ME 的一個關鍵組件是移動信息設備框架(Mobile Information Device Profile(MIDP)),它在蜂窩電話和低端 PDA 上定義 Java API 和運行時環境。由於龐大數量的低端設備,期望在將來能夠廣泛部署 MIDP。
從 Web 服務端,Java 2 企業版(J2EE)已經具有所有必需的 API 和庫來處理 Web 服務 XML 消息。通過 Web 服務接口或網關,可以方便地將用 EJB 技術實現的核心 J2EE 功能、JDBC API 和 RMI API 用於外部世界。為了將這些特性集合起來並啟用無線 Web 服務應用程序,還提出了 J2ME Web 服務規范,目前它在 Java Community Process(JSR 172)中。
無線 Web 服務中的安全性
雖然基於 Java 的無線 Web 服務在普及移動商業世界中有一個光明的前途,但當前技術仍不成熟。安全性仍是剩下待解決的問題之一。無線通信是無線電波攔截容易獲取的目標,而無線設備幾乎沒有任何計算能力來支持所有通信數據的強加密。此外,在後端,Web 服務運行在企業防火牆之外並使用開放消息傳遞協議來彼此交互。無線 Web 服務同樣是易招受各種破解攻擊的目標。已開發得很好的點對點安全性技術(如 SSL/TLS 和 HTTPS)不適合於多供應商、多中間 Web 服務網絡拓撲圖 ― 重點必需集中在保護內容本身而不是傳遞內容的連接上。盡管面臨新挑戰,然而 Web 服務本身還是能用來增強移動商業安全性。新興的 Web 服務安全性規范的出現使您能夠將 Web 服務用作安全性實用程序。
在下面的段落中,我將討論一種常用的安全性技術:數字簽名。我將向您演示如何在 XML 消息中使用數字簽名來保證端對端的數據完整性。我將應用一些示例來說明如何通過在無線端上使用流行的 J2ME/MIDP 平台並在後端上使用 JavaServer Pages(JSP)技術來實現 XML 數字簽名。最後,我將討論性能問題以及在當前 MIDP 設備上使用數字簽名的可行性。MIDP 編程細節不在本文范圍內;如果您需要復習一下,請參閱 參考資料一節。
安全通信的元素
數據完整性只是保護通信的一個方面。數字簽名也可以提供其它方面的解決方案。通常,安全的網絡通信必須符合下列標准:
讓我們假設一下,您是一位股票交易者,當您不在交易大廳時,使用蜂窩電話來跟蹤股票價格變化。在上下班途中,您的電話提醒您正在監視的一只股票的價格已經跌入您的心理最低價。現在,您應根據此提示買進它並利用此低價來賺一筆嗎?在您采取任何行動之前,您必須絕對確保提示本身是可信的。如果競爭者可能攔截並更改消息(例如,更改股票代碼),那麼他就可能引誘您買進弄錯的股票,並將他的高價位股票拋給您。您如何知道消息在從監視服務到您電話的途中沒有被篡改呢?
數據完整性確實是通信安全性的最重要方面之一。物理上安全的網絡十分昂貴,而且並不覆蓋范圍很廣的地理區域。如果您的業務必須依靠因特網才能通信,那麼您必須面對因特網本身幾乎不提供安全性這一事實。因特網數據包在到達其目的地之前必須途經由非對話雙方控制的多個路由器和主機。數據通信在無線因特網上特別容易受到攻擊。
及時出現的援救工具是公鑰基礎設施(PKI)和數字簽名。(和 MIDP 編程一樣,數字簽名不在本文范圍內;感興趣的讀者可以參考 參考資料一節來獲取更多信息。)概括地說,在 PKI 數字簽名模式中,每一方都有兩個密碼術密鑰:公鑰,任何人都可以使用它,而私鑰,是保密的,只有某個人本身可以使用。用私鑰加密的消息只能由相應的公鑰正確解密。當發送方發送消息時,他可以將相同消息的私鑰加密版以及他的公鑰與該消息一起發送。接收方使用發送方的公鑰來解密加密版本。如果它與明文消息匹配,那麼接收方可以知道該消息確實可信。該消息的私鑰加密版用作完整性驗證記號,我們將它稱為“數字簽名”。
因為原始消息可能相當長,而且生成和驗證數字簽名的公鑰算法是資源密集的,所以發送方通常計算稱為“摘要”的原始消息的短版本並且僅對該版本進行數字形式的簽署。摘要具有固定長度,它是任何長度的輸入消息的單向散列;其計算速度非常快。接收方首先驗證接收的消息是否產生正確摘要。如果該摘要不匹配,則在執行任何公鑰算法之前拒絕該消息。這可以有助於防止攻擊造成堵塞,在這種攻擊中,攻擊者通過用偽造的公鑰請求填斥服務器,以耗盡它的計算資源。
在大多數實際應用程序中,公鑰本身由可信的權威部門進行數字簽名,並成為“數字證書”來驗證發送方的標識。不過,數字證書的處理不在本文范圍內,所以在下列示例中,我將假設發送方是可信的並使用未簽署的公鑰來說明方法。
定義的 XML 數字簽名
正如我早先提到的那樣,XML 正成為 Web 服務世界中一個主要的數據交換協議。驅動 Web 服務的 XML 消息在到達目的地之前,通常需要經過多個中間環節。因此,我們保護從端到端的通信內容是重要的。完成這一任務的最好方法是,將 XML 文檔及其安全性信息(如簽名、摘要和密鑰等等)都裝運在一起,作為單個文檔。
XML 數字簽名是將數字簽名添加到 XML 文檔的 W3C 規范。發送方可以選擇對整個文檔或者僅它的一部分進行數字簽名。數字簽名、摘要和公鑰被格式化成 XML 元素。那些安全性信息的額外元素可以封裝整個原始 XML 消息,或者可以將它們嵌入原始消息。為了方便起見,我將在本文中使用封裝格式。
為了清晰起見,我在本文中使用的 XML 數字簽名示例並不完全與 W3C 相符。例如,我省去了標准(XML 標記的標准化)部分,因為它僅確保 XML 文檔的一致性而與安全性本身無關。另外,我將密鑰分成幾個參數並將那些參數傳遞到公鑰元素 KeyInfo
下的單獨 XML 元素中,以代替使用編碼的公鑰證書。這在密鑰和處理它們的 Java 代碼之間建立了更明顯的連接。
處理 MIDP 應用程序中的 XML 數字簽名
IBM alphaWorks 開發了一種稱為 XML Security Suite 的 Java 包,它支持最新的 XML 數字簽名規范。JSR 105 是標准化一組 Java API 以處理 XML 數字簽名的一項社區成果。然而,它們僅作用於 Java 2 標准版(J2SE),這意味著可以在服務器端上使用 XML Security Suite 或 JSR 105 Java XML 數字簽名 API,但不能在 MIDP 無線設備端上使用它們。
要處理 XML 數字簽名,正在使用的無線設備需要支持下列功能:
在下一節中,我將討論一種輕量級 Java 密碼術包,您可以在服務器端和無線 MIDP 設備端上使用它來生成並驗證 XML 數字簽名。
安全移動代碼中的數字簽名
數字簽名不僅有助於保護應用程序數據通信,它們還有助於保護應用程序本身。無線設備常常需要從無線網絡下載應用程序來動態調整自身,以使其適合新環境和任務。懷有敵意的各方可能攔截移動代碼分發版並將病毒和其它有害的特洛伊代碼段插入下載的應用程序中。我們怎麼樣才能確保移動代碼可信呢?移動代碼供應商可以用其數字證書對整個 JAR 壓縮文檔進行數字簽名。無線用戶可以根據供應商的可信級別來確定該應用程序的安全性域。
Bouncy Castle Crypto APIBouncy Castle 是一種用於 Java 平台的開放源碼的輕量級密碼術包。它支持大量的密碼術算法,並提供 JCE 1.2.1 的實現。因為 Bouncy Castle 被設計成輕量級的,所以從 J2SE 1.4 到 J2ME(包括 MIDP)平台,它都可以運行。它是在 MIDP 上運行的唯一完整的密碼術包。
不管 Bouncy Castle 包的功能有多強大,它有一個主要問題:缺少文檔。不存在在線文檔,其 JavaDoc 寫得並不好。與許多其它高級密碼術包相似,Bouncy Castle 包廣泛使用類型多態性來將常規概念與實現算法分開。對於初學者來說,辨認類之間的關系以及方法參數和返回值的正確類型是很困難的。通常,開發人員必須浏覽一下源代碼和測試用例來研究做事的正確方法。顯然,Bouncy Castle 包的指南非常合適。
在本文的其余部分,我們將簡略地論述 XML 數字簽名規范以及幾個不同 Bouncy Castle 密鑰生成器、編碼引擎、數字簽名簽名引擎和摘要引擎的用法。
合在一起
到目前為止,我們已經討論了許多技術和概念。下面,我將說明完整過程:密鑰生成、在服務器端簽署文檔、以安全的 XML 格式編碼和傳送文檔以及在客戶機端驗證文檔。
在接下來的幾節中,我們將遵循這些步驟來實現幾個示例。因為我們的示例在服務器端和客戶機端使用相同的 Bouncy Castle Crypto API,所以很容易更改它們以在無線設備上簽署消息並在服務器端驗證它。
處理摘要
正如我早先提到的那樣,為了改進性能並避免攻擊造成堵塞,您實際上簽署的是消息摘要而不是消息本身。清單 1 說明了如何使用 SHA1Digest
摘要引擎來計算來自一段文本消息的已編碼的摘要。
清單 1. 創建已編碼的摘要
static public String getDigest( String mesg ) throws Exception {
SHA1Digest digEng = new SHA1Digest();
byte [] mesgBytes = mesg.getBytes();
digEng.update( mesgBytes, 0, mesgBytes.length );
byte [] digest = new byte[digEng.getDigestSize()];
digEng.doFinal(digest, 0);
// Encode the digest into ASCII format for XML
return (new String(Base64.encode(digest)));
}
DSA 簽名示例
方法 DSASigUtil.generateKeys()
生成密鑰對。正如我討論過的那樣,這個步驟通常由中央認證中心在脫機狀態下完成,如清單 2 所示:
清單 2. 生成密鑰對
// Get a secure random source.
SecureRandom sr = new SecureRandom();
// Generate DSA parameters.
DSAParametersGenerator DSAParaGen = new DSAParametersGenerator();
DSAParaGen.init(1024, 80, sr);
DSAPara = DSAParaGen.generateParameters();
// Get DSA key generation parameters.
DSAKeyGenerationParameters DSAKeyGenPara =
new DSAKeyGenerationParameters(sr, DSAPara);
// Generate keys.
DSAKeyPairGenerator DSAKeyPairGen = new DSAKeyPairGenerator();
DSAKeyPairGen.init( DSAKeyGenPara );
AsymmetricCipherKeyPair keyPair = DSAKeyPairGen.generateKeyPair();
privKey = (DSAPrivateKeyParameters) keyPair.getPrivate();
pubKey = (DSAPublicKeyParameters) keyPair.getPublic();
Y
來描述,並且用 pubKey.getY()
方法來檢索它。參數 G
、 P
和 Q
描述模型。類 DSAUtil
中的下列方法檢索模型和密鑰參數,它們是重新構造公鑰對象所必需的:
public static String getG() throws Exception {
return (new String(Base64.encode(DSAPara.getG().toByteArray())));
}
public static String getP() throws Exception {
return (new String(Base64.encode(DSAPara.getP().toByteArray())));
}
public static String getQ() throws Exception {
return (new String(Base64.encode(DSAPara.getQ().toByteArray())));
}
public static String getY() throws Exception {
return (new String(Base64.encode(pubKey.getY().toByteArray())));
}
通過使用生成的私鑰,實用程序類 DSASigUtil
可以從摘要獲取兩部分 DSA 簽名 R
和 S
:
static public String [] getSignature (String digest) throws Exception {
// Sign
DSASigner signer = new DSASigner();
signer.init( true, privKey );
BigInteger [] sigArray = signer.generateSignature( digest.getBytes());
String [] result = new String [2];
// Signature R
result[0] = new String(Base64.encode(sigArray[0].toByteArray()));
// Signature S
result[1] = new String(Base64.encode(sigArray[1].toByteArray()));
return result;
}
服務器將摘要、簽名和密鑰參數編碼成 ASCII 文本格式並以 XML 數字簽名格式嵌入該文本,如清單 5 所示:
<SignedMesg>
<mesg>Hello World</mesg>
<Signature>
<SignedInfo>
<SignatureMethod
Algorithm="http://www.w3.org/2000/09/XMLdsig#dsa-sha1" />
<DigestValue>Ck1VqNd45QIvq3AZd8XYQLvEhtA=</DigestValue>
</SignedInfo>
<SignatureValue>
<R>AMfVKyIUyPGdeUCtJxU+N9kQJc2x</R>
<S>RwGahqpopPx//bMYXzH8dtY0lhA=</S>
</SignatureValue>
<KeyInfo>
<KeyValue>
<DSAKeyValue>
<G>
FgLTXVdxKAmDQtQHkDdFF5zthKSpQhUCzRgXxz7yzxM
OLYrRoj5D8AXdGLS+5CzT4gu55MbO62dBfyEWKbWTIO
6E+CuOfa53wvqjMl67tGxc8szgWWA6ZvRwVVVmJ6wqB
m5hNLr7q1X2eJKQ+u3XYpFflJktOjV8O3zeEPOtsTQ=
</G>
<P>
AOAu2WqVEKGTF8Zcxgde4vxc8f/Z+hk8A10M0AtY2lU
8CX54dz2MuD6hOmhqGXJxIVlV9085d9D0yHcMv2wl9V
Vt0/ww+aqFukCKZj9fHgZzq26nOBXMqibDo67J2vfQw
EZMvCnyBXdS665whjzl5i7ubXu2Su+AqsodnvG9pyYB
</P>
<Q>AMjJUZy1RnQRqe/22BS83k2Hk8VR</Q>
<Y>
AM/9leouAW7nyON24xeqibMUpVOW8RyzcdNjp9NiPdfm
HT42BvB4JL/cXx0tCbyHtcR5G+vALoOo7Mh3JJ+/gjx7
sS8uHNngqx6O6dADrc9VdPvyllNDR0szLja1RTRCIy9M
8p0dKe/U8iotAj2zctjfbrroMu/fTOBhkvb2gVvR
</Y>
</DSAKeyValue>
</KeyValue>
</KeyInfo>
</Signature>
</SignedMesg>
驗證 MIDP 應用程序從 XML 文檔解析出摘要、密鑰參數和簽名,重新構造公鑰並使用下列方法來驗證簽名:
static public boolean verify (String digest,
String sig_r, String sig_s,
String key_g, String key_p,
String key_q, String key_y ) {
BigInteger g = new BigInteger( Base64.decode(key_g) );
BigInteger p = new BigInteger( Base64.decode(key_p) );
BigInteger q = new BigInteger( Base64.decode(key_q) );
BigInteger y = new BigInteger( Base64.decode(key_y) );
BigInteger r = new BigInteger( Base64.decode(sig_r) );
BigInteger s = new BigInteger( Base64.decode(sig_s) );
DSAParameters DSAPara = new DSAParameters(p, q, g);
DSAPublicKeyParameters DSAPubKeyPara = new DSAPublicKeyParameters(y,
DSAPara);
// Verify
DSASigner signer = new DSASigner();
signer.init( false, DSAPubKeyPara );
boolean result = signer.verifySignature( digest.getBytes(), r, s );
return result;
}
在 ECDSASigUtil
類中,首先定義您計劃使用的橢圓曲線模型,如清單 7 所示:
清單 7. 定義橢圓曲線模型
private static BigInteger q = new
BigInteger("6277101735386680763835789423207666416083908700390324961279");
private static BigInteger a = new
BigInteger("fffffffffffffffffffffffffffffffefffffffffffffffc", 16);
private static BigInteger b = new
BigInteger("64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1", 16);
private static BigInteger n = new
BigInteger("6277101735386680763835789423176059013767194773182842284081");
private static byte [] G =
Hex.decode("03188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012");
ECDSASigUtil.generateKeys()
方法使用清單 7 中的模型生成隨機的密鑰對。正如前面提到的那樣,這個步驟通常由中央認證中心在脫機狀態下完成。
// Get a secure random source.
SecureRandom sr = new SecureRandom();
ECCurve.Fp curve = new ECCurve.Fp(q, a, b);
ECDomainParameters ECDomPara = new ECDomainParameters(curve,
curve.decodePoint(G),
n );
ECKeyGenerationParameters ECKeyGenPara =
new ECKeyGenerationParameters(ECDomPara, sr);
ECKeyPairGenerator ECKeyPairGen = new ECKeyPairGenerator();
ECKeyPairGen.init( ECKeyGenPara );
AsymmetricCipherKeyPair keyPair = ECKeyPairGen.generateKeyPair();
privKey = (ECPrivateKeyParameters) keyPair.getPrivate();
pubKey = (ECPublicKeyParameters) keyPair.getPublic();
公鑰以參數 Q
來描述,並且用 pubKey.getQ()
方法來檢索它。為了避免與模型參數 q
產生混淆,在方法中使用 QQ
,XML 元素名使用大寫的 Q
。清單 9 顯示了 ECDSAUtil
類中的方法。這些方法檢索模型和密鑰參數,它們是重新構造公鑰對象所必需的。
// public key specific fIEld
public static String getQQ() throws Exception {
return (new String(Base64.encode(pubKey.getQ().getEncoded())));
}
// Key parameter fields. Could also be retrIEved from pubKey.
public static String getQ() throws Exception {
return (new String(Base64.encode(q.toByteArray())));
}
public static String getA() throws Exception {
return (new String(Base64.encode(a.toByteArray())));
}
public static String getB() throws Exception {
return (new String(Base64.encode(b.toByteArray())));
}
public static String getN() throws Exception {
return (new String(Base64.encode(n.toByteArray())));
}
public static String getG() throws Exception {
return (new String(Base64.encode(G)));
}
通過使用生成的私鑰,實用程序類 ECDSASigUtil
可以從摘要獲取兩部分 DSA 簽名 R
和 S
:
static public String [] getSignature (String digest) throws Exception {
// Sign
ECDSASigner signer = new ECDSASigner();
signer.init( true, privKey );
BigInteger [] sigArray = signer.generateSignature( digest.getBytes());
String [] result = new String [2];
// Signature R
result[0] = new String(Base64.encode(sigArray[0].toByteArray()));
// Signature S
result[1] = new String(Base64.encode(sigArray[1].toByteArray()));
return result;
}
服務器將摘要、簽名和密鑰參數編碼成 ASCII 文本格式並以 XML 數字簽名格式嵌入該文本。和檢索方法名中一樣,公鑰參數 Q
被記錄為 QQ
,以將它與相應 XML 元素中的密鑰參數 q
區分開來,如清單 11 所示: 清單 11. 編碼並以數字簽名格式嵌入<SignedMesg>
<mesg>Hello World</mesg>
<Signature>
<SignedInfo>
<SignatureMethod
Algorithm="http://www.w3.org/2000/09/XMLdsig#dsa-sha1" />
<DigestValue>Ck1VqNd45QIvq3AZd8XYQLvEhtA=</DigestValue>
</SignedInfo>
<SignatureValue>
<R>NK/EIL2lrbFFCThnEuYlUWzh6IEfMsts</R>
<S>AMeJDecKWrQO6Eeehl3het+FlDDL4IEdCA==</S>
</SignatureValue>
<KeyInfo>
<KeyValue>
<ECKeyValue>
<QQ>AwCiF5uG+DII/x1XTq84fLm4eGN2fED1PYc=</QQ>
<Q>AP////////////////////7//////////w==</Q>
<A>AP////////////////////7//////////A==</A>
<B>ZCEFGeWcgOcPp+mrciQwSf643uzBRrmx</B>
<N>AP///////////////5ne+DYUa8mxtNIoMQ==</N>
<G>AxiNqA6wMJD2fL8g60OhiAD0/wr9gv8QEg==</G>
</ECKeyValue>
</KeyValue>
</KeyInfo>
</Signature>
</SignedMesg>
驗證 MIDP 應用程序從 XML 文檔解析出摘要、密鑰參數和簽名,重新構造公鑰並使用清單 12 中顯示的方法來驗證簽名:
static public boolean verify (String digest,
String sig_r, String sig_s,
String key_q, String key_a,
String key_b, String key_n,
String key_G, String key_Q ) {
BigInteger q = new BigInteger( Base64.decode(key_q) );
BigInteger a = new BigInteger( Base64.decode(key_a) );
BigInteger b = new BigInteger( Base64.decode(key_b) );
BigInteger n = new BigInteger( Base64.decode(key_n) );
byte [] G = Base64.decode(key_G);
byte [] Q = Base64.decode(key_Q);
BigInteger r = new BigInteger( Base64.decode(sig_r) );
BigInteger s = new BigInteger( Base64.decode(sig_s) );
ECCurve.Fp curve = new ECCurve.Fp(q, a, b);
ECDomainParameters ECDomPara = new ECDomainParameters(
curve, curve.decodePoint(G), n );
ECPublicKeyParameters pubKey = new ECPublicKeyParameters(
curve.decodePoint(Q), ECDomPara );
// Verify
ECDSASigner signer = new ECDSASigner();
signer.init( false, pubKey );
boolean result = signer.verifySignature( digest.getBytes(), r, s );
return result;
}
RSA 算法只有一個模型參數 Exponent
:
private static BigInteger pubExp = new BigInteger("11", 16);
RSASigUtil.generateKeys()
方法使用 Exponent
生成隨機的密鑰對。同樣,這個步驟通常由中央認證中心在脫機狀態下完成。
SecureRandom sr = new SecureRandom();
RSAKeyGenerationParameters RSAKeyGenPara =
new RSAKeyGenerationParameters(pubExp, sr, 1024, 80);
RSAKeyPairGenerator RSAKeyPairGen = new RSAKeyPairGenerator();
RSAKeyPairGen.init(RSAKeyGenPara);
AsymmetricCipherKeyPair keyPair = RSAKeyPairGen.generateKeyPair();
privKey = (RSAPrivateCrtKeyParameters) keyPair.getPrivate();
pubKey = (RSAKeyParameters) keyPair.getPublic();
公鑰以參數 Modulus
來描述,並且用 pubKey.getModulus()
方法來檢索它。清單 14 顯示了 RSAUtil
類中的方法。這些方法檢索 Exponent
和 Modulus
、模型以及密鑰參數,它們是重新構造公鑰對象所必需的。
// Public key specific parameter.
public static String getMod() throws Exception {
return (new String(Base64.encode(pubKey.getModulus().toByteArray())));
}
// General key parameter. pubExp is the same as pubKey.getExponent()
public static String getPubExp() throws Exception {
return (new String(Base64.encode(pubExp.toByteArray())));
}
通過使用生成的私鑰,實用程序類 RSASigUtil
可以從摘要獲取一個字節數組 RSA 簽名:
static public String getSignature (String mesg) throws Exception {
SHA1Digest digEng = new SHA1Digest();
RSAEngine rsaEng = new RSAEngine();
PSSSigner signer = new PSSSigner(rsaEng, digEng, 64);
signer.init(true, privKey);
byte [] sig = signer.generateSignature( mesg.getBytes() );
String result = new String( Base64.encode(sig) );
return result;
}
服務器將摘要、簽名和密鑰參數編碼成 ASCII 文本格式並以 XML 數字簽名格式嵌入該文本:
<SignedMesg>
<mesg>Hello World</mesg>
<Signature>
<SignedInfo>
<SignatureMethod Algorithm="rsa-sha1" />
<DigestValue>Ck1VqNd45QIvq3AZd8XYQLvEhtA=</DigestValue>
</SignedInfo>
<SignatureValue>
IhJ/UMitJX7sWbzhnG8UKIdDYiZ0mfOUoAwemGiG08C
WcQ3cUszgJXoIclHW/LN7w54w2FQyLStB+hPKASEC6r
OjjgTBs6pwhjHCh2XxWx7hS7fdi9/Qk/ybH6xYGaeaZ
3oHDBjFz3hEDtrvBYcHn3keCavncE22idRX7kBl8Do=
</SignatureValue>
<KeyInfo>
<KeyValue>
<RSAKeyValue>
<Modulus>
AKT1SyxSm4uT1zYWEPY9IaFY7vDhpkIM7FZeIQ
OGnKeSEE5d3sPfONkCiHfO2oe4x6jNCXg/ngRi
tmixBkjfKgHzF4trZZtNQZjfzAgcXGljzp9MD2
ZEWQbHKvMZvZyJVrT2SlxLzusxWLwXdacprIDG
bqDAmldBOBpkmrUdPpF9
</Modulus>
<Exponent>EQ==</Exponent>
</RSAKeyValue>
</KeyValue>
</KeyInfo>
</Signature>
</SignedMesg>
驗證 MIDP 應用程序從 XML 文檔解析出摘要、密鑰參數和簽名,重新構造公鑰並使用下列方法來驗證簽名:
static public boolean verify (String mesg, String signature,
String mod, String pubExp) {
BigInteger modulus = new BigInteger( Base64.decode(mod) );
BigInteger exponent = new BigInteger( Base64.decode(pubExp) );
SHA1Digest digEng = new SHA1Digest();
RSAEngine rsaEng = new RSAEngine();
RSAKeyParameters pubKey = new RSAKeyParameters(false, modulus, exponent);
PSSSigner signer = new PSSSigner(rsaEng, digEng, 64);
signer.init(false, pubKey);
boolean res = signer.verifySignature( mesg.getBytes(),
Base64.decode(signature) );
return res;
}
我的測試說明了無線設備上的 XML 解析和摘要生成都非常快。正如預料的那樣,主要的性能瓶頸是公鑰算法的速度很慢。
Bouncy Castle Crypto 包提供幾個使用 DSA、RSA 和 ECC 算法的簽名引擎類來簽署和驗證消息。但在實際設備中,它們並不都是實用的。因為 Bouncy Castle Crypto 包完全基於Java 語言,所以它在沒有特殊優化的情況下依靠很慢的 JVM 來執行甚至是最密集的大整數數學運算。
結果,只有 RSA 算法提供了一個合理的性能,而且是勉強接受的。它只要花一分多鐘就源碼可以在 16MHz Palm VII 設備上驗證具有 1024 位公鑰的簡單數字簽名。通過選擇較弱的密鑰可以改進性能。但雖然如此,驗證進程在任何實際的應用程序中還是必須作為後台線程運行,以避免用戶界面鎖定。
DSA 和 ECC 算法性能在其當前實現中是完全不可接受的。具有 1024 位密鑰的 DSA 簽名和具有 192 位密鑰的 ECC 簽名要花一個多小時在標准的 Palm VII MIDP 上進行驗證。
性能問題強烈暗示我們需要為大整數數學運算和公鑰算法對 JVM 進行優化。JVM 還必須利用可用的特殊硬件和底層的 OS 功能來促進與安全性相關的數學運算。公鑰算法用於安全連接(如 HTTPS)中的信息交換。許多當前的 MIDP VM 可以用合理的性能來支持 HTTPS 協議。MIDP4Palm VM 可以利用 Palm OS 的底層的 inethttps
協議來建立安全連接。可以想象未來的 VM 和核心語言庫不僅優化與安全連接相關的公鑰操作,而且還使優化可用於一般安全性功能(如數字簽名)。
結束語
在本文中,您學習了安全性在無線 Web 服務中的重要性並說明了在無線和 Web 服務端處理 XML 數字簽名的技巧。我使用了 Bouncy Castle Java 密碼術包的純 Java 實現來處理數字簽名。在 Bouncy Castle 提供的所有算法中,只有 RSA 算法提供了無線設備上可勉強接受的性能。然而,未來在 MIDP 運行時環境上的進步可以使數字簽名更易於移動用戶使用。