仅知IP地址,如何用Java获取HTTP服务器证书的CN与SAN?
通过IP地址用Java查询HTTPS服务器证书的通用名称与主题备用名称
假设已知某服务器的IP地址为1.2.3.4(示例虚构地址),通过在线工具查询该IP对应的证书信息,结果示例如下:
Common Name = fqdn.org Organization = somebody City/Locality = Somewhere State/Province = Somestate Country = US Subject Alternative Names = www.fqdn.org, another.fqdn.org ....
可以通过Java自带的SSL相关API直接建立到目标IP的SSL连接,获取并解析服务器证书,无需依赖外部工具。以下是简便实现方案:
核心实现思路
- 建立到目标IP指定端口(默认443)的SSL Socket连接
- 临时跳过主机名验证(因用IP连接时,证书域名可能与IP不匹配,仅适用于测试场景,生产环境需谨慎)
- 从连接中提取服务器证书链的第一个证书(即服务器端证书)
- 解析证书的通用名称(Common Name)和主题备用名称(Subject Alternative Names)
完整代码示例
import javax.net.ssl.*; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; public class SslCertificateChecker { public static void main(String[] args) { String targetIp = "1.2.3.4"; int targetPort = 443; try { // 初始化SSL上下文与Socket工厂 SSLContext sslContext = SSLContext.getDefault(); SSLSocketFactory socketFactory = sslContext.getSocketFactory(); // 创建并连接SSL Socket SSLSocket sslSocket = (SSLSocket) socketFactory.createSocket(targetIp, targetPort); // 禁用主机名验证(仅测试用,生产环境请勿保留) HostnameVerifier skipHostVerify = (hostname, session) -> true; HttpsURLConnection.setDefaultHostnameVerifier(skipHostVerify); // 触发SSL握手 sslSocket.startHandshake(); // 获取服务器证书,取证书链第一个元素 Certificate[] certChain = sslSocket.getSession().getPeerCertificates(); X509Certificate serverCert = (X509Certificate) certChain[0]; // 解析通用名称(CN) String commonName = extractCommonName(serverCert); System.out.println("通用名称(Common Name): " + commonName); // 解析主题备用名称(SAN) List<String> sanList = extractSubjectAlternativeNames(serverCert); System.out.println("主题备用名称(Subject Alternative Names): " + String.join(", ", sanList)); // 关闭连接 sslSocket.close(); } catch (Exception e) { e.printStackTrace(); } } // 从证书SubjectDN中提取通用名称 private static String extractCommonName(X509Certificate cert) { String subjectDn = cert.getSubjectX500Principal().getName(); Pattern cnPattern = Pattern.compile("CN=([^,]+)"); Matcher matcher = cnPattern.matcher(subjectDn); return matcher.find() ? matcher.group(1) : null; } // 提取主题备用名称中的DNS条目 private static List<String> extractSubjectAlternativeNames(X509Certificate cert) { List<String> sanList = new ArrayList<>(); byte[] sanExtension = cert.getExtensionValue("2.5.29.17"); // SAN对应的OID标识 if (sanExtension == null) { return sanList; } // 解析DER编码的SAN扩展内容 int offset = 2; // 跳过序列长度字段 int sanCount = (sanExtension[offset] & 0xFF) << 8 | (sanExtension[offset + 1] & 0xFF); offset += 2; for (int i = 0; i < sanCount; i++) { int tag = sanExtension[offset] & 0xFF; if (tag == 2) { // 仅提取DNS类型的SAN条目 int length = sanExtension[offset + 1] & 0xFF; offset += 2; String dnsName = new String(sanExtension, offset, length); sanList.add(dnsName); offset += length; } else { // 跳过非DNS类型的条目(如IP、邮箱等) int length = sanExtension[offset + 1] & 0xFF; offset += 2 + length; } } return sanList; } }
注意事项
- 安全风险提示:代码中禁用主机名验证的逻辑仅用于快速测试,生产环境中必须替换为符合业务安全要求的
HostnameVerifier实现,避免中间人攻击风险。 - 异常处理:实际使用时需补充连接超时、证书信任异常等场景的捕获与处理,提升代码健壮性。
- 端口适配:默认使用HTTPS标准443端口,若目标服务器使用自定义端口,需修改
targetPort参数。
内容的提问来源于stack exchange,提问作者Bitbang3r
相关产品推荐
相关产品推荐

