You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用RSA公钥验证Webhook签名时Java方法始终返回false的问题求助

RSA公钥验证Webhook签名时Java方法始终返回false的问题求助

大家好,我最近在处理Webhook签名验证的问题,遇到了一个棘手的情况:我的Java验证方法始终返回false,但我确认传入的请求头和请求体都是正确的,按服务商的规则应该验证通过才对。想请各位帮忙排查下问题所在。

问题详情

我的Java签名验证方法无论怎么测试都返回false,但实际传入的请求头和请求体都是正确的,理论上应该返回TRUE。

服务商文档说明

Signature = Base64(RSA512(WEBHOOK_PRIVATE_KEY, SHA512(eventBody)))

相关信息

公钥

-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0+6wd9OJQpK60ZI7qnZG
jjQ0wNFUHfRv85Tdyek8+ahlg1Ph8uhwl4N6DZw5LwLXhNjzAbQ8LGPxt36RUZl5
YlxTru0jZNKx5lslR+H4i936A4pKBjgiMmSkVwXD9HcfKHTp70GQ812+J0Fvti/v
4nrrUpc011Wo4F6omt1QcYsi4GTI5OsEbeKQ24BtUd6Z1Nm/EP7PfPxeb4CP8KOH
clM8K7OwBUfWrip8Ptljjz9BNOZUF94iyjJ/BIzGJjyCntho64ehpUYP8UJykLVd
CGcu7sVYWnknf1ZGLuqqZQt4qt7cUUhFGielssZP9N9x7wzaAIFcT3yQ+ELDu1SZ
dE4lZsf2uMyfj58V8GDOLLE233+LRsRbJ083x+e2mW5BdAGtGgQBusFfnmv5Bxqd
HgS55hsna5725/44tvxll261TgQvjGrTxwe7e5Ia3d2Syc+e89mXQaI/+cZnylNP
SwCCvx8mOM847T0XkVRX3ZrwXtHIA25uKsPJzUtksDnAowB91j7RJkjXxJcz3Vh1
4k182UFOTPRW9jzdWNSyWQGl/vpe9oQ4c2Ly15+/toBo4YXJeDdDnZ5c/O+KKadc
IMPBpnPrH/0O97uMPuED+nI6ISGOTMLZo35xJ96gPBwyG5s2QxIkKPXIrhgcgUnk
tSM7QYNhlftT4/yVvYnk0YcCAwEAAQ==
-----END PUBLIC KEY-----

签名请求头

YNYVgWsx3PdoEGq2nUFGLmE6tE2y0LCc/eWPSY+rAqK+8fcxrPN0SPGbTdAXQ9+v62T5akWaVRWKXc1YBWlZxhVTCa/Ou7FfjVPG6JIQNX3Lks3ZhW0k29bVKf7Qvjp7z8HM9s1D8ZC28HvpX15a4by7DpNKkQ6cLWMDtBvqY02FSO+L4Vq54GZoTrplYkqCYcI4/oWchYzMZMq4omIOuam2DXm5BLlZ7HCR/nhUyp5duJpYnWJCKEwOTh3zLm842r5Fa9humq9WKkkT+AgFxe95bG4F3p8XhsciXiaNgx8diKLF0aBklqJ6yA70vjIP92BHuEnvIl37RiSFiIvkYWvLpMc1LoPxWZvncaLUjlYSVT3zd/gCDPEn1Mu8wUogGt9npkc/eKMdrKefcjEIMrJoO0HMMREZcOpc72F0+RM4QCkMaQMmK4zq9cBF0E2bNaEabNDSWXIfx9fa2VuyGYa5GLmAPUQPYRv50n92IGFewxj9vFAWhca+uthvsqz3FekyHK+c9G1Wh9OScR2TQp9Lbe4LqlX4FGapQitmfDvKRJhjAVm0n5355+k1dRse4fGeXqd2EfledWUJ3egpmW1NlmWBr8d4PsruKYZnphEMn9F5F3Vyu2sCpBSvqmcMANXzyZP7u3lGsUpH4V2lM6nCeBiRcbfwyrFsJ6Q5dso=

请求体

{"type":"TRANSACTION_STATUS_UPDATED","tenantId":"f4df1e73-ec68-53c5-aa92-1a2bc45900ef","timestamp":1671288284087,"data":{"id":"b96f37dd-0fe9-4aa0-853b-f7d39c2ddc52","createdAt":1671286112004,"lastUpdated":1671286112019,"assetId":"BTC_TEST","source":{"id":"","type":"UNKNOWN","name":"External","subType":""},"destination":{"id":"26","type":"VAULT_ACCOUNT","name":"55b49ae0-0f34-4b3c-8cf6-0094254261c2","subType":""},"amount":1.0E-5,"networkFee":1.41E-6,"netAmount":1.0E-5,"sourceAddress":"tb1qluc5wgms8kpu0tydu00590qryfan3969jvmc8e","destinationAddress":"tb1qyhfnsfe2dy8az3040yvx087qdfsw6yxk8pc7yj","destinationAddressDescription":"","destinationTag":"","status":"CONFIRMING","txHash":"15873dc631db22a1bd13c6adecf7fb63f8fbbecce36eb38d12df65573e83dfa9","subStatus":"PENDING_BLOCKCHAIN_CONFIRMATIONS","signedBy":[],"createdBy":"","rejectedBy":"","amountUSD":0.17,"addressType":"","note":"","exchangeTxId":"","requestedAmount":1.0E-5,"feeCurrency":"BTC_TEST","operation":"TRANSFER","customerRefId":null,"numOfConfirmations":2,"amountInfo":{"amount":"0.00001","requestedAmount":"0.00001","netAmount":"0.00001","amountUSD":"0.17"},"feeInfo":{"networkFee":"0.00000141"},"destinations":[],"externalTxId":null,"blockInfo":{"blockHeight":"2411637","blockHash":"00000000d45b7ebf40921cbc70fb6791985a0b256241757a28bf762be07478e8"},"signedMessages":[],"index":1}}

我的Java验证代码

签名验证核心方法

public boolean matches(WebhookEvent body, String header){
    try {
        File publicKeyFile = new File("publicKey.pub");
        byte[] bytes = PemUtils.parsePEMFile(publicKeyFile);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);
        PublicKey publicKey = kf.generatePublic(spec);

        ObjectMapper objectMapper = new ObjectMapper();
        String message = objectMapper.writeValueAsString(body);

        Signature verifier = Signature.getInstance("SHA512withRSA");
        verifier.initVerify(publicKey);
        verifier.update(message.getBytes());

        boolean isVerified = verifier.verify(Base64.getDecoder().decode(header));
        System.out.println("Verified: " + isVerified);
        return isVerified;
    } catch (Exception e){
        log.error("Error while verifying signature : " + e.getMessage());
        e.printStackTrace();
        return false;
    }
}

PEM文件解析工具类

static byte[] parsePEMFile(File pemFile) throws IOException {
    if (!pemFile.isFile() || !pemFile.exists()) {
        throw new FileNotFoundException(String.format("The file '%s' doesn't exist.", pemFile.getAbsolutePath()));
    }
    PemReader reader = new PemReader(new FileReader(pemFile));
    PemObject pemObject = reader.readPemObject();
    byte[] content = pemObject.getContent();
    reader.close();
    return content;
}

相关POJO类

WebhookEvent

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class WebhookEvent {
    @JsonProperty(value = "type")
    private FireblocksEventType eventType;
    @JsonProperty(value = "tenantId")
    private String tenantId;
    @JsonProperty(value = "timestamp")
    private long timestamp;
    @JsonProperty(value = "data")
    private TransactionDetailObject eventData;
}

TransactionDetailObject

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class TransactionDetailObject {
    private String id;
    private String assetId;
    private TransferPeerPathResponse source;
    private TransferPeerPathResponse destination;
    private BigDecimal requestedAmount;
    private AmountInfo amountInfo;
    private FeeInfo feeinfo;
    private BigDecimal amount;
    private BigDecimal netAmount;
    private BigDecimal amountUSD;
    private BigDecimal serviceFee;
    private Boolean treatAsGrossAmount;
    private BigDecimal networkFee;
    private Long createdAt;
    private Long lastUpdated;
    private TransactionStatus status;
    private String txHash;
    private Long index;
    private TransactionSubStatus subStatus;
    private String sourceAddress;
    private String destinationAddress;
    private String destinationAddressDescription;
    private String destinationTag;
    private List<String> signedBy;
    private String createdBy;
    private String rejectedBy;
    private String addressType;
    private String note;
    private String exchangeTxId;
    private String feeCurrency;
    private TransactionOperation operation;
    private AmlScreeningResult amlScreeningResult;
    private String customerRefId;
    private Long numOfConfirmations;
    private List<NetworkRecord>networkRecords;
    private String replacedTxHash;
    private String externalTxId;
    private List<DestinationResponse>destinations;
    private BlockInfo blockInfo;
    private RewardsInfo rewardsInfo;
    private AuthorizationInfo authorizationInfo;
    private List<SignedMessage>signedMessages;
    private Object extraParameters;
}

备注:内容来源于stack exchange,提问作者Gladiator9120

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.23 07:54:32