隨著移動商業的不斷發展,對於移動用戶和無線應用程序開發人員而言,安全性正在成為一個重要方面。無線通信是無線電波攔截容易獲取的目標,而無線設備幾乎沒有任何計算能力來支持所有通信數據的強加密。而目前開發得很好的點對點安全性技術(如 SSL/TLS 和 HTTPS)並不適合於多供應商、多中間 Web 服務的網絡拓撲圖。因此重點必須集中在保護內容本身而不是傳遞內容的連接上。
本文將討論使用一種常見的安全性技術:數字簽名。數字簽名可以滿足網絡通訊安全的四方面標准:
可認證性:通信雙方必須標識其本身。公鑰證書上的數字簽名可以驗證該公鑰的可靠性以及持有它的那一方的可靠性。
數據完整性:通信雙方必須確保內容在傳送期間不被改變。數字簽名是保證數據完整性的最常用技術。
數據機密性:有時候,通信數據是敏感的,必須保密。數字簽名不提供數據機密性。我們必須使用數據加密。
不可抵賴性:消息發送之後,發送方隨後應該不能否認它。數字簽名提供了部分解決方案。如果以數字方式對消息進行簽名,則發送方無法否認其責任,因為只有他能提供這種簽名。
J2ME 平台是由配置(Configuration)和簡表(Profile)構成的。配置是提供給最大范圍設備使用的最小類庫集合,在配置中同時包含Java 虛擬機。簡表是針對一系列設備提供的開發包集合。在J2ME 中還有一個重要的概念是可選包(Optional Package),它是針對特定設備提供的類庫。
目前,J2ME 中有兩個最主要的配置,分別是Connected Limited Devices Configuration(CLDC)和Connected Devices Configuration(CDC)。他們是根據設備的硬件性能進行區分的,例如處理器、內存容量等。CLDC 主要針對那些資源非常受限的設備比如手機、PDA、雙工尋呼機等。而CDC 主要面對那些家電產品,比如機頂盒、汽車導航系統等。簡表是以配置為基礎的,例如Mobile Information Devices Profile(MIDP)就是CLDC 上層的重要簡表。
CLDC規范定義了3個級別的安全機制:底層安全機制,應用級別安全機制和端對端的安全機制。在這裡有一點需要強調的是字節碼驗證過程。JVM 提供了防止惡意代碼進入企業系統的服務,字節碼驗證過程保證了應用程序不能訪問內存空間或使用其域外的資源。字節碼驗證還防止應用程序重載 Java 語言核心庫,這是一種可以用來繞過其它應用程序級安全性措施的方法。但是,由於這種操作高昂的計算開銷,MIDP VM 不在運行時執行完整的字節碼驗證,而是增加了預審和機制。要求應用程序開發人員必須在把應用程序部署到移動設備中之前,在開發平台上預先驗證類。預驗證過程優化執行流,創建應用程序中包含指令目錄的堆棧映射(stackmap),然後將堆棧映射添加到經預驗證的類文件。在運行時,MIDP VM 迅速地對字節碼進行線性掃描,將每個有效的指令與合適的堆棧映射項相匹配。
此外在MIDP2.0中規定了許可和保護域概念。應用程序通過對敏感API 提出許可申請來試圖獲得相應的權限。提供了信任域與非信任域,不同的設備提供的保護域可能是不同的,一般我們開發的MIDlet都是存放到非信任域的。如果想成為可信任的MIDlet需要想一個可信任的組織提出認證申請。
關於更多詳細的內容可以參看www.J2MEdev.com撰寫的《J2ME中文教程》,裡面有對J2ME安全機制的詳細介紹。
不管 Bouncy Castle 包的功能有多強大,它有一個主要問題:缺少文檔。不存在在線文檔,其 JavaDoc 寫得並不好。與許多其它高級密碼術包相似,Bouncy Castle 包廣泛使用類型多態性來將常規概念與實現算法分開。對於初學者來說,辨認類之間的關系以及方法參數和返回值的正確類型是很困難的。通常,開發人員必須浏覽一下源代碼和測試用例來研究做事的正確方法。
由於移動設備自身的特點,對應用程序大小要求比較嚴格。建議在使用Bouncy Castle包時只把需要的源代碼引入到自己的工程中,一起編譯打包。在運行前使用混淆器將class文件混淆下。雖然在很多資料介紹中說混淆過程是一個可選過程,但我在實際開發過程中發現如果不進行混淆,運行程序會報錯:Uncaught exception java/lang/NoClassDefFoundError: Java/math/BigInteger: Cannot create class in system package 。使用混淆器的好處是減少class文件的透明度和減少程序的大小。
常見的混淆器見下表(此表數據來源於《J2ME中文教程》):
名稱
地址
特點
JODE
http://jode.sourceforge.Net/
開源
ProGuard
http://proguard.sourceforge.Net/
開源
RetroGuard
http://www.retrologic.com/
開源,中國移動百寶箱
強制使用
DashO
http://www.preemptive.com/
商業軟件,一般專業公
司使用,昂貴
ZKM
http://www.zelix.com/
商業軟件可試用
JBuilder
http://www.borland.com/
集成開發環境中內附混
淆功能,但JBuilder
的價格也不便宜。
使用RSA算法,生成1024位長密鑰對。
public void generateRSAKeyPair () throws Exception {
RSAPrivateCrtKeyParameters RSAprivKey = null;
RSAKeyParameters RSApubKey = null;
SecureRandom sr = new SecureRandom();
BigInteger pubExp = new BigInteger("10001", 16);
RSAKeyGenerationParameters RSAKeyGenPara =
new RSAKeyGenerationParameters(pubExp, sr, 1024, 80);
RSAKeyPairGenerator RSAKeyPairGen = new RSAKeyPairGenerator();
RSAKeyPairGen.init(RSAKeyGenPara);
AsymmetricCipherKeyPair keyPair = RSAKeyPairGen.generateKeyPair();
RSAprivKey = (RSAPrivateCrtKeyParameters) keyPair.getPrivate();
RSApubKey = (RSAKeyParameters) keyPair.getPublic();
}
對字節數組簽名。
public byte[] RSASign(byte[] toSign, CipherParameters RSAprivKey)
throws Exception {
if (RSAprivKey == null)
throw new Exception("Generate RSA keys first!");
SHA1Digest dig = new SHA1Digest();
RSAEngine eng = new RSAEngine();
PSSSigner signer = new PSSSigner(eng, dig, 64);
signer.init(true, RSAprivKey);
signer.update(toSign, 0, toSign.length);
return signer.generateSignature();
}
驗證簽名值。
public boolean RSAVerify(byte[] mesg, byte[] sig, CipherParameters RSApubKey)
throws Exception {
if (RSApubKey == null)
throw new Exception("Generate RSA keys first!");
SHA1Digest dig = new SHA1Digest();
RSAEngine eng = new RSAEngine();
PSSSigner signer = new PSSSigner(eng, dig, 64);
signer.init(false, RSApubKey);
signer.update(mesg, 0, mesg.length);
return signer.verifySignature(sig);
}
對字符串加密,生成密文。
public byte[] RSAEncrypt(String plainText ,CipherParameters RSApubKey)
throws Exception {
byte[] rv = null;
AsymmetricBlockCipher eng = new RSAEngine();
eng.init(true, RSApubKey);
byte[] ptBytes = plainText.getBytes();
rv = eng.processBlock(ptBytes, 0, ptBytes.length);
return rv;
}
對密文解密,生成原文。
public String RSADecrypt(byte[] cipherText, CipherParameters RSAprivKey)
throws Exception {
byte[] rv = null;
AsymmetricBlockCipher eng = new RSAEngine();
eng.init(false, RSAprivKey);
rv = eng.processBlock(cipherText, 0, cipherText.length);
return new String(rv).trim();
}
讀der證書,獲取證書信息。
public void ShowCert(byte[] cert) throws Exception {
ByteArrayInputStream bIn;
ASN1InputStream aIn;
bIn = new ByteArrayInputStream(cert);
aIn = new ASN1InputStream(bIn);
ASN1Sequence seq = null;
seq = (ASN1Sequence) aIn.readObject();
X509CertificateStructure obj = new X509CertificateStructure(seq);
TBSCertificateStructure tbsCert = obj.getTBSCertificate();
int version = tbsCert.getVersion();
String subject = tbsCert.getSubject().toString();
String issuer = tbsCert.getIssuer().toString();
long serial = tbsCert.getSerialNumber().getValue().longValue();
String sign = tbsCert.getSignature().getObjectId().getId();
// X509 Extensions
X509Extensions ext = tbsCert.getExtensions();
if (ext != null) {
Enumeration en = ext.oids();
while (en.hasMoreElements()) {
DERObjectIdentifier oid = (DERObjectIdentifIEr) en
.nextElement();
X509Extension extVal = ext.getExtension(oid);
}
}
}
Bouncy Castle功能強大,支持大量的密碼算法。特別是提供在MIDP上對證書應用的處理接口,能夠滿足在移動設備上證書應用的需求。不足之處是其文檔太過簡單,對類之間關系和參數含義理解困難,需要去閱讀其原碼。在性能方面,主要的瓶頸是公鑰算法的速度比較慢。我在WTK2.5上生成1024位的RSA密鑰需要用時2分鐘左右。