基于 Java 的 Web 服务和无线 Java 开发是 JavaOne 2002 的两个最突出的主题。它们代表普及计算领域中未来的后端和前端 Java 技术。
虽然基于 Java 的无线 Web 服务在普及移动商业世界中有一个光明的前途,但当前技术仍不成熟。安全性仍是剩下待解决的问题之一。无线通信是无线电波拦截容易获取的目标,而无线设备几乎没有任何计算能力来支持所有通信数据的强加密。此外,在后端,Web 服务运行在企业防火墙之外并使用开放消息传递协议来彼此交互。无线 Web 服务同样是易招受各种破解攻击的目标。已开发得很好的点对点安全性技术(如 SSL/TLS 和 HTTPS)不适合于多供应商、多中间 Web 服务网络拓扑图 — 重点必需集中在保护内容本身而不是传递内容的连接上。尽管面临新挑战,然而 Web 服务本身还是能用来增强移动商业安全性。新兴的 Web 服务安全性规范的出现使您能够将 Web 服务用作安全性实用程序。
让我们假设一下,您是一位股票交易者,当您不在交易大厅时,使用蜂窝电话来跟踪股票价格变化。在上下班途中,您的电话提醒您正在监视的一只股票的价格已经跌入您的心理最低价。现在,您应根据此提示买进它并利用此低价来赚一笔吗?在您采取任何行动之前,您必须绝对确保提示本身是可信的。如果竞争者可能拦截并更改消息(例如,更改股票代码),那么他就可能引诱您买进弄错的股票,并将他的高价位股票抛给您。您如何知道消息在从监视服务到您电话的途中没有被篡改呢?
正如我早先提到的那样,XML 正成为 Web 服务世界中一个主要的数据交换协议。驱动 Web 服务的 XML 消息在到达目的地之前,通常需要经过多个中间环节。因此,我们保护从端到端的通信内容是重要的。完成这一任务的最好方法是,将 XML 文档及其安全性信息(如签名、摘要和密钥等等)都装运在一起,作为单个文档。
下的单独 XML 元素中,以代替使用编码的公钥证书。这在密钥和处理它们的 Java 代码之间建立了更明显的连接。
IBM alphaWorks 开发了一种称为 XML Security Suite 的 Java 包,它支持最新的 XML 数字签名规范。JSR 105 是标准化一组 Java API 以处理 XML 数字签名的一项社区成果。然而,它们仅作用于 Java 2 标准版(J2SE),这意味着可以在服务器端上使用 XML Security Suite 或 JSR 105 Java XML 数字签名 API,但不能在 MIDP 无线设备端上使用它们。
签署消息并验证签名 。这些功能需要一个不属于当前 MIDP 1.0 规范的密码术 API。
在下一节中,我将讨论一种轻量级 Java 密码术包,您可以在服务器端和无线 MIDP 设备端上使用它来生成并验证 XML 数字签名。
Bouncy Castle 是一种用于 Java 平台的开放源码的轻量级密码术包。它支持大量的密码术算法,并提供 JCE 1.2.1 的实现。因为 Bouncy Castle 被设计成轻量级的,所以从 J2SE 1.4 到 J2ME(包括 MIDP)平台,它都可以运行。它是在 MIDP 上运行的唯一完整的密码术包。
到目前为止,我们已经讨论了许多技术和概念。下面,我将说明完整过程:密钥生成、在服务器端签署文档、以安全的 XML 格式编码和传送文档以及在客户机端验证文档。
访问 JSP 页时,服务器计算响应消息的摘要。
然后,JSP 页以“签署”方式调用签名引擎,并使用私钥为摘要生成数字签名。
服务器将签名信息(包括摘要、数字签名本身和公钥参数)嵌入 XML 响应消息中。
客户机接收 XML 文档并将摘要、数字签名和公钥参数解析成 Java 应用程序数据。
客户机计算来自明文消息的摘要并将它与服务器的摘要进行比较。如果两个摘要不匹配,则文档验证失败;如果它们匹配,则转至下一步。
客户机使用嵌入的密钥参数重新构造公钥。
客户机以“验证”方式调用签名引擎,并传递摘要、签名和公钥来验证签名。
在接下来的几节中,我们将遵循这些步骤来实现几个示例。因为我们的示例在服务器端和客户机端使用相同的 Bouncy Castle Crypto API,所以很容易更改它们以在无线设备上签署消息并在服务器端验证它。
正如我早先提到的那样,为了改进性能并避免攻击造成堵塞,您实际上签署的是消息摘要而不是消息本身。清单 1 说明了如何使用 SHA1Digest
摘要引擎来计算来自一段文本消息的已编码的摘要。
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)));
}
方法 DSASigUtil.generateKeys()
生成密钥对。正如我讨论过的那样,这个步骤通常由中央认证中心在脱机状态下完成,如清单 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();
来描述,并且用 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())));
}
可以从摘要获取两部分 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;
}
<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>
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 所示:
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");
方法使用清单 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();
来描述,并且用 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)));
}
可以从摘要获取两部分 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;
}
被记录为 QQ
,以将它与相应 XML 元素中的密钥参数 q
区分开来,如清单 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>
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 );
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);
方法使用 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();
来描述,并且用 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())));
}
可以从摘要获取一个字节数组 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;
}
<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>
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);
Base64.decode(signature) );
return res;
}
我的测试说明了无线设备上的 XML 解析和摘要生成都非常快。正如预料的那样,主要的性能瓶颈是公钥算法的速度很慢。
协议来建立安全连接。可以想象未来的 VM 和核心语言库不仅优化与安全连接相关的公钥操作,而且还使优化可用于一般安全×××(如数字签名)。