Java Development Kit(JDK)自带的keytool工具可在 Java 密钥库内创建密钥对,并将其转换为 CSR(证书签名请求),这是证书颁发机构(CA)在签发证书前用来识别您身份所读取的编码块。对于代码签名而言,keytool 可合法用于哪些用途在 2023 年 6 月 1 日发生了变化。本指南首先说明规则,然后逐步讲解仍然正确可用的 keytool 命令:针对软件密钥库用于内部签名,以及通过 PKCS#11 针对硬件令牌或 HSM 使用。
代码签名密钥必须在硬件上生成
自2023 年 6 月 1 日起,CA/浏览器论坛代码签名基线要求规定,每一张受公众信任的代码签名证书的私钥都必须在符合FIPS 140-2 Level 2、Common Criteria EAL4+或同等标准的硬件加密模块中生成并存储。该密钥必须不可导出。这一规定既适用于标准(组织验证)证书,也适用于扩展验证证书。EV 代码签名一直都要求使用硬件;2023 年的变更将同样的规则扩展到了标准证书。同一份要求还规定代码签名证书的最小密钥长度为RSA 3072,该规定自 2021 年 6 月 1 日起生效。
这对本页内容的影响是直接的。keytool 在您的笔记本电脑或服务器上创建的密钥库文件,无论是 PKCS12 还是较旧的 JKS 格式,存放的都是软件密钥。由此生成的 CSR 不会被公共证书颁发机构接受用于签发代码签名证书。证书颁发机构已停止支持通过浏览器生成密钥以及为这类产品提供可下载的密钥文件。现在,您的密钥只能来自以下两个途径之一:
- CA 寄送给您的预配置令牌。CA 会在其一侧的经认证 USB 令牌上生成密钥对和 CSR,将签发的证书加载到令牌上,然后将令牌邮寄给您。您无需自行创建 CSR。
- 您自己的 HSM 或令牌,配合密钥证明。您在设备内部生成密钥,由此生成 CSR,并提交一份证明文件,向 CA 证明该密钥是在合规硬件上创建的且无法导出。两部分缺一不可;没有有效证明文件的 CSR 会被拒绝。
具体适用哪条途径在您下单时决定。比较详情请参见代码签名证书交付方式。如果您已经拥有合规硬件,请按照对应设备的指南操作:YubiKey 5 FIPS CSR 生成与证明或Luna Network Attached HSM v7.x CSR 及证明指南。
keytool 仍然适用的场景
以上内容并不意味着 keytool 已被淘汰。它在三种情况下依然是正确的工具,其中只有第一种情况会生成可提交给公共 CA 的 CSR:
- 作为令牌或 HSM 的 PKCS#11 前端。keytool 原生支持 PKCS#11。将其指向您供应商的 PKCS#11 库,密钥永远不会离开设备,同时熟悉的-certreq命令依然可以生成 CSR。相关命令见下方硬件章节。
- 用于内部或企业签名。如果您使用自己组织的 CA 对内部软件进行签名,密钥存储由您的内部策略决定,而非公共基线要求。软件密钥库在这种情况下是正常选择。
- 用于练习。如果您已经针对一个用完即弃的密钥库运行过一次提示流程,那么第一次尝试就正确填写主体名称会更容易。
开始之前有一点需要了解:keytool 不会生成密钥证明。证明文件由令牌或 HSM 供应商自己的工具生成,因此即使走 PKCS#11 路径,您也需要使用供应商工具来生成该文件,keytool 仅用于生成 CSR。
使用 keytool 生成 CSR
如果您已经创建了 CSR 并且证书颁发机构已经签发了证书,请直接跳转到导入 CA 回复部分,这一步在别名不匹配时会悄无声息地失败。
keytool 随 JDK 一起提供,因此如果您还没有安装最新版 JDK,请先安装。JDK 25是当前的长期支持版本,JDK 26是当前的短期版本。以下命令可在 JDK 17 及更高版本上运行,涉及版本差异的地方会另作说明。请先确认该工具在您的路径中:
java -version
keytool -help
jarsigner -version
仅安装 Java 运行时环境是不够的,而 keytool 能正常运行也并不能证明您安装的是 JDK:旧版 Oracle JRE 8 附带keytool,但不带jarsigner。现在请依次运行这三条命令。如果前两条有响应而第三条没有,说明您使用的是 JRE,此时安装 JDK 要比等到签名时才发现问题容易得多。
第 1 步:创建密钥库和密钥对
在终端中运行以下命令,或在 Windows 上通过命令提示符或 PowerShell,从您希望密钥库文件所在的目录执行:
keytool -genkeypair -alias codesign -keyalg RSA -keysize 3072 -storetype PKCS12 -keystore codesign.p12
该命令中有四处细节与旧版说明不同,每一处都很重要:
- 是 -genkeypair,而不是 -genkey。旧的-genkey写法仍可运行,keytool 也不会针对其打印任何警告,但它只是在源码中作为遗留别名保留下来,在 JDK 文档中已经完全找不到了。请写作-genkeypair。
- 是 -storetype PKCS12,而不是 JKS。JKS 是 Oracle 的专有密钥库格式。PKCS12 是行业标准格式,自 Java 9 起就是 JDK 的默认格式。如果您仍然创建 JKS 密钥库,keytool 会在每一条涉及该密钥库的命令上发出警告:”The JKS keystore uses a proprietary format. It is recommended to migrate to PKCS12 which is an industry standard format.”(JKS 密钥库使用的是专有格式,建议迁移到行业标准格式 PKCS12。)
- 文件名并不决定格式。将文件命名为 keystore.jks 并不会使其成为 JKS 密钥库。keytool 会根据-storetype来判断类型;如果省略该选项,则从 JDK 安全配置文件中的keystore.type属性读取。在 JDK 9 及更高版本中,该属性的值为pkcs12,因此一条写入 keystore.jks 但未指定-storetype的命令会悄悄生成一个名称具有误导性的 PKCS12 文件。
- 是 -keysize 3072,而不是 2048。基线要求将 RSA 3072 设为代码签名的最小密钥长度,因此 2048 位的请求会被拒绝。当前版本的 JDK 默认将 RSA 密钥长度设为 3072,但 JDK 17 及更早版本默认仍为 2048,因此请显式传入该选项,以保证命令在各处行为一致。
示例中的别名codesign是该条目在密钥库中的标签。请选择一个您能记住的名称,并将其记录下来:后续所有命令都需要用到它,而别名不匹配正是下文所述证书导入失败的原因。
也支持使用 ECDSA 密钥。如果您偏好 P-256,请用 -keyalg EC -groupname secp256r1 替代 RSA 相关选项,并向您的 CA 确认您所购买的产品支持 ECDSA。
第 2 步:回答用于构建可分辨名称的提示问题
keytool 会两次询问密钥库密码,然后按以下确切顺序提出六个问题。顺序很重要:第二个提示询问的是组织单位,而非组织,一些旧版指南对此列错了顺序,导致读者在此处填写公司名称,从而使其落入名称中的错误组成部分。
- What is your first and last name?(您的名和姓是什么?)尽管措辞如此,这一项实际会成为通用名称(CN)。对于代码签名证书而言,CN 是用户将看到的发布者身份,因此请输入您组织的准确法定名称,或者对于个人证书输入您本人的完整法定姓名。请勿在此处填写域名。
- What is the name of your organizational unit?(您的组织单位名称是什么?)即部门,例如 IT。请勿直接按回车跳过:keytool 会将字面值Unknown写入该名称,导致 OU=Unknown 出现在您的 CSR 中。如果您根本不想设置组织单位,请使用下方的-dname形式,并在字符串中省略 OU 部分。
- What is the name of your organization?(您的组织名称是什么?)即已注册的法定名称,需与官方记录中的拼写一致。CA 会依据公开注册信息核实该名称。
- What is the name of your City or Locality?(您的城市或所在地名称是什么?)即注册所在城市,需完整填写。
- What is the name of your State or Province?(您的州或省名称是什么?)需填写全称,而非缩写。
- What is the two-letter country code for this unit?(该单位所在国家的两位字母代码是什么?)即 ISO 代码,例如 US。
随后 keytool 会打印出组装好的名称并要求您确认:
Is CN=Example LLC, OU=IT, O=Example LLC, L=Miami, ST=Florida, C=US correct?
[no]:
默认答案是no,因此直接按回车会让您重新回答全部六个问题。输入yes以确认接受。请先仔细阅读这一行:这是完整的可分辨名称,而不仅仅是通用名称,其中每一个组成部分都会写入 CSR。
PKCS12 密钥库没有单独的密钥密码。较旧的指南在这一步末尾会写“为密钥输入一个密码”,那是 JKS 的行为。在 PKCS12 密钥库中,密钥密码就是密钥库密码,如果您传入与之不同的-keypass值,keytool 会提示:”Different store and key passwords not supported for PKCS12 KeyStores. Ignoring user-specified -keypass value.”(PKCS12 密钥库不支持密钥库密码与密钥密码不同的情形,将忽略用户指定的 -keypass 值。)
如果想完全跳过这些提示,可以使用-dname提供完整名称。请将该值放在一对英文直引号内:
keytool -genkeypair -alias codesign -keyalg RSA -keysize 3072 -storetype PKCS12 -keystore codesign.p12 -dname "CN=Example LLC, OU=IT, O=Example LLC, L=Miami, ST=Florida, C=US"
不要在命令行中直接写入-storepass,而应让 keytool 提示您输入密码。将密码作为参数传递会将其写入您的 shell 历史记录,并暴露给任何能够列出正在运行进程的人。
第 3 步:创建 CSR
此时密钥库中已保存了一个私钥和一个临时的自签名证书。将该条目转换为证书请求:
keytool -certreq -alias codesign -keystore codesign.p12 -file codesign.csr
系统提示时输入密钥库密码。这里不需要-storetype:keytool 会自动检测已存在密钥库文件的格式。别名必须与第 1 步中使用的一致,因为 CSR 是由该条目对应的私钥签名的。
此命令不会创建新的私钥。密钥是在第 1 步中创建的,并一直保存在密钥库内,这就是密钥库文件及其密码如今与密钥本身一样敏感的原因。任何同时持有这两者的人都可以以您的名义签名软件。
第 4 步:发送前检查 CSR
被拒绝的 CSR 会耗费一次验证周期,因此请解码并回读它:
keytool -printcertreq -file codesign.csr
确认输出中的三项内容。Subject一行应在正确的组成部分中列出您的信息,法定组织名称位于 O 中,发布者身份位于 CN 中。公钥一行应显示为3072-bit RSA key或更大。签名算法应为 SHA-2 系列算法:当前版本的 JDK 会使用 SHA384withRSA 为 3072 位 RSA 请求签名,较旧版本则使用 SHA256withRSA,两者均可接受。该算法只能证明您持有对应的私钥,它并不是 CA 用来签署您证书的算法。
您也可以将该请求粘贴到我们的CSR 解码工具中,在浏览器里查看相同的字段。
提交请求时,请在纯文本编辑器中打开该文件并复制全部内容,包括首尾两行。keytool 会写入以下确切标记,两侧各有五个连字符:
-----BEGIN NEW CERTIFICATE REQUEST-----
MIID3TCCAkUCAQAwaDELMAkGA1UEBhMCVVMxEDAOBgNVBAgTB0Zsb3JpZGExDjAM
...base64 encoded request...
-----END NEW CERTIFICATE REQUEST-----
NEW CERTIFICATE REQUEST这一措辞是 keytool 输出的正常写法,注册表单会接受它。如果您的编辑器将某一段连字符替换成了长破折号,该请求将被拒绝:请将标记重新输入为普通连字符,或改用代码编辑器复制该文件。
使用 keytool 在令牌或 HSM 上生成 CSR
这条路径生成的 CSR 才是公共 CA 可以受理的。keytool 通过 SunPKCS11 提供程序与硬件令牌通信,因此密钥对是在设备内部创建的,从不以文件形式存在。首先编写一个小型配置文件,例如token.cfg,用于指定您的令牌名称,并指向您供应商安装的 PKCS#11 库:
name = token
library = /usr/local/lib/libeToken.so
name和library这两行是唯一必需的。库路径因供应商而异,也因操作系统而异,请从您的令牌文档中获取,而不要照搬本示例。在 Windows 上,该路径通常是系统目录下的一个 DLL 文件。若未指定 slot 行,提供程序会连接到设备报告的第一个插槽,这在只插入一个令牌时正是您想要的效果。如果您有多个读卡器或令牌,可添加slotListIndex(指定该列表中的位置,从零开始计数),或添加slot(指定供应商工具打印出的数字插槽 ID)。文件中只能出现二者之一,且插槽 ID 与列表位置并非同一个数字,因此不要随意猜测slot = 0。
列出设备上的内容。别名由令牌提供,因此在进行其他操作之前您需要先获取它:
keytool -list -keystore NONE -storetype PKCS11 -providerClass sun.security.pkcs11.SunPKCS11 -providerArg token.cfg
只要密钥库不是文件,就必须使用-keystore NONE,此时的密码提示实际询问的是令牌 PIN。keytool 也接受用 -addprovider SunPKCS11 -providerarg token.cfg 来代替-providerClass和-providerArg这一组参数;两种写法都可以,证书颁发机构的文档通常使用较旧的那种写法。
获得别名后,在设备上生成密钥,然后针对该密钥请求 CSR:
keytool -genkeypair -alias codesign -keyalg RSA -keysize 3072 -keystore NONE -storetype PKCS11 -providerClass sun.security.pkcs11.SunPKCS11 -providerArg token.cfg -dname "CN=Example LLC, O=Example LLC, L=Miami, ST=Florida, C=US"
keytool -certreq -alias codesign -keystore NONE -storetype PKCS11 -providerClass sun.security.pkcs11.SunPKCS11 -providerArg token.cfg -file codesign.csr
有两点需要注意。部分令牌不允许通过 PKCS#11 生成密钥,而要求您使用供应商自己的工具,这也没有问题:keytool 依然可以针对供应商工具生成的密钥创建 CSR。此外,keytool 无法生成您的 CA 会要求提供的证明文件,因此请在生成密钥的同时使用供应商工具生成该文件,具体可参考YubiKey或Luna HSM指南。
将 CA 回复导入同一别名
证书签发后,必须被导回生成 CSR 的那个条目中。如果导入到其他任何别名,keytool 仍会报告成功,但生成的密钥库却无法用于签名,因此请在运行任何命令之前阅读本节内容。
请先导入 CA 的根证书以及所有中间证书,每个证书使用各自独立的别名:
keytool -importcert -trustcacerts -alias caroot -file root.crt -keystore codesign.p12
keytool 会打印即将存储的证书,并询问Trust this certificate?(是否信任该证书?),默认答案为no,因此请输入yes。在确认之前,请将指纹与您证书颁发机构公布的指纹进行核对。
如果跳过根证书导入步骤,下一条命令会失败,并给出一条完全看不出原因的信息:
keytool error: java.lang.Exception: Failed to establish chain from reply
现在使用与第 1 步相同的别名导入已签发的证书:
keytool -importcert -alias codesign -file codesign.crt -keystore codesign.p12
您希望看到的消息是Certificate reply was installed in keystore(证书回复已安装到密钥库中)。这表示 keytool 识别出了该别名下已存在的私钥,并已将签发的证书及其证书链附加到该私钥上。
如果您改用一个新建的别名,keytool 会接受该文件并打印Certificate was added to keystore(证书已添加到密钥库中)。这看似成功,实则不然。keytool 已将该证书作为一个独立的受信任条目存储起来,其背后没有任何私钥,该条目永远无法用于签名。与此同时,原来的别名仍然保存着第 1 步中生成的临时自签名证书。请检查您实际拥有的是哪一种:
keytool -list -keystore codesign.p12
您用于签名的别名必须列为PrivateKeyEntry。若某个别名显示为trustedCertEntry,则说明出现了上述错误。使用 keytool -delete -alias wrongalias -keystore codesign.p12 将其删除,然后针对正确的别名重新执行导入。在 list 命令中加入-v,以确认该条目现在携带的是完整的证书链,而不是单一的自签名证书。
使用硬件令牌时,大多数情况下无需导入任何内容,因为 CA 会在寄送设备之前就将证书加载到设备上。如果您的 CA 针对您在自己 HSM 上生成的密钥发来了证书文件,请使用上一节中带有 PKCS#11 选项的同一条-importcert命令。
使用该证书对 JAR 文件签名
将代码签名证书放入 Java 密钥库的目的在于使用同样随 JDK 提供的jarsigner对 JAR 文件签名。在证书已安装到正确别名下的前提下:
jarsigner -keystore codesign.p12 -tsa https://your-ca-timestamp-url application.jar codesign
务必始终通过-tsa传入您证书颁发机构公布的时间戳 URL。时间戳会记录该 JAR 是在证书仍然有效期间签署的,因此即使证书到期,签名依然可以正常验证。如果不加时间戳,一旦证书过期,您软件的每一份副本都会立即无法通过验证。
当密钥存放在令牌上时,请像 keytool 那样将 jarsigner 指向 PKCS#11:
jarsigner -keystore NONE -storetype PKCS11 -providerClass sun.security.pkcs11.SunPKCS11 -providerArg token.cfg -tsa https://your-ca-timestamp-url application.jar codesign
如果令牌空间不足以容纳完整的证书链,可通过-certchain单独提供。之后请检查结果:
jarsigner -verify -verbose -certs application.jar
成功执行会打印jar verified,并附带签名者的可分辨名称和时间戳详情。当前版本的 JDK 默认使用 SHA-384 作为摘要算法,因此您很少需要手动设置-digestalg或-sigalg。
创建该请求的其他方式详见OpenSSL、CertReq、Microsoft Management Console以及macOS Keychain Access指南,且同样的硬件规则适用于所有这些方式。另请参阅我们完整的代码签名教程合集以及代码签名证书 CSR 生成概述。如果您需要的是网站证书而非代码签名证书的 keytool CSR,请改为参阅Tomcat或JBoss指南。
常见问题
只有在 keytool 通过 PKCS#11 在硬件令牌或 HSM 内部生成密钥,并且您能够提供证书颁发机构所要求的证明文件的情况下才可以。从您计算机上的普通密钥库文件创建的 CSR 属于软件密钥,自 2023 年 6 月 1 日起,公共 CA 不再针对软件密钥签发代码签名证书。若用于内部或企业 CA 签名,软件密钥库仍然完全适用。
请使用-genkeypair。-genkey写法是一个遗留别名,JDK 目前仍会接受它且不会给出任何警告,但它在许多版本的文档中都已被移除。如今两者功能相同,只是其中一个有文档记录。
请使用 PKCS12。它是一种行业标准格式,自 Java 9 起就是 JDK 的默认密钥库类型,而 JKS 是 Oracle 专有格式,会导致 keytool 在每条命令上打印迁移警告。文件扩展名完全没有影响:keytool 从-storetype判断格式,若省略该选项,则依据keystore.type安全属性判断,因此在现代 JDK 上创建的名为 keystore.jks 的文件通常实际是 PKCS12 文件。若要转换现有密钥库,可运行 keytool -importkeystore -srckeystore keystore.jks -destkeystore keystore.p12 -deststoretype pkcs12。
应使用 RSA 3072 位或更大,这是代码签名基线要求自 2021 年 6 月 1 日起的规定,或者使用同等强度的 ECDSA 密钥,例如 P-256。请显式传入 -keysize 3072,因为 JDK 17 及更早版本默认使用 2048 位,而该长度的请求会被拒绝。
您很可能把 CA 的回复导入到了一个新的别名,而不是生成 CSR 的那个别名。此时 keytool 会将其存储为一个没有关联私钥的独立受信任证书,并打印Certificate was added to keystore,看起来像是成功了。请运行 keytool -list -keystore codesign.p12:用于签名的别名必须显示为PrivateKeyEntry,而不是trustedCertEntry。删除错误的条目,然后使用原始别名重新导入,此时您应该会看到Certificate reply was installed in keystore。
keytool 无法从您已签发的证书构建到某个已受信任证书的路径。请先将 CA 的根证书和中间证书导入同一密钥库,每个证书使用各自的别名,并加上-importcert -trustcacerts,然后再重新导入您的证书。或者,也可以导入一个包含您的证书、后接中间证书和根证书的单一文件。
不需要。代码签名证书是通过其代码签名扩展密钥用途来标识的,而不是通过某个工具链,因此同一张证书既可用 jarsigner 为 JAR 文件签名,也可用 signtool 为 Windows 可执行文件签名。您在下单时真正需要选择的是交付方式,它决定了 CA 是寄送预配置令牌给您,还是由您在自己已有的硬件上生成密钥。
使用令牌或 HSM 时,密钥存放在设备内部且无法被复制出来,这正是该要求的核心所在。若使用软件密钥库进行内部签名,密钥则存放在密钥库文件中,因此该文件及其密码共同构成了需要保密的信息:请将它们保存在受限访问的位置,切勿将两者中的任何一个提交到源代码管理系统,并让 keytool 提示输入密码,而不要在命令行中传入-storepass,以免其被记录到您的 shell 历史记录中。

