【问题标题】:Can't verify signature ECDSA key无法验证签名 ECDSA 密钥
【发布时间】:2022-01-07 00:23:31
【问题描述】:

我正在尝试使用 javascript 使用 ECDSA 算法创建密钥对,签署消息,然后在服务器端(使用 Java)验证它。

示例 javascript 代码:

    // help function
    function _arrayBufferToBase64( buffer ) {
        var binary = '';
        var bytes = new Uint8Array( buffer );
        var len = bytes.byteLength;
        for (var i = 0; i < len; i++) {
            binary += String.fromCharCode( bytes[ i ] );
        }
        return window.btoa( binary );
    }
    const encodedMessage = new TextEncoder().encode('test');

    // keys generation
    crypto.subtle.generateKey({
        name: 'ECDSA',
        namedCurve: 'P-256'
    }, true, ['sign','verify']).then((keys) => {

        console.log('keys:', keys);
        console.log('[....] public key export');

        // public key export
        crypto.subtle.exportKey('spki', keys.publicKey).then(result => {

            console.log('publicKey byte:', result);
            console.log('publicKey base64:', _arrayBufferToBase64(result));
  
            // sign
            crypto.subtle.sign({
                    name: 'ECDSA',
                    hash: {name:'SHA-256'}
                },
                keys.privateKey,
                encodedMessage
                ).then(signature => {

                    console.log('signature byte:', signature);
                    console.log('signature base64:', _arrayBufferToBase64(signature));
          
                    // verify
                    crypto.subtle.verify({
                            name: 'ECDSA',
                            hash: {name: 'SHA-256'}
                        },
                        keys.publicKey,
                        signature,
                        encodedMessage
                    ).then(verified => {
                         console.log('signature verified?', verified);
                    });
                });
         });
     });

例如:

公钥 base64:MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERhn3GM1eQodijVDdy0Zb+ERjvWZ6idQw8PbaFfTzPdA7oNFh8+AHlni91snTS5Sk2o4Xw9CCnlvHP3uWVyCIvA== 签名base64:nAHPhibM3txezQ53O3C4vMlbQnMI75ILcaamqBezNS51c6wB2ZHwwk/4phgbp+aFlM9omvxaH0fdzJpCT0s/Zg== 签名验证?真的

一切都很好。

问题是我无法同时使用 Java 或 this website 验证同一消息:

Java 代码:

private boolean receiver(String message, String algorithm, String signature, String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, UnsupportedEncodingException, SignatureException {

    Signature ecdsaVerify = Signature.getInstance(algorithm);

    //import org.apache.commons.codec.binary.Base64;
    byte[] decodedPk = Base64.decodeBase64(new String(publicKey).getBytes("UTF-8"));
    EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(decodedPk);

    KeyFactory keyFactory = KeyFactory.getInstance("EC");
    PublicKey pk = keyFactory.generatePublic(publicKeySpec);
    
    ecdsaVerify.initVerify(pk);
    
    byte[] decodedString = new String(message).getBytes("UTF-8");
    byte[] decodedSignature = Base64.decodeBase64(new String(signature).getBytes("UTF-8"));

    ecdsaVerify.update(decodedString);
    boolean result = ecdsaVerify.verify(decodedSignature);

    return result;
}

在哪里

字符串消息=“测试”; 字符串算法 = "SHA256withECDSA"; 字符串签名 = "nAHPhibM3txezQ53O3C4vMlbQnMI75ILcaamqBezNS51c6wB2ZHwwk/4phgbp+aFlM9omvxaH0fdzJpCT0s/Zg=="; 字符串 publicKey = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERhn3GM1eQodijVDdy0Zb+ERjvWZ6idQw8PbaFfTzPdA7oNFh8+AHlni91snTS5Sk2o4Xw9CCnlvHP3uWVyCIvA==";

我得到了这个例外:

[错误] java.security.SignatureException:无法验证签名 [错误] 在 sun.security.ec.ECDSASignature.engineVerify(ECDSASignature.java:325) [错误] 在 java.security.Signature$Delegate.engineVerify(Signature.java:1223) [错误] 在 java.security.Signature.verify(Signature.java:656) [错误] 在 it.bpc.bpwebrs.application.services.IamService.receiver(IamService.java:2740) [错误] 在 it.bpc.bpwebrs.application.services.IamService.elaboraAbilitazioni(IamService.java:186) [错误] 在 it.bpc.bpwebrs.infrastructure.servicesImpl.IamResource.eseguiServizioElaborazione(IamResource.java:127) [错误] 在 sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) [错误] 在 sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) [错误] 在 sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) [错误] 在 java.lang.reflect.Method.invoke(Method.java:498) [错误] 在 it.bpc.bpwebrs.dispatcher.DispatcherResource.richiamaServizioRichiesto(DispatcherResource.java:447) [错误] 在 it.bpc.bpwebrs.dispatcher.DispatcherResource.postJSONRequest(DispatcherResource.java:227) [错误] 在 sun.reflect.GeneratedMethodAccessor77.invoke(未知来源) [错误] 在 sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) [错误] 在 java.lang.reflect.Method.invoke(Method.java:498) [错误] 在 org.glassfish.jersey.server.model.internal.ResourceMethodInvocationHandlerFactory$1.invoke(ResourceMethodInvocationHandlerFactory.java:81) [错误] 在 org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher$1.run(AbstractJavaResourceMethodDispatcher.java:144) [错误] 在 org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.invoke(AbstractJavaResourceMethodDispatcher.java:161) [错误] 在 org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$ResponseOutInvoker.doDispatch(JavaResourceMethodDispatcherProvider.java:160) [错误] 在 org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.dispatch(AbstractJavaResourceMethodDispatcher.java:99) [错误] 在 org.glassfish.jersey.server.model.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:389) [错误] 在 org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:347) [错误] 在 org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:102) [错误] 在 org.glassfish.jersey.server.ServerRuntime$2.run(ServerRuntime.java:326) [错误] 在 org.glassfish.jersey.internal.Errors$1.call(Errors.java:271) [错误] 在 org.glassfish.jersey.internal.Errors$1.call(Errors.java:267) [错误] 在 org.glassfish.jersey.internal.Errors.process(Errors.java:315) [错误] 在 org.glassfish.jersey.internal.Errors.process(Errors.java:297) [错误] 在 org.glassfish.jersey.internal.Errors.process(Errors.java:267) [错误] 在 org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:317) [错误] 在 org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:305) [错误] 在 org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1154) [错误] 在 org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:473) [错误] 在 org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:427) [错误] 在 org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:388) [错误] 在 org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:341) [错误] 在 org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:228) [错误] 在 com.ibm.ws.webcontainer.servlet.ServletWrapper.service(ServletWrapper.java:1285) [错误] [内部课程] [错误] 在 org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) [错误] 在 org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:118) [错误] 在 org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:84) [错误] 在 org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [错误] 在 org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:113) [错误] 在 org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [错误] 在 org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:103) [错误] 在 org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [错误] 在 org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:154) [错误] 在 org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [错误] 在 org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:45) [错误] 在 org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [错误] 在 org.springframework.security.web.authentication.www.BasicAuthenticationFilter.doFilter(BasicAuthenticationFilter.java:150) [错误] 在 org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [错误] 在 org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter.doFilter(DefaultLoginPageGeneratingFilter.java:155) [错误] 在 org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [错误] 在 org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:199) [错误] 在 org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [错误] 在 org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter.doFilter(OAuth2AuthenticationProcessingFilter.java:176) [错误] 在 org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [错误] 在 org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:110) [错误] 在 org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [错误] 在 org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:50) [错误] 在 org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [错误] 在 org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [错误] 在 org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87) [错误] 在 org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [错误] 在 org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192) [错误] 在 org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160) [错误] 在 org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:344) [错误] 在 org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:261) [错误] 在 com.ibm.ws.webcontainer.filter.FilterInstanceWrapper.doFilter(FilterInstanceWrapper.java:207) [错误] [内部课程] [错误] 在 it.bpc.bpwebrs.infrastructure.security.cors.CorsFilter.doFilter(CorsFilter.java:180) [错误] 在 com.ibm.ws.webcontainer.filter.FilterInstanceWrapper.doFilter(FilterInstanceWrapper.java:207) [错误] [内部课程] [错误] 在 it.bpc.bpwebrs.infrastructure.filters.MDCFilter.doFilter(MDCFilter.java:49) [错误] 在 com.ibm.ws.webcontainer.filter.FilterInstanceWrapper.doFilter(FilterInstanceWrapper.java:207) [错误] [内部课程] [错误] 在 it.bpc.bpwebrs.utilities.infrastructure.EncodingFilter.doFilter(EncodingFilter.java:30) [错误] 在 com.ibm.ws.webcontainer.filter.FilterInstanceWrapper.doFilter(FilterInstanceWrapper.java:207) [错误] [内部课程] [错误] 在 java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [错误] 在 java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [错误] 在 java.lang.Thread.run(Thread.java:748) [err] 原因:java.security.SignatureException:签名编码无效 [错误] 在 sun.security.ec.ECDSASignature.decodeSignature(ECDSASignature.java:400) [错误] 在 sun.security.ec.ECDSASignature.engineVerify(ECDSASignature.java:322) [错误] ... 92 更多 [err] Caused by: java.io.IOException: Sequence tag error [错误] 在 sun.security.util.DerInputStream.getSequence(DerInputStream.java:330) [错误] 在 sun.security.ec.ECDSASignature.decodeSignature(ECDSASignature.java:376) [呃] ... 93 更多

我做错了什么?

【问题讨论】:

    标签: javascript cryptography java-7 public-key-encryption webcrypto-api


    【解决方案1】:

    ECDSA 签名有不同的格式,一方面是 IEEE P1363 (r|s) 格式,另一方面是 ASN.1/DER 编码。

    两种格式的关系解释here

    WebCrypto 应用 IEEE P1363 格式,网站和 Java 代码中的 SHA256withECDSA 算法使用 ASN.1/DER,这就是问题的原因。

    SunEC 提供程序还支持 IEEE P1363,为此要应用的算法是 SHA256withECDSAinP1363Format。这样,验证就可以与 Java 代码一起使用。

    编辑:

    或者,可以使用 BouncyCastle,它支持两种格式:

    import org.bouncycastle.jce.provider.BouncyCastleProvider;
    ...
    Security.addProvider(new BouncyCastleProvider());
    Signature ecdsaVerify = Signature.getInstance("SHA256withPlain-ECDSA", "BC"); //IEEE P1363
    ...
    

    【讨论】:

    • 感谢您的帮助。我试过SHA256withECDSAinP1363Format,但它只适用于Java 9(我被困在Java 7...)。我想我应该将 javascript 签名转换为 ASN.1 格式然后...
    • @ErnestoSchiavo - 这是一种可能性,尽管 WebCrypto 作为低级加密库不支持这种格式转换。另一种可能更简单的方法是在 Java 代码中使用 BouncyCastle,它支持这两种格式。
    • @ErnestoSchiavo - 添加了 BC 替代方案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-14
    • 2020-07-15
    • 2014-12-28
    • 2022-01-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多