如何从Java中的X509Certificate中提取CN?

Mar*_* C. 82 java ssl x509certificate x509

我正在使用SslServerSocket和客户端证书,并希望从客户端的SubjectDN中提取CN X509Certificate.

我打电话的那一刻,cert.getSubjectX500Principal().getName()但这当然给了我客户端的格式化DN.出于某种原因,我只对CN=theclientDN 的部分感兴趣.有没有办法提取DN的这一部分而不自己解析String?

Jak*_*kub 91

这是另一种方式.您的想法是您获得的DN是rfc2253格式,与LDAP DN使用的格式相同.那么为什么不重用LDAP API呢?

import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;

String dn = x509cert.getSubjectX500Principal().getName();
LdapName ldapDN = new LdapName(dn);
for(Rdn rdn: ldapDN.getRdns()) {
    System.out.println(rdn.getType() + " -> " + rdn.getValue());
}
Run Code Online (Sandbox Code Playgroud)

  • 注意:虽然它看起来是一个很好的解决方案,但它存在一些问题。我使用这个已经有好几年了,直到我发现“非标准”字段的解码问题。对于具有诸如“CN”(又名“2.5.4.3”)等众所周知类型的字段,“Rdn#getValue()”包含一个“String”。但是,对于自定义类型,结果是“byte[]”(可能基于以“#”开头的内部编码表示)。Ofc,`byte[]` -> `String` 是可能的,但包含额外的(不可预测的)字符。我已经使用基于 BC 的 @laz 解决方案解决了这个问题,因为它在“String”中正确处理和解码了这个问题。 (2认同)

gtr*_*rak 79

以下是新推荐的不推荐使用的BouncyCastle API的代码.你需要bcmail和bcprov发行版.

X509Certificate cert = ...;

X500Name x500name = new JcaX509CertificateHolder(cert).getSubject();
RDN cn = x500name.getRDNs(BCStyle.CN)[0];

return IETFUtils.valueToString(cn.getFirst().getValue());
Run Code Online (Sandbox Code Playgroud)

  • @grak,我对你如何找到这个解决方案很感兴趣.当然,仅仅通过查看API文档,我就无法理解这一点. (8认同)
  • 请注意,此代码(2012年10月23日)BouncyCastle(1.47)也需要bcpkix发布. (7认同)
  • 是的,我有同样的感受......我不得不在邮件列表上询问. (4认同)
  • `IETFUtils.valueToString`似乎没有产生正确的结果.由于base 64编码,我有一个包含一些等号的CN(例如`AAECAwQFBgcICQoLDA0ODw ==`).`valueToString`方法将反斜杠添加回结果.取而代之的是,使用`toString`似乎正在起作用.很难确定这实际上是api的正确用法. (4认同)
  • 一个证书可以有多个CN。您应该迭代所有内容并返回 CN 列表,而不是仅仅返回 cn.getFirst() 。 (2认同)

laz*_*laz 12

如果添加依赖项不是问题,您可以使用Bouncy Castle的 API来处理X.509证书:

import org.bouncycastle.asn1.x509.X509Name;
import org.bouncycastle.jce.PrincipalUtil;
import org.bouncycastle.jce.X509Principal;

...

final X509Principal principal = PrincipalUtil.getSubjectX509Principal(cert);
final Vector<?> values = principal.getValues(X509Name.CN);
final String cn = (String) values.get(0);
Run Code Online (Sandbox Code Playgroud)

更新

在发布时,这是实现此目的的方法.然而,正如gtrak在评论中提到的那样,这种方法现在已被弃用.请参阅gtrak 使用新Bouncy Castle API 的更新代码.


Ivi*_*vin 9

作为gtrak代码的替代品,不需要''bcmail'':

    X509Certificate cert = ...;
    X500Principal principal = cert.getSubjectX500Principal();

    X500Name x500name = new X500Name( principal.getName() );
    RDN cn = x500name.getRDNs(BCStyle.CN)[0]);

    return IETFUtils.valueToString(cn.getFirst().getValue());
Run Code Online (Sandbox Code Playgroud)

@Jakub:我已经使用了你的解决方案,直到我的SW必须在Android上运行.Android没有实现javax.naming.ldap :-(

  • 不确定何时更改,但现在可以正常工作:`X500Name x500Name = new X500Name(cert.getSubjectX500Principal().getName()); String cn = x500Name.getCommonName();`(使用java 8) (8认同)

Coc*_*lla 7

cert.getSubjectX500Principal().getName()如果您不想依赖 BouncyCastle,这里介绍了如何使用正则表达式 over 来完成此操作。

该正则表达式将解析一个专有名称,为每个匹配提供name一个val捕获组。

当 DN 字符串包含逗号时,它们应该被引用 - 此正则表达式可以正确处理带引号和不带引号的字符串,并且还处理带引号的字符串中的转义引号:

(?:^|,\s?)(?:(?<name>[A-Z]+)=(?<val>"(?:[^"]|"")+"|[^,]+))+

这是格式很好的:

(?:^|,\s?)
(?:
    (?<name>[A-Z]+)=
    (?<val>"(?:[^"]|"")+"|[^,]+)
)+
Run Code Online (Sandbox Code Playgroud)

这是一个链接,您可以看到它的实际效果: https ://regex101.com/r/zfZX3f/2

如果您希望正则表达式仅获取CN,那么这个改编版本可以做到这一点:

(?:^|,\s?)(?:CN=(?<val>"(?:[^"]|"")+"|[^,]+))


小智 6

http://www.cryptacular.org一行

CertUtil.subjectCN(certificate);
Run Code Online (Sandbox Code Playgroud)

JavaDoc:http://www.cryptacular.org/javadocs/org/cryptacular/util/CertUtil.html#subjectCN(java.security.cert.X509Certificate )

Maven依赖:

<dependency>
    <groupId>org.cryptacular</groupId>
    <artifactId>cryptacular</artifactId>
    <version>1.1.0</version>
</dependency>
Run Code Online (Sandbox Code Playgroud)


小智 6

无需使用任何库即可获取证书的通用名称。使用正则表达式

为了得到名字

String name = x509Certificate.getSubjectDN().getName();
Run Code Online (Sandbox Code Playgroud)

从全名中提取常用名

    String name = "CN=Go Daddy Root Certificate Authority - G2, O=\"GoDaddy.com, Inc.\", L=Scottsdale, ST=Arizona, C=US";
    Pattern pattern = Pattern.compile("CN=(.*?)(?:,|\$)");
    Matcher matcher = pattern.matcher(name);
    if (matcher.find()) {
        System.out.println(matcher.group(1));
    }
Run Code Online (Sandbox Code Playgroud)

希望这对任何人都有帮助。(-_-)


Abh*_*kar 5

到目前为止发布的所有答案都有一些问题:大多数使用内部X500Name或外部Bounty Castle依赖项.以下内容基于@ Jakub的答案并仅使用公共JDK API,但也提取OP所要求的CN.它也使用Java 8,它在2017年中期,你真的应该.

Stream.of(certificate)
    .map(cert -> cert.getSubjectX500Principal().getName())
    .flatMap(name -> {
        try {
            return new LdapName(name).getRdns().stream()
                    .filter(rdn -> rdn.getType().equalsIgnoreCase("cn"))
                    .map(rdn -> rdn.getValue().toString());
        } catch (InvalidNameException e) {
            log.warn("Failed to get certificate CN.", e);
            return Stream.empty();
        }
    })
    .collect(joining(", "))
Run Code Online (Sandbox Code Playgroud)