X509 证书共享标准(java servlet)

Standard for X509 certificate sharing (java servlet)

我正在尝试使用 servlet 和 Apache Tomcat 开发一个简单的 Web 服务,其中客户端和服务器交换它们的 public 密钥;这是为了在进一步的通信阶段发送一些签名数据。

我在互联网上了解到,共享 public 密钥的最佳方式是通过 X509 证书来证明其可靠性/完整性,对吗?

我已经使用 Bouncy Castle 和 JCE 生成了密钥和自签名的 X509 证书,这两个证书必须在双方之间交换。在这里,我在 PEM、DER 等不同格式和共享证书字节的建议之间迷失了方向(即通过封装在 ObjectOutputStream 中的 ByteArrayOutputStream 传输证书对象)。这样做的正确方法是什么?我应该如何编码?

旁注:该软件仅供学习使用,不适合实际使用,因此可以像 "the connection is secure" 那样做大假设。这就是为什么我宁愿保持简单而不实施 SSL 的原因。但是,如果确实有必要,我可以尝试使用它。

谢谢。

编辑: 这就是我解决问题的方法,感谢@Lothar

X509Certificate taCert = Certificates.getCertificate();
StringWriter sw = new StringWriter();
JcaPEMWriter pw = new JcaPEMWriter(sw);
pw.writeObject(taCert);
pw.flush();
String pemData = sw.toString();
pw.close();
out.println(pemCert);

函数 getCertificate() 使用 BouncyCastle 生成 X509Certificate,生成的 pemData 是这样的:

-----BEGIN CERTIFICATE-----
MIIBwDCCASmgAwIBAgIGAWIe66NnMA0GCSqGSIb3DQEBCwUAMBkxFzAVBgNVBAMT
DlRydXN0QXV0aG9yaXR5MB4XDTE4MDMxMjEwMzMwMloXDTE4MDMzMDExMTMzMFow
GTEXMBUGA1UEAxMOVHJ1c3RBdXRob3JpdHkwgZ8wDQYJKoZIhvcNAQEBBQADgY0A
MIGJAoGBAMT2Y7iU8dWrqDzciR64HPuXOTEsf/90cLF0hickYvJULHFJ90Z1PXxA
Um/WJ5vX4m6+ESmurrFmClyeLMfThgHBlYfBfKSNYzIB1M0NXoe8znaCP9U+WKy7
HdkRvvenJPRx8mqWCcWH1nBPI5SehppgWxWZzYD4BTIQ+ILwdm8fAgMBAAGjEzAR
MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADgYEATUmc3BfItRc3UObi
qxdzeN32ZEiLsoywan8qDZyxLFJWf8HhLFq2KOFht6+PatAh1SmcZ67Iw+2thb/0
ptuRE1MetpZnY3M++afv5HRrWm1k52sZYKkYtlfAXlzZuNDPm7lDQzSyS/0IhvjC
32bwhNiajLcc0mLqPQCEMd5oi5U=
-----END CERTIFICATE-----

通过 response.getWriter() 提供的 PrintWriter 以 Content-Type text/plain; charset=utf-8 发送。客户端,通过HttpURLConnection urlConnection接收响应,并通过InputStream in = urlConnection.getInputStream();读取。更具体地说,

InputStream in = urlConnection.getInputStream();
Scanner scanner = new Scanner(in);
scanner.useDelimiter("\A");

boolean hasInput = scanner.hasNext();
if (hasInput)
    return scanner.next();
else
    return null;

要创建 X509Certificate,我使用 CertificateFactory,将 contactServerResult 视为从服务器接收的字符串:

CertificateFactory fact = CertificateFactory.getInstance("X.509");
certificate = (X509Certificate) fact.generateCertificate(new ByteArrayInputStream(contactServerResult.getBytes(Charset.forName("UTF-8"))));

最后我在重建收到的证书时没有使用 PEMParser,因为它返回了 X509CertificateHolder 而不是 X509Certificate,导致 ClassCastException .

大多数(如果不是全部)加密库支持的格式是 DER。如果你有一个 X.509 实例,你只需调用 getEncoded 就可以了。 PEM 也得到广泛支持,但您需要一个解析器(例如 BouncyCastle 中的PEMParser)才能再次从中创建证书。

我不推荐使用 DataOutputStream 序列化对象。您序列化的对象取决于创建证书时涉及的加密提供程序。如果另一方没有此提供程序可用,反序列化将失败。如果另一端不是 Java-based,则数据流完全没用,因为它是 java 特定的。

为了回答您的问题,如果我们讨论的是 DER 格式,如何对其进行编码:

发送证书(假设在 servlet 中):

response.getOutputStream().write(certificate.getEncoded());

根据收到的数据创建证书(使用 BouncyCastle 作为提供者):

byte[] data = getDataFromSomewhere();
CertificateFactory cf = CertificateFactory.getInstance("X.509", "BC");
X509Certificate x509 = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(data));

为了好玩,对 PEM 做同样的事情:

发送中:

StringWriter sw = new StringWriter();
JcaPEMWriter pw = new JcaPEMWriter(sw);
pw.writeObject(cert);
pw.flush();
String pemData = sw.toString();

接收:

String data = getDataFromSomewhere();
PEMParser pr = new PEMParser(new StringReader(data));
X509Certificate x509 = (X509Certificate) pr.readObject();

所有示例均已简化,例如假设有数据要读取并且数据实际上代表 X.509 证书。还有其他东西可以是 DER- 或 PEM-encoded,因此您可能会在解析或进行转换时遇到异常,因为返回的对象不是证书而是 CSR 等。