SSLException:无效填充解包TLS应用程序数据

时间:2013-01-11 13:24:52

标签: java ssl jsse sslengine

几个月前,我开发了一个SSL web server using NIO and the SSLEngine。 GET请求与小POST请求一样工作得很好(约10KB以下)。但是,当我发布大于此的任何内容时,我会遇到零星的SSLException异常。

例如,如果我发布一个小图像(~16KB),我可以看到3个代表应用程序数据的TLS记录。第一个是HTTP标头,另外两个包含有效负载。这是分组的小小的一种感觉:

1: 613 bytes
2: 16341 bytes
3: 549 bytes

在我的代码中,我调用SSLEngine来解包/解密TLS记录(应用程序数据)。 SSLEngine经常在尝试打开第二条记录时窒息。这是错误:

javax.net.ssl.SSLException: Invalid padding
    at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Unknown Source)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.fatal(Unknown Source)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.readRecord(Unknown Source)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.readNetRecord(Unknown Source)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.unwrap(Unknown Source)
    at javax.net.ssl.SSLEngine.unwrap(Unknown Source)
    at javaxt.http.servlet.HttpServletRequest.getApplicationData(HttpServletRequest.java:1793)
    ...

    Caused by: javax.crypto.BadPaddingException: Invalid TLS padding: 199
    ...

这是抛出错误的代码。

private byte[] getApplicationData() throws IOException {

  //Read the next 5 bytes from the socket channel. This should contain TLS
  //record information.
    if (recordHeader==null){
        recordHeader = ByteBuffer.allocateDirect(5);
        read(recordHeader);
    }

  //Verify that the message contains application data
    recordHeader.rewind();
    if(recordHeader.get()!=23) throw new IOException();

  //Get the length of the TLS record
    recordHeader.position(3);
    int recordLength = Integer.parseInt(getHex(recordHeader) + getHex(recordHeader), 16);
    recordHeader.rewind();

  //Read the TLS record
    ByteBuffer recordData = ByteBuffer.allocateDirect(recordLength);
    read(recordData);
    recordData.rewind();

  //Merge the TLS header and record data into a single buffer
    ByteBuffer tlsRecord = ByteBuffer.allocateDirect(recordLength+recordHeader.capacity());
    tlsRecord.put(recordHeader);
    tlsRecord.put(recordData);
    tlsRecord.rewind();

  //Decrypt the application data
    ByteBuffer output = ByteBuffer.allocateDirect(tlsRecord.capacity());
    SSLEngineResult serverResult = sslEngine.unwrap(tlsRecord, output);
    runDelegatedTasks(serverResult, sslEngine);


    byte[] arr = new byte[output.position()];
    output.rewind();
    output.get(arr);


  //Clean up
    recordHeader.clear();
    recordData.clear();
    tlsRecord.clear();
    output.clear();
    recordHeader = recordData = tlsRecord = output = null;


  //Return array
    return arr;
}

SSLException抛出此行:

SSLEngineResult serverResult = sslEngine.unwrap(tlsRecord, output);

偶尔会发生此错误。有时我看到它,有时我不看。通常情况下,如果我确实看到错误并只是重新发布数据(例如刷新浏览器),那么一切都很好(没有错误)。

我不是SSL专家,所以我不知道如何最好地调试问题。我很确定我正在正确地调用unwrap方法。第二条记录是否有点腐败?密码是否已更改,我需要重新启动握手?遇到此错误后该怎么办?

结合其他观点:

  • 我正在使用捆绑的Sun / Oracle JDK 6和Java安全套接字扩展(JSSE)。
  • 使用Mobile Safari,iOS 6时,我发现这个错误很多。我很少遇到FireFox这个问题。

提前致谢!

2 个答案:

答案 0 :(得分:3)

你这样做是错的。

您应该在需要传入数据时调用unwrap()。 unwrap()会告诉您在必要时从网络读取(BUFFER_UNDERFLOW)通过其返回状态;它也可能告诉你运行一个任务,做一个包装等等。你应该自己自己读取网络,查看TLS记录,读取长度,读取数据,重新组装它们,以及喂他们到SSLEngine。它已经做到了这一切。你的代码是颠倒的。

同样在写作时,只需调用wrap()。它会告诉您何时写入网络(BUFFER_OVERFLOW),运行任务等。

答案 1 :(得分:-1)

经过长时间的中断后,我终于解决了这个问题。问题是,当我读取TLS记录时,我假设我的read()方法返回了所有请求的字节(即recordData ByteBuffer已满)。那不是真的。结果,sslEngine.unwrap()调用被轰炸了。

要解决此问题,我只需将其替换为:

  //Read the TLS record
    ByteBuffer recordData = ByteBuffer.allocateDirect(recordLength);
    read(recordData)

有了这个:

  //Read the TLS record
    ByteBuffer recordData = ByteBuffer.allocateDirect(recordLength);
    int ttl = read(recordData);
    if (ttl<recordLength){
        while (ttl<recordLength){
            recordData.position(ttl);
            ByteBuffer tmp = ByteBuffer.allocateDirect(recordLength-ttl);
            ttl += read(tmp);
            recordData.put(tmp);
        }
        recordData.rewind();
    }

一旦recordData ByteBuffer被填满,SSLEngine unwrap()方法就能完美运行。