Search code examples
javalinuxopensslcryptographybouncycastle

Decrypt private key info fails only in Linux where the file was generated, but works in Windows (epki.decryptPrivateKeyInfo)


We have generated a private and public key pairs and convert private to PEM format pkcs8:

openssl genrsa -out psp_api_incoming_private.pem 2048 && openssl rsa -in psp_api_incoming_private.pem -pubout > psp_api_incoming_public.pem
openssl pkcs8 -topk8 -in psp_api_incoming_private.pem -out psp_api_incoming_private_key.pem

We have copied private for debugging uses for Windows machine and copied to another Linux folder where we have assigned them sudo chmod tomcat:tomcat access rights

On Windows machine we were able to run the below code, to generate the sign with this private key tat were deciphered with our public key on other side:

Signature signatureSHA256Java = Signature.getInstance("RSASSA-PSS");
signatureSHA256Java.setParameter(new PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1));
signatureSHA256Java.initSign(stringToPrivateKey(connectionData.getPathToKey(), connectionData.getPassForKey()));
signatureSHA256Java.update(IOUtils.toByteArray(request.getEntity().getContent()));
byte[] signSHA256Java = signatureSHA256Java.sign();

    
static public PrivateKey stringToPrivateKey(String s, String password)
                throws IOException, PKCSException {
            PrivateKeyInfo pki;
            Security.addProvider(new BouncyCastleProvider());
            try (PEMParser pemParser = new PEMParser(new StringReader(s))) {
                Object o = pemParser.readObject();
                if (o instanceof PKCS8EncryptedPrivateKeyInfo) { // encrypted private key in pkcs8-format
                    logger.info("key in pkcs8 encoding");
                    PKCS8EncryptedPrivateKeyInfo epki = (PKCS8EncryptedPrivateKeyInfo) o;
                    logger.info("epki:" + epki.getEncryptionAlgorithm().getAlgorithm());
                    JcePKCSPBEInputDecryptorProviderBuilder builder =
                            new JcePKCSPBEInputDecryptorProviderBuilder().setProvider("BC");
                    InputDecryptorProvider idp = builder.build(password.toCharArray());
                    pki = epki.decryptPrivateKeyInfo(idp); //<-- here we fail
                } else if (o instanceof PEMEncryptedKeyPair) { // encrypted private key in pkcs8-format
                    logger.info("key in pkcs1 encoding");
                    PEMEncryptedKeyPair epki = (PEMEncryptedKeyPair) o;
                    PEMKeyPair pkp = epki.decryptKeyPair(new BcPEMDecryptorProvider(password.toCharArray()));
                    pki = pkp.getPrivateKeyInfo();
                } else if (o instanceof PEMKeyPair) { // unencrypted private key
                    logger.info("key unencrypted");
                    PEMKeyPair pkp = (PEMKeyPair) o;
                    pki = pkp.getPrivateKeyInfo();
                } else {
                    throw new PKCSException("Invalid encrypted private key class: " + o.getClass().getName());
                }
                JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
                return converter.getPrivateKey(pki);
            }
}

And then I'm getting the below exception:

ERROR c.o.s.w.s.i.WebServiceExceptionHandler - org.bouncycastle.pkcs.PKCSException: unable to read encrypted data: Error finalising cipher
java.lang.RuntimeException: org.bouncycastle.pkcs.PKCSException: unable to read encrypted data: Error finalising cipher
        at com.openpayment.provider.mpc.qr.service.MpcQRServiceImpl.getPrivateKey(MpcQRServiceImpl.java:348)
        at com.openpayment.provider.mpc.qr.service.MpcQRServiceImpl.access$500(MpcQRServiceImpl.java:68)
        at com.openpayment.provider.mpc.qr.service.MpcQRServiceImpl$ProviderHttpClient.post(MpcQRServiceImpl.java:272)
        at com.openpayment.provider.mpc.qr.service.MpcQRServiceImpl.getSubscriberInfo(MpcQRServiceImpl.java:162)
        at com.openpayment.provider.mpc.qr.MpcQRProviderImpl.getSubscriberStatus(MpcQRProviderImpl.java:113)
        at com.openpayment.impl.service.PaymentServiceImpl.getInfo(PaymentServiceImpl.java:193)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:566)
        at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:333)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
        at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:69)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
        at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
        at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:283)
        at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
        at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
        at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
        at com.sun.proxy.$Proxy281.getInfo(Unknown Source)
        at com.openpayment.site.web.service.PaymentResource.getPropertiesInfo(PaymentResource.java:110)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:566)
        at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
        at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:133)
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:854)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:765)
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967)
        at com.openpayment.site.web.service.init.SiteWebDispatcherServlet.doDispatch(SiteWebDispatcherServlet.java:48)
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901)
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
        at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:655)
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:764)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:228)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:163)
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:190)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:163)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:317)
        at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:127)
        at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:91)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
        at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:114)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
        at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:137)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
        at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
        at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:170)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
        at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
        at org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter.doFilter(OAuth2AuthenticationProcessingFilter.java:176)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
        at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
        at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:66)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
        at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
        at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
        at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:214)
        at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:177)
        at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:347)
        at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:263)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:190)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:163)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
        at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:687)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382)
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1723)
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: org.bouncycastle.pkcs.PKCSException: unable to read encrypted data: Error finalising cipher
        at org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo.decryptPrivateKeyInfo(Unknown Source)
        at com.openpayment.provider.mpc.qr.service.MpcQRServiceImpl.stringToPrivateKey(MpcQRServiceImpl.java:380)
        at com.openpayment.provider.mpc.qr.service.MpcQRServiceImpl.getPrivateKey(MpcQRServiceImpl.java:346)
        ... 95 common frames omitted
Caused by: org.bouncycastle.crypto.io.InvalidCipherTextIOException: Error finalising cipher
        at org.bouncycastle.jcajce.io.CipherInputStream.finaliseCipher(Unknown Source)
        at org.bouncycastle.jcajce.io.CipherInputStream.nextChunk(Unknown Source)
        at org.bouncycastle.jcajce.io.CipherInputStream.read(Unknown Source)
        at org.bouncycastle.util.io.Streams.pipeAll(Unknown Source)
        at org.bouncycastle.util.io.Streams.readAll(Unknown Source)
        ... 98 common frames omitted
Caused by: javax.crypto.BadPaddingException: pad block corrupted
        at org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher$BufferedGenericBlockCipher.doFinal(Unknown Source)
        at org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineDoFinal(Unknown Source)
        at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2085)
        ... 103 common frames omitted

In the most questions about this error it is pointed, that the wrong password is the reason, so I try:

openssl rsa -noout -in YOUR_PRIVATE_KEY_FILE.pem -passin "pass:YOUR_PASSWORD"

With the last Linux file location, and it confirms that the password is exactly what I expect.

Java that runs Tomcat apps is OpenJDK 64-Bit Server VM AdoptOpenJDK-11.0.11+9

So now I have no idea what else could be wrong.


Solution

  • To make it working, I made those changes:

    static public PrivateKey stringToPrivateKey(String s, String password)
                throws IOException, PKCSException {
            PrivateKeyInfo pki;
            BouncyCastleProvider bouncyCastleProvider = new BouncyCastleProvider(); //INITIALISED PROVIDER OBJECT
            Security.addProvider(bouncyCastleProvider);
            try (PEMParser pemParser = new PEMParser(new StringReader(s))) {
                Object o = pemParser.readObject();
                if (o instanceof PKCS8EncryptedPrivateKeyInfo) { // encrypted private key in pkcs8-format
                    logger.info("key in pkcs8 encoding");
                    PKCS8EncryptedPrivateKeyInfo epki = (PKCS8EncryptedPrivateKeyInfo) o;
                    logger.info("epki:" + epki.getEncryptionAlgorithm().getAlgorithm());
                    JcePKCSPBEInputDecryptorProviderBuilder builder =
                            new JcePKCSPBEInputDecryptorProviderBuilder().
                                    setProvider(bouncyCastleProvider); // USED THIS OBJECT HERE INSTEAD OF STRING PARAMETER "BC
                    InputDecryptorProvider idp = builder.build(password.toCharArray());
                    pki = epki.decryptPrivateKeyInfo(idp);
                } else if (o instanceof PEMEncryptedKeyPair) { // encrypted private key in pkcs8-format
                    logger.info("key in pkcs1 encoding");
                    PEMEncryptedKeyPair epki = (PEMEncryptedKeyPair) o;
                    PEMKeyPair pkp = epki.decryptKeyPair(new BcPEMDecryptorProvider(password.toCharArray()));
                    pki = pkp.getPrivateKeyInfo();
                } else if (o instanceof PEMKeyPair) { // unencrypted private key
                    logger.info("key unencrypted");
                    PEMKeyPair pkp = (PEMKeyPair) o;
                    pki = pkp.getPrivateKeyInfo();
                } else {
                    throw new PKCSException("Invalid encrypted private key class: " + o.getClass().getName());
                }
                JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(bouncyCastleProvider); // USED THIS OBJECT HERE INSTEAD OF STRING PARAMETER "BC"
                return converter.getPrivateKey(pki);
            }
        }
    

    I have no idea why this change made the trick. The BouncyCastles bcpkix-jdk15on can't be debugged (and no debug implementation is provided as for bcprov(-debug)-jdk15on). So I have no idea what it chaged in the internals.