How Does Signing a Message Prevent Tampering? (And Why Encryption Alone Isn't Enough)
Problem
When I first learned about cryptography, I thought encrypting a message was enough to keep it secure. But then I encountered a troubling question: what if someone changes the encrypted message?
I assumed encryption protected everything—confidentiality, integrity, authenticity. That assumption was wrong.
Here’s the core problem: encryption only keeps your message secret. It does NOT prevent someone from tampering with it.
Environment
- Python 3.10+ (cryptography library)
- Node.js 18+ (crypto module)
- Go 1.21+ (crypto/hmac package)
What happened?
I was building a system to send financial instructions between services. My plan was:
- Encrypt the message with the recipient’s public key
- Send the encrypted message
- Recipient decrypts with their private key
Simple, right? But I realized something was missing. If an attacker intercepted my encrypted message, they could:
- Replace it with a previously captured valid encrypted message (replay attack)
- Flip bits in predictable ways if the encryption mode wasn’t authenticated
- Resend the same message multiple times
Even though they couldn’t read the message, they could still cause damage by manipulating it.
Here’s why encryption alone fails:
┌─────────────────┐│ Attacker ││ (Can't read) ││ But CAN: ││ - Replace ││ - Replay ││ - Bit-flip │└────────┬────────┘ │ ▼┌─────────────────┐ ┌─────────────────┐│ Encrypted │ ──► │ Recipient ││ Message │ │ Decrypts: OK ││ (Tampered!) │ │ But WRONG msg! │└─────────────────┘ └─────────────────┘The key insight from a Reddit discussion:
“When you sign a message you don’t encrypt it. It remains in plain text. You just attach a hash with it that the receiver can use to verify that the message was not changed.” — plastikmissile
And another comment clarified the real purpose:
“It doesn’t prevent. It makes CHECKING for tampering evident.” — kschang
How to solve it?
The solution is digital signatures. But here’s what surprised me: signatures don’t prevent tampering either—they make it detectable.
How Digital Signatures Work
Sender side:
1. Message: "Transfer $100 to Alice"2. Hash the message: SHA-256("Transfer $100 to Alice") = 0x7f3a...3. Encrypt hash with private key: RSA_Sign(private_key, 0x7f3a...) = SIGNATURE4. Send: Message + SIGNATUREReceiver side:
1. Receive: "Transfer $100 to Alice" + SIGNATURE2. Hash the received message: SHA-256("Transfer $100 to Alice") = 0x7f3a...3. Decrypt signature with public key: RSA_Verify(public_key, SIGNATURE) = 0x7f3a...4. Compare: 0x7f3a... == 0x7f3a... → VERIFIEDIf someone tampers:
Attacker changes message to: "Transfer $10000 to Bob"1. New hash: SHA-256("Transfer $10000 to Bob") = 0xb4c1...2. Attacker tries to use old signature: SIGNATURE3. Decrypt signature: RSA_Verify(public_key, SIGNATURE) = 0x7f3a...4. Compare: 0xb4c1... != 0x7f3a... → TAMPERING DETECTEDThe attacker can’t forge a new signature because they don’t have the sender’s private key.
Python Example: RSA Signatures
from cryptography.hazmat.primitives import hashesfrom cryptography.hazmat.primitives.asymmetric import rsa, padding
# Generate key pairprivate_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)public_key = private_key.public_key()
# Sign a messagemessage = b"Transfer $100 to Alice"signature = private_key.sign( message, padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH ), hashes.SHA256())
# Verify the signature (receiver side)try: public_key.verify( signature, message, padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH ), hashes.SHA256() ) print("Signature is valid - message is authentic and intact")except Exception: print("Signature verification failed - message may be tampered")
# Tampering attempttampered_message = b"Transfer $10000 to Bob"try: public_key.verify( signature, tampered_message, # Different message! padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH ), hashes.SHA256() )except Exception: print("Tampering detected! Signature doesn't match.")JavaScript/Node.js Example: ECDSA Signatures
const crypto = require('crypto');
// Generate ECDSA key pairconst { privateKey, publicKey } = crypto.generateKeyPairSync('ec', { namedCurve: 'secp256k1'});
// Sign a messageconst message = 'Transfer $100 to Alice';const sign = crypto.createSign('SHA256');sign.update(message);sign.end();const signature = sign.sign(privateKey, 'hex');
console.log('Signature:', signature);
// Verify the signatureconst verify = crypto.createVerify('SHA256');verify.update(message);verify.end();const isValid = verify.verify(publicKey, signature, 'hex');console.log('Valid:', isValid); // true
// Tampering detectionconst tamperVerify = crypto.createVerify('SHA256');tamperVerify.update('Transfer $10000 to Bob'); // Different messagetamperVerify.end();const isTampered = tamperVerify.verify(publicKey, signature, 'hex');console.log('Tampered message valid:', isTampered); // falseThe reason
I think the key reason for the confusion is that people conflate three different security properties:
| Property | What It Means | Encryption | Digital Signature |
|---|---|---|---|
| Confidentiality | Only authorized parties can read | Yes | No (message is plaintext) |
| Integrity | Message wasn’t altered | No | Yes |
| Authenticity | Proof of who sent it | No | Yes |
| Non-repudiation | Sender can’t deny sending | No | Yes |
Encryption keeps secrets. Signatures prove authenticity and detect tampering. They’re complementary, not alternatives.
Common Mistakes I Made
Mistake 1: Confusing signing with encrypting
- Signing: Encrypts hash with your private key, message stays plaintext
- Encrypting: Encrypts message with recipient’s public key
Mistake 2: Thinking signatures prevent tampering
- They don’t prevent—they detect
- The message can still be changed, but verification will fail
Mistake 3: Using encryption without authentication
- ECB, CBC, CTR modes alone don’t provide integrity
- Always use authenticated encryption (AES-GCM, ChaCha20-Poly1305)
Mistake 4: Not checking the signature
- Receiving a signed message without verifying is useless
- Always verify before trusting
Summary
In this post, I explained why encryption alone cannot prevent message tampering and how digital signatures make tampering detectable. The key point is that signatures create a tamper-evident seal by encrypting a hash of your message with your private key—anyone can verify the message came from you and wasn’t altered, but they can’t forge a new signature without your private key.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
- 👨💻 Reddit Discussion: How does signing a message prevent tampering?
- 👨💻 NIST Digital Signature Standard (FIPS 186-5)
- 👨💻 RFC 8017 - PKCS #1: RSA Cryptography Specifications
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments