保护您的 java/j2me/ mobile/j2me/ J2ME/MIDP 应用程序(2)
处理 MIDP 应用程序中的 XML 数字签名
IBM alphaWorks 开发了一种称为 XML Security Suite 的 Java 包,它支持最新的 XML 数字签名规范。JSR 105 是标准化一组 Java API 以处理 XML 数字签名的一项社区成果。然而,它们仅作用于 Java 2 标准版(java/j2se/ J2SE),这意味着可以在/server/ 服务器端上使用 XML Security Suite 或 JSR 105 Java XML 数字签名 API,但不能在 MIDP 无线设备端上使用它们。
要处理 XML 数字签名,正在使用的无线设备需要支持下列功能:
- 对 XML 文档读写数据 。在示例 MIDP 应用程序中,kXML 解析器将 XML 文档和元素解析成 Java 对象(请参阅 参考资料 )。除了 kXML 外,还有几个可在不同许可证条款下使用的其它 MIDP XML 解析器。
- 签署消息并验证签名 。这些功能需要一个不属于当前 MIDP 1.0 规范的密码术 API。
在下一节中,我将讨论一种轻量级 Java 密码术包,您可以在服务器端和无线 MIDP 设备端上使用它来生成并验证 XML 数字签名。
安全移动代码中的数字签名
数字签名不仅有助于保护应用程序数据通信,它们还有助于保护应用程序本身。无线设备常常需要从无线网络下载应用程序来动态调整自身,以使其适合新环境和任务。怀有敌意的各方可能拦截移动代码分发版并将病毒和其它有害的特洛伊代码段插入下载的应用程序中。我们怎么样才能确保移动代码可信呢?移动代码供应商可以用其数字证书对整个 JAR 压缩文档进行数字签名。无线用户可以根据供应商的可信级别来确定该应用程序的安全性域。
Bouncy Castle Crypto API
Bouncy 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 格式编码和传送文档以及在客户机端验证文档。
- 服务器使用一组密钥模型参数生成由一个随机公钥和一个私钥组成的密钥对。在实际生产系统中,这个步骤通常不是必需的,因为密钥对通常是预先生成的并存储在服务器密钥库中。
- 访问 JSP 页时,服务器计算响应消息的摘要。
- 然后,JSP 页以“签署”方式调用签名引擎,并使用私钥为摘要生成数字签名。
- 服务器将签名信息(包括摘要、数字签名本身和公钥参数)嵌入 XML 响应消息中。
- 客户机接收 XML 文档并将摘要、数字签名和公钥参数解析成 Java 应用程序数据。
- 客户机计算来自明文消息的摘要并将它与服务器的摘要进行比较。如果两个摘要不匹配,则文档验证失败;如果它们匹配,则转至下一步。
- 客户机使用嵌入的密钥参数重新构造公钥。
- 客户机以“验证”方式调用签名引擎,并传递摘要、签名和公钥来验证签名。
在接下来的几节中,我们将遵循这些步骤来实现几个示例。因为我们的示例在服务器端和客户机端使用相同的 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)));
} |
在下几节中,我们将了解如何使用 Bouncy Castle 的 DSA、ECC 和 RSA 签名引擎来签署并验证数字签名。那些签名引擎使用不同的算法和不同的密钥,并且需要不同的参数。我们还将讨论如何将安全性信息(签名、摘要和公钥)嵌入 XML 文档。最后,我将比较三个签名引擎并提出今后的改进建议。
DSA 签名示例
方法 DSASigUtil.generateKeys() 生成密钥对。正如我讨论过的那样,这个步骤通常由中央认证中心在脱机状态下完成,如清单 2 所示:
清单 2. 生成密钥对
// Get a secure random source.
SecureRandom sr = new SecureRandom();
// Generate DSA parameters.
Dcorp/sap/ SAParametersGenerator 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 中的下列方法检索模型和密钥参数,它们是重新构造公钥对象所必需的:
清单 3. 检索模型和密钥参数
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 :
清单 4. 检索 DSA 签名
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 所示:
清单 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 文档解析出摘要、密钥参数和签名,重新构造公钥并使用下列方法来验证签名:
清单 6. 验证签名
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;
} |
(未完待续)
|