关于HashiCorp Vault Transit SHA256withECDSA签名支持及Java验证异常的问询
核心结论
Transit引擎完全支持ecdsa-p256密钥搭配SHA256withECDSA算法——ecdsa-p256(NIST P-256)本身就是与SHA-256配对的椭圆曲线密钥体系,验证失败的问题出在签名格式不兼容,而非算法支持问题。
原因分析
Vault Transit API返回的ECDSA签名默认采用ASN.1 DER编码格式,而Java的SHA256withECDSA签名验证逻辑(基于SunEC等主流provider)默认期望的是IEEE P1363格式(即直接拼接签名的r、s分量,形成64字节的二进制数据),格式不匹配导致抛出SignatureException: Invalid encoding for signature。
解决方案
有两种可行的解决方式,推荐第一种更直接的方案:
方案1:让Vault返回IEEE P1363格式的签名
调用/v1/transit/sign/ API时,添加signature_algorithm: "raw"参数,让Vault直接输出r+s拼接的原始格式签名,Java可直接验证。
修改后的curl命令:
curl --location 'http://localhost:8200/v1/transit/sign/test' \ --header 'X-Vault-Token: hvs.GJjDLDxW1iikrJfjK9PJgXTa' \ --header 'Content-Type: application/json' \ --data '{ "input": "c2FtcGxlIHRleHQ=", "signature_algorithm": "raw" }'
注意:原请求的
Content-Type设为text/plain是错误的,请求体为JSON时需改为application/json。
对应的Java验证示例:
import java.security.*; import java.security.spec.X509EncodedKeySpec; import java.util.Base64; public class ECDSAValidation { public static void main(String[] args) throws Exception { // 从Vault获取的公钥(Base64编码) String publicKeyB64 = "替换为你的公钥Base64字符串"; // 从Vault获取的raw格式签名(Base64编码) String signatureB64 = "替换为获取到的raw签名Base64字符串"; // 原始输入文本 String input = "sample text"; // 加载公钥 byte[] pubKeyBytes = Base64.getDecoder().decode(publicKeyB64); KeyFactory keyFactory = KeyFactory.getInstance("EC"); PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(pubKeyBytes)); // 初始化签名验证 Signature sig = Signature.getInstance("SHA256withECDSA"); sig.initVerify(pubKey); sig.update(input.getBytes()); // 验证签名 boolean isValid = sig.verify(Base64.getDecoder().decode(signatureB64)); System.out.println("签名验证结果:" + isValid); } }
方案2:在Java中转换DER格式为IEEE P1363格式
如果无法修改Vault请求参数,可在Java中解析DER编码的签名,提取r、s分量后拼接成所需格式。需要引入BouncyCastle库来处理ASN.1解析:
import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.ASN1Integer; import org.bouncycastle.asn1.ASN1Sequence; import java.util.Base64; public static byte[] convertDerToRaw(byte[] derSignature) throws Exception { try (ASN1InputStream asn1In = new ASN1InputStream(derSignature)) { ASN1Sequence seq = (ASN1Sequence) asn1In.readObject(); ASN1Integer r = (ASN1Integer) seq.getObjectAt(0); ASN1Integer s = (ASN1Integer) seq.getObjectAt(1); byte[] rBytes = r.getPositiveValue().toByteArray(); byte[] sBytes = s.getPositiveValue().toByteArray(); // 补全为32字节(P-256固定长度) byte[] rPadded = new byte[32]; System.arraycopy(rBytes, rBytes.length > 32 ? 1 : 0, rPadded, 32 - rBytes.length, Math.min(rBytes.length, 32)); byte[] sPadded = new byte[32]; System.arraycopy(sBytes, sBytes.length > 32 ? 1 : 0, sPadded, 32 - sBytes.length, Math.min(sBytes.length, 32)); // 拼接r和s byte[] rawSig = new byte[64]; System.arraycopy(rPadded, 0, rawSig, 0, 32); System.arraycopy(sPadded, 0, rawSig, 32, 32); return rawSig; } }
使用时,先将Vault返回的DER签名转换为raw格式,再传入Signature.verify()方法即可。
内容的提问来源于stack exchange,提问作者yash srivastava

