编程语言

Keystore was tampered with, or password was incorrect 怎么修

作者: · 最后核实

报错信息java.io.IOException: Keystore was tampered with, or password was incorrect

java.io.IOException: Keystore was tampered with, or password was incorrect 的意思是:Java 打开了一个 JKS 密钥库,用你给的密码计算完整性校验值,结果和文件里存的不一致。 大多数情况就是库密码填错了;其余情况多半是文件在部署过程中被损坏,最常见的是 Maven 资源过滤或者按文本方式复制。文件被篡改的情况很少。

这条报错是什么意思

这条报错表示 JKS 文件的完整性摘要对不上:文件末尾存着一段用文件内容和库密码计算出的摘要。用非空密码加载密钥库时,JDK 会重新计算这段摘要,不一致就由 JavaKeyStore 抛出这条 IOException,原因(cause)是 UnrecoverableKeyException: Password verification failed。JDK 无法区分“密码错了”和“文件被改了”,所以报错里两个都提。

通常你会在 keytool 或服务器日志里看到:

keytool error: java.io.IOException: Keystore was tampered with, or password was incorrect
Caused by: java.security.UnrecoverableKeyException: Password verification failed

JDK 9 起默认密钥库类型是 PKCS12,而且 keystore.type.compat=true 让 JKS 和 PKCS12 两种类型都能加载对方格式的文件。PKCS12 文件密码错误时报的是另一条:keystore password was incorrect。下面的修法对两者都适用。

排查从一步开始:在服务器上用 keytool、同一个文件、同一个密码打开一次。打得开,问题在应用拿到的密码或路径;打不开,再对比文件校验和,判断是密码错了还是文件坏了。不要一上来就重新生成证书,那样会掩盖配置错误,下次部署还会再出现。

常见原因(按出现频率排序)

这个报错有 5 种原因,最常见的是库密码不对,包括密码在传给 Java 之前被 shell 或 YAML 改掉。

1. 库密码不对

打错了、用了另一个环境的密码、自建的库却用了默认的 changeit,或者密码在传给 Java 之前被改掉了:$ 被 shell 展开、# 在 properties 文件里被当成注释、YAML 里没加引号。

  • 确认: 用完全相同的文件和密码测试,密码加引号:
keytool -list -keystore /opt/app/keystore.jks -storepass 'the-password'

keytool 能打开,说明文件没问题,是应用拿到的密码或文件不一样。

  • 修复: 在应用实际读取的地方改正(server.ssl.key-store-password、Tomcat 的 certificateKeystorePassword、javax.net.ssl.trustStorePassword 等),并在 shell 和 YAML 里给它加引号。

2. 密钥库和信任库的配置搞混了

javax.net.ssl.trustStore 指向了服务器的密钥库,或者自建信任库用别的密码创建,却配置了 trustStorePassword=changeit。

  • 确认: 看进程实际使用的 JVM 参数:ps -ef | grep java,或 jcmd <pid> VM.system_properties | grep javax.net.ssl。
  • 修复: 每个属性指向正确的文件,并配上它自己的密码。

3. 文件在传输或构建中被损坏

二进制文件被当成文本处理就会损坏。Maven 文档明确警告:过滤含二进制内容的文件很可能把它弄坏。Git 换行符转换、FTP 用 ASCII 模式传输、提交的是 Git LFS 指针而不是文件本身、Kubernetes Secret 被 base64 编码了两次,表现都一样。

  • 确认: 对比原始文件和部署后文件的校验和,再看文件头:
sha256sum keystore.jks /opt/app/keystore.jks
xxd -l 4 /opt/app/keystore.jks     # JKS 文件以 feed feed 开头
  • 修复: 不要过滤密钥库文件,在 Git 里标记为二进制,用二进制方式复制:
<plugin>
  <artifactId>maven-resources-plugin</artifactId>
  <configuration>
    <nonFilteredFileExtensions>
      <nonFilteredFileExtension>jks</nonFilteredFileExtension>
      <nonFilteredFileExtension>p12</nonFilteredFileExtension>
    </nonFilteredFileExtensions>
  </configuration>
</plugin>
# .gitattributes
*.jks binary
*.p12 binary

4. 应用加载的不是你以为的那个文件

相对路径在另一个工作目录下解析、JAR 包里打进了同名密钥库,都可能让你改的文件没被用上。改用绝对路径,并在启动日志里打印实际解析出的路径。

5. 文件确实被改过

很少见,但如果校验和不一致又说不清原因,应该按安全事件处理:更换私钥并重新签发证书。

分平台修复命令

各平台的修法都围绕 keytool:用应用的同一个密码验证文件、修改密码,或者转换成 PKCS12。

Linux 和 macOS:

keytool -list -v -keystore /opt/app/keystore.jks -storepass 'the-password' | head -20
keytool -storepasswd -keystore /opt/app/keystore.jks          # 修改密码(需要旧密码)
keytool -importkeystore -srckeystore keystore.jks -destkeystore keystore.p12 -deststoretype pkcs12

Windows(PowerShell): 用单引号,避免 $ 被展开:

& "$env:JAVA_HOME\bin\keytool.exe" -list -keystore C:\app\keystore.jks -storepass 'the-password'

JDK 信任库: keytool -list -cacerts -storepass changeit 用文档里的初始密码列出默认信任库。

Spring Boot:

server.ssl.key-store=file:/opt/app/keystore.p12
server.ssl.key-store-type=PKCS12
server.ssl.key-store-password=${KEYSTORE_PASSWORD}

国内云证书: 从阿里云、腾讯云等平台下载 Tomcat 或 JKS 格式的证书时,密钥库密码以平台提供的为准,不要沿用自己习惯的密码。下载后尽量直接解压到服务器,不要经过会改动文件内容的中转方式。

想从 PEM 文件生成 PKCS12,见 PKCS#12 速查或用 SSL 格式转换工具;用私钥匹配工具确认私钥和证书是一对。

不推荐:关闭证书校验

信任库打不开时改装“信任所有证书”的 TrustManager,等于取消服务器身份验证。有人打算“等找到密码再改回来”,这期间任何证书都会被接受,账号密码和数据都可能被截获。同样,不要为了部署方便把密码明文提交到代码仓库。

更安全的替代办法:用 keytool -importcert 把需要的 CA 证书导入一个新的信任库;密码放进密钥管理服务或环境变量;统一转换成 PKCS12,让各种工具对文件的处理方式一致。

怎么确认已经修好

修好的标准是:keytool 用应用的同一个密码能打开文件,应用启动时不再报这个 IOException。

keytool -list -keystore /opt/app/keystore.p12 -storepass "$KEYSTORE_PASSWORD" >/dev/null && echo "keystore OK"
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -subject -enddate

如果是服务器密钥库,443 端口出示的证书应该就是你预期的那一张,可以再用 SSL 检测工具从外部确认。

常见问题

密钥库密码忘了,能找回吗?

没有正规工具能找回,密码本身就是保护这个文件的东西。如果是只装了公开 CA 证书的信任库,把同样的证书导入一个新库即可;如果是带私钥的密钥库,就用原始的私钥和证书文件重新生成,或者生成新私钥后重新签发证书。

cacerts 的默认密码是什么?

初始密码是 changeit,keytool 文档同时建议管理员修改。部分发行版和企业镜像确实改过,以你所用平台的说明为准。

为什么 keytool 能打开,应用却报错?

应用用的不是你测试的那个文件或那个密码。常见差异:相对路径在别的工作目录下解析到了另一个文件;服务启动时环境变量没设置;JAR 包里打包了一个同名密钥库;密码里的 $、# 等字符被 shell 或 YAML 解释掉了。

库密码和私钥密码是一回事吗?

不一定。库密码保护整个文件,每个私钥条目还可以有自己的密钥密码。私钥密码错了会报别的错误(UnrecoverableKeyException: Cannot recover key 或 Get Key failed)。Tomcat 和 Spring Boot 对这两个密码都有单独的配置项。

资料来源

  1. Oracle keytool documentation (JDK 21)
  2. OpenJDK source: JavaKeyStore.java
  3. OpenJDK source: PKCS12KeyStore.java
  4. Apache Maven Resources Plugin: Filtering (binary files)
  5. JSSE Reference Guide (javax.net.ssl.keyStorePassword)