生产环境加密异常

Posted

技术标签:

【中文标题】生产环境加密异常【英文标题】:Encryption Exception in production environment 【发布时间】:2017-08-03 16:40:39 【问题描述】:

我在生产环境中遇到了加密问题。

以下代码是问题的根源:

public static void standardExceptionHandling(Exception exc, Logger alog) 
    StringWriter sw = new StringWriter();
    PrintWriter pw = new PrintWriter(sw);
    exc.printStackTrace(pw);
    alog.info(sw.toString()); /* Line 292 */


/**
 * Method that takes a key/value set, converts it into a standard web parameter string
 * and then encrypts the string.
 *
 * @param values the key value set
 * @return the encrypted string
 *
 */
public static String encrypt(Map<String, String> values) 
    StringBuilder unencrypted = new StringBuilder();
    boolean first = true;
    for (Map.Entry<String, String> value : values.entrySet()) 
        if (first) 
            first = false;
         else 
            unencrypted.append("&");
        
        unencrypted.append(value.getKey())
                .append("=")
                .append(value.getValue());
    

    try 
        Cipher cipher = Cipher.getInstance("AES");
        Key aesKey = new SecretKeySpec(AES_KEY.getBytes(), "AES");
        cipher.init(Cipher.ENCRYPT_MODE, aesKey);
        byte[] encrypted = cipher.doFinal(unencrypted.toString().getBytes("UTF8"));

        String enc = new sun.misc.BASE64Encoder().encode(encrypted);
        return enc;
     catch (Exception e) 
        standardExceptionHandling(e, log);
        return "";
    


/**
 * Method that takes an encrypted string containing a standard web parameter string
 * and converts it to a key/value set
 *
 * @param encrypted the encrypted string
 * @return the key value set
 */
public static Map<String, String> decrypt(String encrypted) 
    String decrypted = "";
    try 
        Cipher cipher = Cipher.getInstance("AES");
        Key aesKey = new SecretKeySpec(AES_KEY.getBytes(), "AES");
        cipher.init(Cipher.DECRYPT_MODE, aesKey);
        byte[] dec = new sun.misc.BASE64Decoder().decodeBuffer(encrypted);
        decrypted = new String(cipher.doFinal(dec), "UTF8");
     catch (Exception e) 
        standardExceptionHandling(e, log);
    

    Map<String, String> values = new HashMap<String, String>();
    for (String pair : decrypted.split("&")) 
        String[] split_pair = pair.split("=");
        String key, value;
        if (split_pair.length == 1) 
            key = split_pair[0];
            value = "";
         else if (split_pair.length == 2) 
            key = split_pair[0];
            value = split_pair[1];
         else if (split_pair.length > 2) 
            log.debug("Error when decrypting string, parameter found with more than 2 parts (" + pair + ")");
            continue;
         else 
            // We should never reach this, as it is impossible to split a string into a 0 length array.
            log.debug("The impossible happened, we split a String into a 0 length array (" + pair + ")");
            continue;
        
        // This is only reach when key and value have been initialised thank to the continue statements when we hit
        // an error state.
        values.put(key, value);
    
    return values;

加密文本时不会抛出异常。 加密后,通过 servlet 通过 URL 传回

即http://URL/servlet?hash=TVgYDScPqQ3eaJfEBmwuSCZUN0GCEshOBZ9H0YKH%2BS2b96BYdLRPBa6Dl8Z0mMmpPM1r2uxdv0sq%0A5BNbWTMcww%3D%3D

在我们的生产服务器上,我们得到以下异常:

INFO 10/mar/2017 06:02:37 [http-nio-80-exec-104] (HelperMethods.java:292) - javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:913)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:824)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:436)
at javax.crypto.Cipher.doFinal(Cipher.java:2165)
at com.gg.gomoenterprise.utils.HelperMethods.decrypt(HelperMethods.java:349)
at com.gg.gomomessenger.servlets.EmailServlet.doPost(EmailServlet.java:60)
at com.gg.gomomessenger.servlets.EmailServlet.doGet(EmailServlet.java:46)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:622)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:616)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:518)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1091)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:673)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1526)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1482)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
DEBUG 10/mar/2017 06:02:37 [http-nio-80-exec-104] (EmailServlet.java:88) - com.gg.gomomessenger.commons.exceptions.EmailSeverletException: <h1>We do not see your email address in this program. You may have already unsubscribed or be subscribed with a different email address. If you need assistance, email support@mymobilemsg.com.</h1>
at com.gg.gomomessenger.commons.exceptions.EmailSeverletException.dataMisingException(EmailSeverletException.java:39)
at com.gg.gomomessenger.servlets.EmailOptInServlet.handleOpt(EmailOptInServlet.java:91)
at com.gg.gomomessenger.servlets.EmailServlet.doPost(EmailServlet.java:82)
at com.gg.gomomessenger.servlets.EmailServlet.doGet(EmailServlet.java:46)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:622)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:616)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:518)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1091)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:673)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1526)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1482)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)

这只发生在生产上,而不是本地、开发或登台。 所有服务器运行 Tomcat 8.0.26

本地 JRE java版本“1.8.0_121” Java(TM) SE 运行时环境 (build 1.8.0_121-b13) Java HotSpot(TM) 64 位服务器 VM(内部版本 25.121-b13,混合模式)

暂存 JRE java版本“1.8.0_66” Java(TM) SE 运行时环境 (build 1.8.0_66-b17) Java HotSpot(TM) 64 位服务器 VM(内部版本 25.66-b17,混合模式)

Stagin JRE java版本“1.8.0_66” Java(TM) SE 运行时环境 (build 1.8.0_66-b17) Java HotSpot(TM) 64 位服务器 VM(内部版本 25.66-b17,混合模式)

生产 JRE java版本“1.8.0_60” Java(TM) SE 运行时环境 (build 1.8.0_60-b27) Java HotSpot(TM) 64 位服务器 VM(内部版本 25.60-b23,混合模式)

这可能是代码问题吗?

【问题讨论】:

调试时数据长度是多少(dec的长度),它必须是块大小的倍数,AES为16字节?错误消息:“使用填充密码解密时,输入长度必须是 16 的倍数”说明了一切。 您在生产环境中使用该代码?它使用欧洲央行模式......你应该解决这个问题。 @LukePark,不是很有帮助....你能告诉我有什么用吗?而不是仅仅说“修复它”此外,这并不是一个主要的加密算法,它只是为了掩盖用户的 4 个参数。如果我们不加密,我们将传递 4 个参数,它们都需要相互匹配(所有不同但链接)。如果他们没有正确链接,页面将不会加载! 我有点希望您能够自行研究欧洲央行为何不好。如果可以,请改用 GCM。 不要使用 ECB 模式,它不安全,请参阅ECB mode,向下滚动到企鹅。而是使用带有随机IV的CBC模式,只需在加密数据前加上IV用于解密即可,它不需要保密。 【参考方案1】:

我注意到这个例子:

TVgYDScPqQ3eaJfEBmwuSCZUN0GCEshOBZ9H0YKH%2BS2b96BYdLRPBa6Dl8Z0mMmpPM1r2uxdv0sq%0A5BNbWTMcww%3D%3D

采用 URL 编码并包含换行符 (%0A)。 Base64 需要去掉 URL 编码。

Base64 编码也不应该添加换行符,应该有一个选项。无论如何,它们都需要在 Base64 解码中被剥离。

正确解码为十六进制:

4D58180D270FA90DDE6897C4066C2E48265437418212C84E059F47D18287F92D9BF7A05874B44F05AE8397C67498C9A93CCD6BDAEC5DBF4B2AE4135B59331CC3

【讨论】:

点...我错过了 URLDecode 即使加密执行 URLEncode,谢谢!但是,您对为什么它在其他环境中使用完全相同的设置有任何解释吗? 运气。根据加密数据,Base64 编码中可能没有“+”和“/”,并且根据长度,UIRL 编码中没有尾随“=”字符。 Base64解码函数一般会去掉换行0x0A。 虽然,我真的很喜欢你的回答,并且绝对认为这是一个问题....我真的很惊讶它只发生在 PROD 中只是运气。然而,另一方面,我们没有进行太多测试,......我们做了一个紧急修复以返回以删除加密。也许有些哈希值已经在生产中起作用了。 哈希也不行,它是base64编码。【参考方案2】:

正如@zaph 指出的那样,解析密文可能存在问题,这导致密文长度不同于块大小的倍数(16 bytes)。正确编解码密文后,应该可以验证密文长度为n * 16字节。

此外,获得Cipher 类实例Cipher.getInstance("AES"); 的行并不理想。 AES 在 Java 中默认为 AES/ECB/PKCS5Padding(但这也可能因提供者而异)。 ECB 是不推荐/弃用/损坏的block cipher mode。除了在不知不觉中选择了糟糕的操作模式之外,这里的模棱两可是不必要的。评估可用模式并明确选择合适的模式(可能是GCM,或者可能是CBCCTR 与强大的HMAC 函数结合使用)。

您还应该为每个消息加密使用唯一且不可预测的初始化向量 (IV)。

【讨论】:

请说明 ECB 模式在哪里被弃用/损坏的分组密码模式。 @zaph:欧洲央行不安全是常识:crypto.stackexchange.com/questions/20941/… 也许这些术语很强大,但根据您的威胁模型,我支持它们。最明显的例子是"penguin image",但有many other examples 和ECB 模式对现代密码分析不安全。 欧洲央行不安全并不意味着它已被弃用/损坏。 Deprecated is 是一个正式的声明,broken 表示曾经有一段时间它没有被破坏。有些用例可以使用 ECB 模式。如果您查看我的评论,您会发现我经常在哪里反对欧洲央行模式,并且似乎通常被那些不了解的人以及不合适的情况所使用。 刚刚与我的加密货币 SME 交谈,并被告知 ECB 并未被弃用,目前仍在关键应用程序中使用。我的观点是要小心措辞 WRT 加密。弃用的例子是 NIST 正式弃用使用双长度密钥的 DES 和 3DES。

以上是关于生产环境加密异常的主要内容,如果未能解决你的问题,请参考以下文章

在生产环境中使用 Fluent Asserts 增强默认的 .NET LINQ 不良异常?

Java项目生产环境部署,遇到FTP连接加密服务器的踩坑及爬坑过程

记录一次生产环境中Redis内存增长异常排查全流程!

只需4个步骤,分析解决在生产环境下JVM内存泄露问题

asp.net生产环境和开发环境的错误日志包装策略

项目实现远程Debug调试