DEV Community

Cover image for Building Own MAC: Part 1 - Encrypted, but Not Trusted
Dmytro Huz
Dmytro Huz

Posted on • Originally published at dmytrohuz.com

Building Own MAC: Part 1 - Encrypted, but Not Trusted

Huston, we have a problem

All right, now we have a super-duper cipher — AES.
(You don’t? 0_o That means you missed the previous series of articles where we, with paper, glue, and a bit of magic, built our own AES cipher from scratch. Stop reading. Go grab it: Building own AES)

We can encrypt any message, and only the person who knows the secret key can decrypt it.
The rest of the world would need a billion years to break it.

Does this mean we’re fine now?
Did we finish cryptography?
Is there nothing left to worry about?

Let’s make a small experiment.

We created an API for the bank. And one simplified endpoint looks like this:

POST: /api/transfer

BODY
user=$NAME
transaction=$AMOUNT
Enter fullscreen mode Exit fullscreen mode

Assume the bank can decrypt the request. Ignore key management for now — this story is not about that.

Allice wants to send Bob 10$.

She prepares the message:

user=Bob; transaction=10
Enter fullscreen mode Exit fullscreen mode

She encrypts the message with AES and sent to the bank.

POST: /api/transfer
BODY:
"a94f4aabdf..."
Enter fullscreen mode Exit fullscreen mode

In a few minutes she received a notification from the bank:

Your transaction of 10 000$ to the Bob is successful.

0_o…

That was…unexpected.

What just happened?

A hacker has been quietly listening to Alice’s network traffic for weeks.

He cannot decrypt anything. AES is doing its job.

But he can see many encrypted transfers going back and forth.

Over time, he notices something interesting:

small, predictable changes in the encrypted data cause predictable changes after decryption.

So he modifies the encrypted message — just a little.

The bank decrypts the message successfully.

And that is the problem.

The message was decrypted…successfully 🤦🏼‍♂️

But it was not authentic!!!

Yes, this example is simplified. Real attacks look different.

But the idea is very real and very dangerous.

We can no longer blindly trust encrypted data.

Houston, we have a problem.

It’s a Cipher… It’s a Hash… It’s SuperMAC

So.

We encrypted the message.

AES did its job.

The attacker still won.

That should feel wrong.

Let’s rewind a bit.


What encryption actually promised us

Encryption is very honest.

It promises exactly one thing:

“If you don’t know the key, you can’t read this message.”

That’s it.

No hidden features. No bonus guarantees.

And to be fair — it kept that promise perfectly.

The hacker never learned:

  • who Alice paid
  • how much she paid
  • what the message even says

AES was innocent.


Then why did everything break?

Because we quietly assumed something else.

We assumed that:

“If the message decrypts — it must be OK.”

And that assumption is false.

Encryption does not promise that:

  • the message wasn’t modified
  • the message was constructed intentionally
  • the message makes sense
  • the message is safe to execute

It only promises secrecy.

And yes — secrecy is still necessary.

We absolutely want the attacker to stay blind.

But secrecy alone is not enough.


What the bank actually needed

The bank needed one more answer.

Not a complicated one.

Just this:

“Was this message created by someone who knows the secret — and was it changed on the way?”

Encryption cannot answer that question.

So we don’t throw encryption away.

We add something next to it.

Not to hide the message.

But to protect it.


“Can’t we just hash it?”

At this point, many people say:

“Okay, fine. Let’s just hash the message.”

Hashes are nice.

  • change one bit → completely different output
  • fast
  • simple

But there’s a problem.

Hashes have no secrets.

Anyone can compute them.

Which means anyone can fake them.

So hashes alone don’t help.


So… SuperMAC?

Let’s make a small trick.

Alice is still sending a message to the bank.

She already knows how to encrypt it.

We don’t change that part.

Now we add one more step.

Before sending the message, Alice takes:

  • the message itself
  • a secret key (shared with the bank)

And she computes a small extra value.

Call it a tag.

This tag is not encrypted data.

It does not hide anything.

It’s just a short fingerprint that depends on:

  • the message
  • and the secret key

Now Alice sends two things to the bank:

  • the encrypted message
  • the tag

That’s it.


What does the bank do?

The bank receives:

  • the encrypted message
  • the tag

It decrypts the message.

Then it does the same computation itself:

  • same message
  • same secret key

If the newly computed tag matches the received one — good.

The message was not modified.

If it doesn’t — something is wrong.

The message is rejected.

No guessing.

No “maybe it’s fine”.

Just a hard yes or no.

So what is a MAC, finally?

This is it.

That tag we just computed

is the Message Authentication Code.

Nothing more.

Nothing less.

A MAC is:

  • a small piece of data
  • computed from the message
  • using a secret key
  • and verified on the other side

If the tag matches — the message is authentic.

If it doesn’t — the message is rejected.

That’s the whole mechanism.


Important clarification

A MAC does not replace encryption.

We still need encryption.

We still want secrecy.

The MAC adds something else.

It adds:

  • integrity — the message was not modified
  • authenticity — the message was created by someone who knows the secret

So the picture now looks like this:

  • Encryption hides the message
  • MAC protects the message

Two different tools.

Two different guarantees.

Used together.


Why this extra layer matters

Without a MAC:

  • modified messages can slip through
  • decryption can succeed on garbage
  • the system has no way to say “stop”

With a MAC:

  • every modification is detected
  • every forged message is rejected
  • the system can finally trust what it decrypts

This is why real systems don’t use encryption alone.

They use encryption + authentication.

Final thoughts — and what comes next

Over my career, I’ve learned one important thing.

If you can detect the problem and then define it correctly — you already solved about 60% of it.

Another 38% is designing the right architecture.

And the remaining 2% is implementation.

In this article, we focused on the biggest and most underestimated part of the problem:

trusting encrypted data.

We saw that encryption alone is not enough.

We saw why “it decrypts successfully” is not a security guarantee.

And we saw what kind of extra property we are missing.

Deliberately, we stopped there.


Why we stop here

Because a MAC is not a single algorithm.

It is not a cipher.

It is not a trick.

It is not one formula.

A MAC is a family of designs.

And before touching any code, it’s much more important to understand:

  • how MACs are built
  • which architectures exist
  • and which building blocks they rely on

That’s what the next article is about.


What’s next

In the next article, we will:

  • take a close look at the two main architectures used to build MACs
  • zoom in on the primitives used inside those architectures
  • understand why these designs work — and why others don’t

Only after that, in the final part, we’ll move to implementation.

We’ll:

  • implement the primitives (starting with SHA-256)
  • and then build a MAC on top of them
  • completely from scratch

No black boxes.

No “just trust the library”.

No magic.

Top comments (0)