随着移动商业的不断发展,对于移动用户和无线应用程序开发人员而言,安全性正在成为一个重要方面。无线通信是无线电波拦截容易获取的目标,而无线设备几乎没有任何计算能力来支持所有通信数据的强加密。而目前开发得很好的点对点安全性技术(如 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 是一种用于 Java 平台的开放源码的轻量级密码术包。它支持大量的密码术算法,并提供 JCE 1.2.1 的实现。因为 Bouncy Castle 被设计成轻量级的,所以从 J2SE 1.4 到 J2ME(包括 MIDP)平台,它都可以运行。它是在 MIDP 上运行的唯一完整的密码术包。
不管 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分钟左右。
l The Bouncy Castle project
http://www.bouncycastle.org/
l j2me中文教程
J2ME开发网(www.j2medev.com)
l Data security in mobile Java applications
http://www.javaworld.com/javaworld/jw-12-2002/jw-1220-wireless.html?page=1