Skip to content

Getting Started with Signal

💡
Before you start

Python 3 and a terminal. Steps 1–4 need no phone, no Signal account and no network — the model runs entirely on your own machine.

Step 5 needs Signal installed and a second person to compare with. It is the only step that leaves the terminal.

You need no cryptography background. One idea carries the whole lab: a fingerprint is a short value derived from a key, so two people holding the same pair of keys always compute the same fingerprint — and anyone standing between them cannot.

Why Signal?

Signal is a free, open-source messaging app that provides end-to-end encryption for all messages, calls, and file transfers. Unlike most messaging apps, Signal collects virtually no metadata about its users.

End-to-end encryption Only you and the recipient can read messages. Not even Signal's servers can access them.
Open source Signal's code is publicly auditable. Security researchers can verify it works as claimed.
Non-profit Signal is operated by the Signal Foundation, not a company selling your data.

Installing Signal

On Your Phone

1
Download Signal from the App Store (iOS) or Google Play Store (Android)
2
Open Signal and enter your phone number for verification
3
Enter the verification code sent via SMS
4
Set up your profile with a name (and optionally a photo)
5
Create a PIN to secure your Signal account (this helps recover your account if you change phones)

On Desktop

Signal Desktop links to your phone and syncs messages:

1
Download Signal Desktop from signal.org for Windows, macOS, or Linux
2
Open Signal on your phone, go to Settings > Linked Devices > Link New Device
3
Scan the QR code shown on your desktop with your phone's camera

Essential Privacy Settings

Open Signal Settings on your phone to configure these important options:

Disappearing Messages

Set a default timer for messages to automatically delete after a set period. Go to Settings > Privacy > Default timer for new chats and choose a duration (e.g., 1 week or 4 weeks).

Screen Security

Enable Settings > Privacy > Screen Security to prevent Signal content from appearing in the app switcher and block screenshots within the app.

Registration Lock

Enable Settings > Account > Registration Lock to prevent someone from re-registering your phone number on another device without your PIN.

💡
Remember your PIN!

If you enable Registration Lock and forget your PIN, you may be locked out of your own account for up to 7 days.

Using Signal Effectively

  • Group chats: Create encrypted group conversations for family, friends, or teams
  • Voice and video calls: All calls are end-to-end encrypted by default
  • File sharing: Send documents, photos, and files securely
  • Note to Self: Send messages to yourself as an encrypted notepad
  • Verify safety numbers: Tap a contact's name to compare safety numbers in person, confirming there is no interception

Signal vs Other Messengers

Signal vs WhatsApp WhatsApp uses Signal's protocol but is owned by Meta, which collects extensive metadata. Signal collects almost none.
Signal vs Telegram Telegram chats are NOT end-to-end encrypted by default (only "Secret Chats" are). Signal encrypts everything by default.
Signal vs iMessage iMessage is encrypted but only works in the Apple ecosystem. Signal works on all platforms.

Now Do It Yourself: Build a Safety Number and Watch It Catch an Interception

Signal's safety number is the one feature that turns "the app says it is encrypted" into something you can actually check — and it is the feature almost nobody uses. In twenty minutes you can build a working model of it, catch a simulated interception with it, and see precisely why the warning it produces is so often ignored.

You need Python 3. Steps 1–4 need no phone, no Signal account and no network — they are arithmetic. This is a faithful model of the structure, not Signal's exact algorithm: real Signal iterates the hash 5,200 times over real Curve25519 identity keys, and the numbers here will not match any real conversation. Every output below came from running these exact commands.

1
Build the safety number both phones compute

Go: open a terminal, then mkdir siglab and cd siglab. Signal is not needed for steps 1–4 and nothing is sent anywhere.

Do: save this as safetynumber.py. It is a working model of how Signal turns two identity keys into the 60 digits your phone shows:

import hashlib, sys

def fingerprint(identity_key: bytes, phone: str) -> str:
    """A person's half of a safety number: 30 digits derived from their key + identifier.

    Signal iterates the hash 5200 times; 5000 here keeps the lab fast. The structure is the
    point: the digits come from the KEY, so a different key gives different digits.
    """
    h = b"\x00\x00" + identity_key + phone.encode()
    for _ in range(5000):
        h = hashlib.sha512(h + identity_key).digest()
    return "".join(f"{int.from_bytes(h[i:i+5], 'big') % 100000:05d}" for i in range(0, 30, 5))

def safety_number(a_key, a_phone, b_key, b_phone) -> str:
    """Both people compute this and must get the SAME answer. Sorting makes it order-independent."""
    halves = sorted([fingerprint(a_key, a_phone), fingerprint(b_key, b_phone)])
    joined = "".join(halves)
    return " ".join(joined[i:i+5] for i in range(0, len(joined), 5))

if __name__ == "__main__":          # only runs when you execute THIS file directly
    alice_key = b"A" * 32          # stand-ins for real identity keys
    bob_key   = b"B" * 32
    print("What ALICE's phone shows:")
    print(" ", safety_number(alice_key, "+15550001", bob_key, "+15550002"))
    print("\nWhat BOB's phone shows:")
    print(" ", safety_number(bob_key, "+15550002", alice_key, "+15550001"))

Run it:

python3 safetynumber.py

You should see: two phones, computing separately, arriving at exactly the same number:

What ALICE's phone shows:
  04889 70742 81764 26723 23436 65923 67623 17767 77424 37247 58785 47306

What BOB's phone shows:
  04889 70742 81764 26723 23436 65923 67623 17767 77424 37247 58785 47306

Neither phone asked a server what the number should be. Each derived it from the two keys it already holds, and sorting the halves makes the result independent of who is asking. That is why the number can be compared out loud — there is nothing to look up and nothing to trust.

If not: if it takes more than a second or two, that is normal — the hash runs 5,000 times on purpose. If you get ModuleNotFoundError, you are in a different folder from the file.

2
Change one byte of a key and watch the digits move

Go: the same folder.

Do: save this as onebyte.py. It computes the number twice, with Bob’s key differing by a single byte at the very end:

from safetynumber import safety_number

alice = b"A" * 32
bob   = b"B" * 32
bob2  = b"B" * 31 + b"C"       # exactly one byte different

print("Bob key ending ...BBBB:")
print(" ", safety_number(alice, "+15550001", bob, "+15550002"))
print()
print("Bob key ending ...BBBC  (one byte changed):")
print(" ", safety_number(alice, "+15550001", bob2, "+15550002"))

Then run it:

python3 onebyte.py

You should see: the first half identical — that is Alice, who did not change — and Bob's half completely different:

Bob key ending ...BBBB:
  04889 70742 81764 26723 23436 65923 67623 17767 77424 37247 58785 47306

Bob key ending ...BBBC  (one byte changed):
  04889 70742 81764 26723 23436 65923 12991 09980 38610 94821 99705 60423

One byte in, thirty digits out, sharing nothing with the original. There is no "close" here — a safety number either matches or it does not, so comparing the first few groups and stopping is not a shortcut, it is the whole check skipped.

If not: if both lines are identical, the b2 line lost its final +b'C' when copying. The two keys must genuinely differ.

3
Put an interceptor in the middle

Go: the same folder.

Do: save this as mitm.py. Mallory relays messages between Alice and Bob, holding a separate encrypted conversation with each:

from safetynumber import safety_number

alice_key   = b"A" * 32
bob_key     = b"B" * 32
mallory_key = b"M" * 32     # the interceptor's own key

print("Alice believes she is talking to Bob, but Mallory is in the middle.")
print("Alice's phone was handed MALLORY's key and labelled it 'Bob':")
print(" ", safety_number(alice_key, "+15550001", mallory_key, "+15550002"))
print("\nBob's phone was handed MALLORY's key and labelled it 'Alice':")
print(" ", safety_number(bob_key, "+15550002", mallory_key, "+15550001"))
print("\nBoth chats are encrypted. Both show a padlock. Neither number matches the other.")

Run it:

python3 mitm.py

You should see: two different numbers where there should be one:

Alice believes she is talking to Bob, but Mallory is in the middle.
Alice's phone was handed MALLORY's key and labelled it 'Bob':
  04889 70742 81764 26723 23436 65923 73155 49416 17918 17607 29185 54540

Bob's phone was handed MALLORY's key and labelled it 'Alice':
  67623 17767 77424 37247 58785 47306 96140 08900 01723 96453 28547 77638

Both chats are encrypted. Both show a padlock. Neither number matches the other.

🔴 Read the last line again. Both conversations are genuinely end-to-end encrypted. Both phones display every reassuring sign the app has. Encryption was never the thing that failed — the keys were swapped before it started, and the only evidence is that two numbers which should be identical are not. Nothing inside either phone can see this. Only comparing the numbers with each other can.

If not: if both numbers come out the same, mallory_key is equal to one of the others — check it reads b"M" * 32.

4
Find the reason people ignore the warning

Go: the same folder.

Do: save this as reinstall.py. Nobody is attacking here — Bob simply got a new phone, which gives Signal a new identity key:

from safetynumber import safety_number

alice_key = b"A" * 32
bob_key   = b"B" * 32
bob_new   = b"B2" + b"B" * 30      # Bob reinstalled Signal: brand-new identity key

print("Before Bob reinstalled:")
print(" ", safety_number(alice_key, "+15550001", bob_key, "+15550002"))
print("\nAfter Bob reinstalled (new phone, or restored from backup):")
print(" ", safety_number(alice_key, "+15550001", bob_new, "+15550002"))
print("\nSignal shows 'Your safety number with Bob has changed'.")
print("Identical to what an interception looks like. The app cannot tell them apart.")

Run it:

python3 reinstall.py

You should see: the number changes, exactly as it did under attack:

Before Bob reinstalled:
  04889 70742 81764 26723 23436 65923 67623 17767 77424 37247 58785 47306

After Bob reinstalled (new phone, or restored from backup):
  04889 70742 81764 26723 23436 65923 76980 49552 25797 27048 08046 62552

Signal shows 'Your safety number with Bob has changed'.
Identical to what an interception looks like. The app cannot tell them apart.

🔴 This is why the warning gets dismissed. A new phone and an interception produce the same alert, and the innocent explanation is overwhelmingly the common one — so people learn to tap through it. The app genuinely cannot distinguish the two, and that is not a flaw to be fixed: no software can tell whether a key changed because your friend upgraded or because someone stepped between you. Only your friend can, and only over a channel the attacker does not control.

If not: if the two numbers match, bob_new is not actually different — it must start b"B2" so the first bytes differ from bob_key.

5
Do it for real, with someone you actually message

Go: Signal on your phone. Open a conversation, tap the contact's name at the top, then View Safety Number.

Do: compare that 60-digit number with theirs over a channel Signal does not control — in person, or on a phone call where you recognise their voice. If you are together, scanning each other's QR code does the same comparison instantly. Then tap Mark as verified. Reading it aloud is fine; the number is not a secret and revealing it costs nothing.

You should see: a green checkmark on that conversation. From then on, Signal warns you if the number ever changes — and you now know from step 4 exactly what that warning does and does not mean.

This step needs the app and a second person, so it was not re-run for this page. Steps 1–4 were, and they are what the reasoning rests on.

🔴 Do not verify over Signal itself. If someone is in the middle, they are relaying those messages too, and can simply send each of you the number you expect. The comparison is only worth anything on a channel the attacker does not sit on — which is the entire reason the feature exists.

If not: if the numbers differ and neither of you changed phones, stop messaging anything sensitive and re-establish contact another way. If your contact recently reinstalled, that explains it — but confirm that by voice, not by asking in the chat, because in the case you are checking for, the chat is exactly what is compromised.

🎉
Check yourself before moving on

Signal tells you your safety number with a friend has changed. You message them in that same Signal chat and they reply "yes, I got a new phone." Are you now safe? Answer: No — you have learned nothing. If someone is intercepting, they are relaying that chat too and can send exactly that reply. Step 3 showed both sides seeing a normal, encrypted, padlocked conversation throughout. The reply has to come over a channel the attacker does not control: their voice on a call, or in person.

Now do it without the page: explain to someone why a safety number can be read aloud in a crowded room without weakening anything, while a password cannot. If you can say why one is a fingerprint and the other is a secret, you understand what the feature is for.

Summary

In this tutorial, you learned:

  • Why Signal is recommended for private communication
  • How to install Signal on phone and desktop
  • Essential privacy settings to configure
  • How to use Signal's features effectively
  • How Signal compares to other messaging apps
🎉
You are now communicating securely!

Encourage your contacts to join Signal too. Encryption only works when both sides use it.