Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Java HMAC field framing: prevent ambiguous concatenation

Last updated: 5 Oct 20264 min read
tutorial
AdvancedBy AITrove Editorial

A MAC authenticates bytes, so two distinct field tuples that serialize to the same bytes receive the same tag under one key.

Encode boundaries before signing

The pairs (AB, C) and (A, BC) both become ABC under naive concatenation. That is not a cryptographic collision; the application supplied identical input. Prefix each UTF-8 field with its byte length before updating the MAC so tuple boundaries survive serialization.

The fixture prints equality for both encodings. This format is only a local protocol sketch: a stored protocol also needs a version and a canonical definition for every field type. HMAC verification explains the tag's authentication role.

Validate lengths at ingestion

A length prefix does not limit the resource cost of an untrusted field. Cap each field before allocating or MACing it; reject a negative or oversized advertised length in a decoder before reading a body.

Working program

Java
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;

public class OrderTupleTag {
    static byte[] framed(String first, String second) throws Exception {
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
        DataOutputStream output = new DataOutputStream(bytes);
        for (String field : new String[] { first, second }) {
            byte[] encoded = field.getBytes(StandardCharsets.UTF_8);
            output.writeInt(encoded.length);
            output.write(encoded);
        }
        return bytes.toByteArray();
    }

    static byte[] tag(SecretKey key, byte[] message) throws Exception {
        Mac signer = Mac.getInstance("HmacSHA256");
        signer.init(key);
        return signer.doFinal(message);
    }

    public static void main(String[] args) throws Exception {
        SecretKey key = KeyGenerator.getInstance("HmacSHA256").generateKey();
        byte[] leftRaw = tag(key, "ABC".getBytes(StandardCharsets.UTF_8));
        byte[] rightRaw = tag(key, ("A" + "BC").getBytes(StandardCharsets.UTF_8));
        System.out.println("raw same=" + MessageDigest.isEqual(leftRaw, rightRaw));
        System.out.println("framed same=" + MessageDigest.isEqual(tag(key, framed("AB", "C")), tag(key, framed("A", "BC"))));
    }
}

Output

Output
raw same=true
framed same=false

Cost and ownership

Framing and MAC calculation each process the fields' bytes, so time and temporary memory grow with total encoded length. A streaming encoder can avoid the whole-message buffer when records are large; its verifier must use exactly the same field order and lengths.

Common Mistakes

  • Do not concatenate variable-length fields without a boundary rule.
  • Do not mistake an application serialization collision for a hash-function collision.
  • Do not accept an unbounded advertised field length.

Read next

Java HmacSHA256 verification: reject a changed message, Java ByteBuffer byte order: decode the protocol before reading integers, Java DataInputStream.readFully: EOF can leave partial bytes in the buffer, Java SHA-256: hash declared bytes, not an implicit string encoding.

java
cryptography
hmac-field-framing
Storage details