使用 GCP 生成的非对称密钥时验证签名不起作用

问题描述 投票:0回答:1

在我的 Spring boot 应用程序中,我有一个过滤器,可以为每个响应生成签名。我在GCP中创建了一个非对称密钥(由GCP本身生成)

关键算法是

2048 bit RSA key PSS Padding - SHA256 Digest

这是我的类,负责签名和验证。验证将移至前端,但目前,由于这只是一个测试,我已将所有内容添加到单个类中:

@Configuration
@Slf4j
public class ResponseFilterConfig {
    GcpKmsConfiguration gcpKmsConfiguration;
    private CryptoKeyVersionName asymmetricSignKeyForAPI;
    @Autowired
    KeyManagementServiceClient keyManagementServiceClient;
    @Autowired
    GcpKmsProperties gcpKmsProperties;
    @Autowired
    GCPKeyUseCase gcpKeyUseCase;

    public ResponseFilterConfig(GcpKmsConfiguration gcpKmsConfiguration) {
        this.gcpKmsConfiguration = gcpKmsConfiguration;
        this.asymmetricSignKeyForAPI = gcpKmsConfiguration.asymmetricSignKeyForAPI();
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    @ConditionalOnWebApplication
    public Filter responseLoggingFilter() {
        return (request, response, chain) -> {
            HttpServletRequest httpRequest = (HttpServletRequest) request;
            boolean isFilterAllowed = shouldNotFilter(httpRequest);
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            ContentCachingResponseWrapper responseCacheWrapperObject = new ContentCachingResponseWrapper(httpResponse);
            chain.doFilter(request, responseCacheWrapperObject);

            byte[] responseArray = responseCacheWrapperObject.getContentAsByteArray();
            String responseStr = new String(responseArray, responseCacheWrapperObject.getCharacterEncoding());

            var dataToSign = httpResponse.getStatus() + responseStr;
            var signedData = signPayloadWithGcp(dataToSign);
            if (!isFilterAllowed) {
                httpResponse.addHeader("digital-signature", signedData);
            }
            responseCacheWrapperObject.copyBodyToResponse();
        };
    }

    private String signPayloadWithGcp(String payload) throws IOException {
        byte[] plaintext = payload.getBytes(StandardCharsets.UTF_8);
        MessageDigest sha256;
        AsymmetricSignResponse result;
        try {
            sha256 = MessageDigest.getInstance("SHA-256");
            byte[] hash = sha256.digest(plaintext);
            Digest digest = Digest.newBuilder().setSha256(ByteString.copyFrom(hash)).build();
            result = keyManagementServiceClient.asymmetricSign(asymmetricSignKeyForAPI, digest);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("Error signing payload with GCP", e);
        }

        String s = Base64.getEncoder().encodeToString(result.getSignature().toByteArray());

        //for test verify the signature
        try {
            var res = verifySignature(s, payload);
            log.info("Signature verification result: {}", res);
        } catch (Exception e) {
            log.error("Exception", e);
        }
        return s;
    }

    private boolean verifySignature(String signedData, String payload) throws Exception {
        byte[] signatureBytes = Base64.getDecoder().decode(signedData);

        byte[] plaintext = payload.getBytes(StandardCharsets.UTF_8);

        // Get the public key.
        com.google.cloud.kms.v1.PublicKey publicKey = fetchPublicKey();

        // Convert the public PEM key to a DER key (see helper below).
        byte[] derKey = convertPemToDer(publicKey.getPem());
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(derKey);
        java.security.PublicKey rsaKey = KeyFactory.getInstance("RSA").generatePublic(keySpec);

        // Verify the 'RSA_SIGN_PKCS1_2048_SHA256' signature.
        // For other key algorithms:
        // http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#Signature
        Signature rsaVerify = Signature.getInstance("SHA256withRSA");
        rsaVerify.initVerify(rsaKey);
        rsaVerify.update(plaintext);

        // Verify the signature.
        boolean verified = rsaVerify.verify(signatureBytes);
        System.out.printf("Signature verified: %s", verified);
        return verified;
    }

    private byte[] convertPemToDer(String pem) {
        BufferedReader bufferedReader = new BufferedReader(new StringReader(pem));
        String encoded =
                bufferedReader
                        .lines()
                        .filter(line -> !line.startsWith("-----BEGIN") && !line.startsWith("-----END"))
                        .collect(Collectors.joining());
        return Base64.getDecoder().decode(encoded);
    }

    // Method to fetch public key from Google Cloud KMS
    private com.google.cloud.kms.v1.PublicKey fetchPublicKey() throws IOException {
        return gcpKeyUseCase.getPublicKey2(gcpKmsProperties.getProjectId(), gcpKmsProperties.getLocationId(), gcpKmsProperties.getSigningKeyRing(), gcpKmsProperties.getApiSignKey(), gcpKmsProperties.getApiSignKeyVersion());
    }

    protected boolean shouldNotFilter(HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        return requestURI.contains("actuator") || requestURI.contains("open-api") || requestURI.contains("swagger") || requestURI.contains("/api/sign-key");
    }

}

问题是

verified
由于某种原因 varaibale 总是错误的结果。关于这段代码中可能存在的问题有什么建议吗?

有效负载示例:

200{"cards":[{"id":"69","referenceId":"89000901","number":"4459XXXXXXXX0901","status":"OPEN","expiryDate":"0528","modifiedAt":"2024-03-27T10:31:23","customerId":"36","nameOnCard":"Bora Duran","isPinCreated":false}]}

响应头中的签名示例:

R8e+LjoD1GDVhXB8ZqzS+22Lf2SIADU3nCKHtHfJ6la5trSwAplyVmnKl9kT79yZO/bBaXUT25yyVEIicj/um71Ja5czl0DbWcF48oJNVpN1Hol0TJTQOMbKtBhN63H97Pir4u1SLEC9yF2Xkhvuf0fmM4tVcSqnfwZymNnU6TpKGnF7WGhulrW4esGsKXw2zjGIhSfSkZNv74VOy2c+FUX3tTD/oDA9QtV2mQdCPXhiFrh9h9Ukn2u2ysOzGuhmnhFqP98tzNRBjftijv2ZiSdRDm5sqsc2kREp/33DrQUmFe4ywBmDS6l6yI2oCWfTjXQDdLXTvRGh9rRU8zOynQ==

这是我正在关注的文档: 创建和验证数字签名

java google-cloud-platform digital-signature pki google-cloud-kms
1个回答
0
投票

看起来您正在使用 RSASSA-PSS 方案进行签名,该方案对至少一个哈希值(消息或 MGF1 PRF)使用 SHA-256,可能两者都是因为使它们相同是很常见的,但尝试使用 RSASSA 进行验证-PKCS1-v1_5(又名经典或传统)方案非常不同。

使用“标准”(Oracle/OpenJDK)提供程序执行

Signature.getInstance("RSASSA-PSS")
,然后使用
.setParameter()
 的合适实例执行 
PSSParameterSpec
,这可能是
("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1)
。然后照常
.update()
数据和
.verify()
签名。

如果您拥有或可以(很容易)获得 BouncyCastle,并且如上所述,两个哈希值相同,您可以简单地

.getInstance(scheme,"BC")
来获取以下任意一项:

"SHA256withRSA/PSS"
"SHA256withRSASSA-PSS"
"SHA256withRSAandMGF1"

(无论是大写还是小写,JCA 算法名称始终如此)——这些都是同一算法的别名——然后照常使用它。

© www.soinside.com 2019 - 2024. All rights reserved.