在java中将x509证书写入PEM格式的字符串?

Posted

技术标签:

【中文标题】在java中将x509证书写入PEM格式的字符串?【英文标题】:Write x509 certificate into PEM formatted string in java? 【发布时间】:2011-03-19 18:53:30 【问题描述】:

是否有一些高级方法可以将 X509Certificate 写入 PEM 格式的字符串? 目前我正在执行 x509cert.encode() 将其写入 DER 格式的字符串,然后对其进行 base 64 编码并附加页眉和页脚以创建 PEM 字符串,但这似乎很糟糕。特别是因为我也必须换行。

【问题讨论】:

【参考方案1】:

这还不错。 Java 不提供任何函数来编写 PEM 文件。你正在做的是正确的方法。甚至 KeyTool 也做同样的事情,

BASE64Encoder encoder = new BASE64Encoder();
out.println(X509Factory.BEGIN_CERT);
encoder.encodeBuffer(cert.getEncoded(), out);
out.println(X509Factory.END_CERT);

如果你使用 BouncyCastle,你可以使用 PEMWriter 类在 PEM 中写出 X509 证书。

【讨论】:

PEMWriter 现已弃用。 JcaPEMWriter 是替代它的较新类。 你能包含out的声明吗?【参考方案2】:

还没有看到有人提出 Java 8 的 Base64.getMimeEncoder 方法 - 实际上允许您像这样指定行长度 行分隔符:

final Base64.Encoder encoder = Base64.getMimeEncoder(64, LINE_SEPARATOR.getBytes());

我查看了这个 ^ 与标准编码器是否有任何区别,但我找不到任何东西。 javadoc 引用 RFC 2045 用于 BASIC 和 MIME 编码器,并添加了 RFC 4648 用于 BASIC。 AFAIK 这两个标准都使用相同的 Base64 字母表(表格看起来相同),因此如果您需要指定行长,则应该使用 MIME。

这意味着在 Java 8 中,这可以通过以下方式完成:

import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.util.Base64;

...

public static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----";
public static final String END_CERT = "-----END CERTIFICATE-----";
public final static String LINE_SEPARATOR = System.getProperty("line.separator");

...

public static String formatCrtFileContents(final Certificate certificate) throws CertificateEncodingException 
    final Base64.Encoder encoder = Base64.getMimeEncoder(64, LINE_SEPARATOR.getBytes());

    final byte[] rawCrtText = certificate.getEncoded();
    final String encodedCertText = new String(encoder.encode(rawCrtText));
    final String prettified_cert = BEGIN_CERT + LINE_SEPARATOR + encodedCertText + LINE_SEPARATOR + END_CERT;
    return prettified_cert;

【讨论】:

调用需要 API 级别 26。【参考方案3】:

上一个答案给出了与 3de 方软件(如 php)的兼容性问题,因为 PEM 证书未正确分块。

进口:

import org.apache.commons.codec.binary.Base64;

代码:

protected static String convertToPem(X509Certificate cert) throws CertificateEncodingException 
 Base64 encoder = new Base64(64);
 String cert_begin = "-----BEGIN CERTIFICATE-----\n";
 String end_cert = "-----END CERTIFICATE-----";

 byte[] derCert = cert.getEncoded();
 String pemCertPre = new String(encoder.encode(derCert));
 String pemCert = cert_begin + pemCertPre + end_cert;
 return pemCert;

【讨论】:

OpenSSL一般需要64个字符的行。【参考方案4】:

以下不使用大型外部库或可能版本不一致的 sun.* 库。它建立在 judoman 的答案之上,但它也按照 OpenSSL、Java 和其他人的要求将行分成 64 个字符。

进口:

import javax.xml.bind.DatatypeConverter;
import java.security.cert.X509Certificate;
import java.io.StringWriter;

代码:

public static String certToString(X509Certificate cert) 
    StringWriter sw = new StringWriter();
    try 
        sw.write("-----BEGIN CERTIFICATE-----\n");
        sw.write(DatatypeConverter.printBase64Binary(cert.getEncoded()).replaceAll("(.64)", "$1\n"));
        sw.write("\n-----END CERTIFICATE-----\n");
     catch (CertificateEncodingException e) 
        e.printStackTrace();
    
    return sw.toString();

(我本来只是对 judoman 的回答发表评论,但我没有足够的声望点可以发表评论,并且我的简单编辑被拒绝,因为它应该是评论或答案,所以这里是答案。 )

如果你想直接写入文件,也可以import java.io.FileWriter 和:

FileWriter fw = new FileWriter(certFilePath);
fw.write(certToString(myCert));
fw.close();

【讨论】:

不幸的是 javax.xml.bind 已在 Java 11 中删除。现在最好的选择似乎是使用org.apache.commons.codec.binary.Base64 中的Base64.encodeBase64String 而不是printBase64Binary【参考方案5】:

如果您有充气城堡的 PEMWriter,那么您可以执行以下操作:

进口:

import org.bouncycastle.openssl.PEMWriter;

代码:

/**
 * Converts a @link X509Certificate instance into a Base-64 encoded string (PEM format).
 *
 * @param x509Cert A X509 Certificate instance
 * @return PEM formatted String
 * @throws CertificateEncodingException
 */
public String convertToBase64PEMString(Certificate x509Cert) throws IOException 
    StringWriter sw = new StringWriter();
    try (PEMWriter pw = new PEMWriter(sw)) 
        pw.writeObject(x509Cert);
    
    return sw.toString();

【讨论】:

它现在将返回空字符串,为了解决这个问题,在写入对象“pw.flush()”之后添加这个。【参考方案6】:

要基于 ZZ Coder 的想法,但不使用不能保证在 JRE 版本之间保持一致的 sun.misc 类,请考虑这一点

使用类:

import javax.xml.bind.DatatypeConverter;

代码:

try 
    System.out.println("-----BEGIN CERTIFICATE-----");
    System.out.println(DatatypeConverter.printBase64Binary(x509cert.getEncoded()));
    System.out.println("-----END CERTIFICATE-----");
 catch (CertificateEncodingException e) 
    e.printStackTrace();

【讨论】:

没有一个 sun.* 类是为了稳定:oracle.com/technetwork/java/faq-sun-packages-142232.html 谢谢 pimlottc。我删除了 sun.security.provider.X509Factory 字段引用并将它们替换为字符串值。【参考方案7】:

几乎和@Andy Brown一样少了一行代码。

StringWriter sw = new StringWriter();

try (JcaPEMWriter jpw = new JcaPEMWriter(sw)) 
    jpw.writeObject(cert);


String pem = sw.toString();

正如他所说,PEMWriter 已被弃用。

【讨论】:

【参考方案8】:

使用Guava's BaseEncoding 进行编码的另一种选择:

import com.google.common.io.BaseEncoding;

public static final String LINE_SEPARATOR = System.getProperty("line.separator");
public static final int LINE_LENGTH = 64;

然后:

String encodedCertText = BaseEncoding.base64()
                                     .withSeparator(LINE_SEPARATOR, LINE_LENGTH)
                                     .encode(cert.getEncoded());

【讨论】:

还需要添加-----BEGIN CERTIFICATE----------END CERTIFICATE----- 行。 谢谢!经过 24 小时的搜索,你救了我的命【参考方案9】:

在 BouncyCastle 1.60 中,PEMWriter 已被弃用,取而代之的是 PemWriter

StringWriter sw = new StringWriter();

try (PemWriter pw = new PemWriter(sw)) 
  PemObjectGenerator gen = new JcaMiscPEMGenerator(cert);
  pw.writeObject(gen);


return sw.toString();

PemWriter 被缓冲,因此您需要在访问构造它的写入器之前刷新/关闭它。

【讨论】:

【参考方案10】:

使用我在下面制作的一些微小的 Base64 不依赖于其他东西,如 base64 或 bouncycastle。

import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
class Convert 
    private static byte[] convertToPem(X509Certificate cert)
        throws CertificateEncodingException 
        String cert_begin = "-----BEGIN CERTIFICATE-----\n";
        String end_cert = "-----END CERTIFICATE-----\n";
        String b64 = encode(cert.getEncoded()).replaceAll("(.64)", "$1\n");
        if (b64.charAt(b64.length() - 1) != '\n') end_cert = "\n" + end_cert;
        String outpem = cert_begin + b64 + end_cert;
        return outpem.getBytes();
    

    // Taken from https://gist.github.com/EmilHernvall/953733
    private static String encode(byte[] data) 
        String tbl = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
        StringBuilder buffer = new StringBuilder();
        int pad = 0;
        for (int i = 0; i < data.length; i += 3) 
            int b = ((data[i] & 0xFF) << 16) & 0xFFFFFF;
            if (i + 1 < data.length) b |= (data[i+1] & 0xFF) << 8;
            else pad++;
            if (i + 2 < data.length) b |= (data[i+2] & 0xFF);
            else pad++;
            for (int j = 0; j < 4 - pad; j++, b <<= 6) 
                int c = (b & 0xFC0000) >> 18;
                buffer.append(tbl.charAt(c));
            
        
        for (int j = 0; j < pad; j++) buffer.append("=");
        return buffer.toString();
    

【讨论】:

replaceAll("(.64)", "$1\n"); +1! @RobAu 我已经从this那里得到了这条线【参考方案11】:

BouncyCastle 这是一种方法:

// You may need to add BouncyCastle as provider:
public static init() 
    Security.addProvider(new BouncyCastleProvider());


public static String encodePEM(Certificate certificate) 
    return encodePEM("CERTIFICATE", certificate.encoded);

public static String encodePEM(PrivateKey privateKey) 
    return encodePEM("PRIVATE KEY", privateKey.encoded);

public static String encodePEM(PublicKey publicKey) 
    return encodePEM("PUBLIC KEY", publicKey.encoded);
    
/**
 * Converts byte array to PEM
 */
protected static String toPEM(String type, byte[] data) 
  final PemObject pemObject = new PemObject(type, data);
  final StringWriter sw = new StringWriter();
  try (final PemWriter pw = new PemWriter(sw)) 
    pw.writeObject(pemObject);
  
  return sw.toString();

【讨论】:

以上是关于在java中将x509证书写入PEM格式的字符串?的主要内容,如果未能解决你的问题,请参考以下文章

Java X509 证书解析和验证

如何使用PEM格式的公钥创建证书对象?

DER 和 PEM 格式

DER 和 PEM 格式

如何将公钥从字符串类型转换为 PEM

如何在Java中检索/计算X509证书的指纹?