在我的Spring bootstrap 应用程序中,我有一个过滤器,可以 for each 响应生成签名.我在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 (NoSuch算法rithmException 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==

这是我正在关注的文档: Creating and validating digital signatures

推荐答案

看起来您正在使用RSASSA-CSS方案签署,该方案将SHA-256用于至少一个哈希值(消息或MGF 1 PGP),可能两者都是,因为使它们相同是常见的,但try 使用RSASSA-PKCS 1-v1_5(又名classic 或传统)方案进行验证,这是非常不同的.

使用"标准"(Oracle/OpenJK)提供程序使用合适的实例PSSParameterSpec(可能是("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1))执行Signature.getInstance("RSASSA-PSS")然后执行.setParameter().然后像往常一样.update()数据和.verify()签名.

如果您拥有或可以(足够容易地)获得BouncyCastle提供者,并且如上所述,两个哈希相同,则您可以简单地为以下任何一个设置.getInstance(scheme,"BC"):

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

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

Java相关问答推荐

如何使用Spring Data从MongoDB文档中包含的数组中 Select 单个元素?

无法找到符号错误—Java—封装

方法没有用正确的值填充数组—而是将数组保留为null,'

基本时态运算的ISO-8601周数据表示法

Kubernetes的Java客户端检索状态.处于终止状态的Pod的阶段';正在运行';

SpringBootreact 式Web应用程序的Spring Cloud Configer服务器中的资源控制器损坏

RESTful框架类字段是安全的还是不安全的

由于 list 中的权限错误,Android未生成

每次FXMLLoader调用ApplationConext.getBean(类)时创建@Component的新实例

一对多关系和ID生成

如何在ApacheHttpClient 5中为单个请求设置代理?

RestTemplate Bean提供OkHttp3ClientHttpRequestFactory不支持Spring Boot 3中的请求正文缓冲

获取所有可以处理Invent.ACTION_MEDIA_BUTTON Android 13 API33的Android包

在权限列表中找不到我的应用程序

为什么没有加载java.se模块?

当我将JTextField的getText函数与相等的String进行比较时;t返回true

验证没有';t work on Hibernate Entity';s字段

如果c不为null,Arrays.sort(T[]a,Comparator<;?super T>;c)是否会引发ClassCastException?

spring 数据Elastic search 与 spring 启动数据Elastic search 之间的区别是什么?

这是JavaFX SceneBuilder的错误吗?