Random HttpMessageNotReadableException while posting Base64 encoded string of an encrypted (AES GCM)

5/13/2020

I have a very weird problem where about %0,5 of the POST requests to my Spring Boot backend fails with HttpMessageNotReadableException. I will be describing the issue step by step and my findings and sample code.

Some information about the technologies being used:

Client side:

  • Requests are posted via UnityWebRequest class from both iOS and Android

  • Payload size is around 1-2 Kb

  • Payloads are AES GCM encrypted then Base64 encoded strings (this is a product requirement)

  • No chunked transfer

Server side:

  • Spring Boot app with embedded Tomcat server. No custom configurations

  • Deployed via Kubernetes on AWS Classic Load Balancer, again no custom configurations, all default

When I inspect the headers of a failing request I saw that all headers are present as expected, Content-Length is greater than 0 but controllers are not invoked, HttpMessageNotReadableException is thrown after the timeout period (60seconds default). Content-Length is a number divisible by 4.

What I think happening is server thinks the client will send more data and waits until the timeout then closes the connection and throws the exception.

Unfortunately I can't ever reproduce this and I have no idea how it is happening.

Related code sections:

Client Encryption -> Base64

public static string Encrypt(string rawData)
{
   if (string.IsNullOrEmpty(rawData))
       return string.Empty;

   var plainText = Encoding.UTF8.GetBytes(rawData);
   var cipherText = InternalEncrypt(plainText, Encoding.UTF8.GetBytes(Key));
   return cipherText == null ? string.Empty : Convert.ToBase64String(cipherText);
}

private static byte[] InternalEncrypt(byte[] rawMessage, byte[] key, byte[] nonSecretPayload = null)
{
    //User Error Checks
    if (key == null || key.Length != KeyBitSize / 8)
    {
        Log.Instance.Error(String.Format("Key needs to be {0} bit!", KeyBitSize));
        return null;
    }

    if (rawMessage == null || rawMessage.Length == 0)
    {
        Log.Instance.Error(String.Format("Invalid message!", KeyBitSize));
        return null;
    }

    //Non-secret Payload Optional
    nonSecretPayload = nonSecretPayload ?? new byte[] { };

    //Using random nonce large enough not to repeat
    System.Random rnd = new System.Random();
    var nonce = new byte[NonceBitSize / 8];
    rnd.NextBytes(nonce);

    var cipher = new GcmBlockCipher(new AesFastEngine());
    var parameters = new AeadParameters(new KeyParameter(key), MacBitSize, nonce, nonSecretPayload);
    cipher.Init(true, parameters);

    //Generate Cipher Text With Auth Tag
    var cipherText = new byte[cipher.GetOutputSize(rawMessage.Length)];
    var len = cipher.ProcessBytes(rawMessage, 0, rawMessage.Length, cipherText, 0);
    cipher.DoFinal(cipherText, len);

    //Assemble Message
    using (var combinedStream = new MemoryStream())
    {
        using (var binaryWriter = new BinaryWriter(combinedStream))
        {
            //Prepend Authenticated Payload
            binaryWriter.Write(nonSecretPayload);
            //Prepend Nonce
            binaryWriter.Write(nonce);
            //Write Cipher Text
            binaryWriter.Write(cipherText);
        }
        return combinedStream.ToArray();
    }
}

Sending the request:

UnityWebRequest request = method == HttpMethod.Get
                ? UnityWebRequest.Get(url)
                : new UnityWebRequest(url);

if (method == HttpMethod.Post)
{
    request.method = "POST";
    request.uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(postData));
    request.downloadHandler = new DownloadHandlerBuffer();
}

request.SetRequestHeader("Content-Type", "text/plain");
request.timeout = 5;
yield return request.SendWebRequest();

Spring controller definition:

@PostMapping(value = "/", consumes = MediaType.TEXT_PLAIN_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
    public ResponseEntity<String> post(@RequestBody String body)

Like I said previously, about %0,5 to %1 of the requests fail, not even reaching controller and throws HttpMessageNotReadableException.

Any idea what I should look at to further debug this issue?

-- arkenthera
aes-gcm
base64
kubernetes
spring-boot
unity3d

0 Answers