Python 3 and a terminal. No account of yours is accessed, and nothing connects to the
internet. macOS and Linux include Python; on Windows install it from
python.org with “Add python.exe to PATH” ticked, then check with
python3 --version.
Nothing below asks for a real password, and you should not put one in these files. The exercise works on the relationships between accounts, not their contents. Step 5 ends with a ten-minute task on your own accounts, which is where the value actually is — the code exists to show you what you are looking for first.
Why Recovery Planning Matters
Account recovery is the part of security that people think about only after it is too late. You set up a strong password and enable two-factor authentication, and then one day your phone breaks, your laptop is stolen, or you simply forget a password. Without a recovery plan, your own security measures become the lock that keeps you out.
Recovery is also a common attack vector. Attackers who cannot crack your password will try to exploit your recovery options instead. If your recovery email is poorly secured, your phone number is vulnerable to SIM swapping, or your security questions have answers that can be found on social media, an attacker can bypass your password entirely by going through the recovery process.
You need recovery options that are accessible enough that you can use them in an emergency, but secure enough that an attacker cannot abuse them. Striking this balance is the core challenge of recovery planning. A recovery method that anyone can use is not security -- it is a backdoor.
Setting Up Recovery Email Securely
Most online services let you designate a recovery email address. If you forget your password or get locked out, the service sends a reset link to this address. This makes your recovery email critically important -- if an attacker gains access to it, they can take over any account that uses it for recovery.
Best Practices for Recovery Email
- Use a dedicated recovery email -- Consider having a separate email address that you use exclusively for account recovery, not for daily communication. This email is less likely to be targeted by phishing since nobody knows about it.
- Secure the recovery email itself -- This email account needs the strongest security you can apply: a unique strong passphrase, two-factor authentication with a hardware key or authenticator app, and its own recovery plan.
- Do not use your work email -- If you leave your job or lose access to your work email, you lose your recovery path. Always use a personal email account that you fully control.
- Keep the recovery email active -- Some email providers delete inactive accounts after a period. Log in periodically to prevent this from happening.
Recovery email chain -- avoid circular dependencies:
BAD (circular):
Gmail recovery → Outlook
Outlook recovery → Gmail
If either is compromised, the attacker gets both.
BETTER (hierarchical):
Daily email: Gmail
Recovery email: ProtonMail (dedicated, not used for anything else)
Recovery: backup codes stored offline
Daily email: Outlook
Recovery email: same ProtonMail account
Recovery: backup codes stored offline
The recovery email is the root of trust. It must be the
most secure account you own.
If Account A recovers through Account B, and Account B recovers through Account A, compromising either one gives an attacker both. Your recovery chain should be a hierarchy, not a loop. There should be one root account at the top that is secured independently with offline backup codes or a hardware key.
Phone Number Security for Recovery
Many services use your phone number as a recovery option, sending an SMS code to verify your identity. While convenient, phone-based recovery has significant vulnerabilities that you need to understand and mitigate.
The SIM Swapping Threat
In a SIM swap attack, an attacker contacts your phone carrier and convinces them to transfer your phone number to a new SIM card. They do this through social engineering (posing as you) or by bribing carrier employees. Once they control your number, they receive all your SMS messages -- including recovery codes and 2FA codes.
Protecting Your Phone Number
- Set a PIN or passphrase with your carrier -- Most carriers offer an account PIN or security passphrase that must be provided before making changes. Enable this immediately.
- Use a carrier that supports port-freeze -- Some carriers let you freeze number porting entirely, requiring in-person verification at a store to transfer your number.
- Prefer non-SMS recovery methods -- Where possible, choose email-based recovery or backup codes instead of SMS-based recovery.
- Consider a separate number for recovery -- A prepaid SIM or VoIP number used only for account recovery and known only to you is harder for an attacker to target.
SIM swapping is not theoretical. Cryptocurrency holders, public figures, and journalists have lost accounts and significant assets to SIM swap attacks. In 2019, the CEO of Twitter had his account compromised through a SIM swap. If your accounts protect anything of value, phone number security deserves serious attention.
Security Questions: Why They Are Weak
Many services still use security questions as a recovery mechanism. "What is your mother's maiden name?" "What city were you born in?" "What was the name of your first pet?" These questions were designed in an era before social media, when personal details were not publicly available. Today, they are one of the weakest forms of account protection.
The Problems with Security Questions
- Answers are publicly available -- Social media profiles, public records, and genealogy sites contain most of the information these questions ask for
- Answers are guessable -- There are only so many common pet names, cities, and car models. Attackers can brute-force the limited answer space
- Answers never change -- Your mother's maiden name is permanent. Once exposed in one breach, it compromises every account that uses the same question
- Answers are shared across services -- If you answer "Fluffy" on ten different sites, compromising one reveals the answer for all ten
How to Handle Required Security Questions
If a service forces you to set security questions, do not answer them truthfully. Instead, treat each answer as another password. Generate random strings or nonsensical answers and store them in your password manager alongside your password.
Security question handling:
Question: "What is your mother's maiden name?"
Real answer: Smith <-- Never use this
Secure answer: kJ7$mPqR2vN4 <-- Random string stored in password manager
Question: "What city were you born in?"
Real answer: Chicago <-- Never use this
Secure answer: correct-horse <-- Random passphrase stored in password manager
Store these in your password manager's notes field
for the corresponding account.
Truthful answers to security questions are not secrets. They are facts that can be researched, guessed, or extracted through casual conversation. Treat security question answers as passwords: random, unique per service, and stored in your password manager. The only person who should be able to answer them is someone with access to your vault.
Storing Backup Codes Safely
Backup codes are one-time codes provided by a service when you enable two-factor authentication. They are your emergency access method when you cannot use your normal 2FA device. Losing them can mean permanent account lockout. Leaving them exposed can mean an attacker bypasses your 2FA entirely.
Where to Store Backup Codes
- In your password manager -- The most practical option. Create a note or custom field in the account's entry and paste the backup codes there. They are encrypted alongside your passwords.
- On paper in a secure location -- Print your backup codes and store the paper in a fireproof safe, a safety deposit box, or a sealed envelope in a secure location. This ensures access even if all your devices are destroyed.
- In an encrypted offline file -- Store them in a VeraCrypt container, LUKS volume, or encrypted USB drive kept in a safe location. This is appropriate for people who manage many accounts.
Where NOT to Store Backup Codes
- Plain text file on your computer -- Malware, unauthorized access, or a stolen laptop exposes everything
- Screenshot in your photos -- Cloud-synced photos can be accessed if your cloud account is compromised
- Sticky note on your monitor -- Physically visible to anyone in the room
- Email to yourself -- If your email is compromised, your backup codes go with it
- Notes app without encryption -- Most default notes apps sync to the cloud without end-to-end encryption
Store backup codes in at least two separate locations. For example, in your password manager and printed on paper in a safe. If one storage method fails (corrupted vault, fire destroys the paper), you still have the other. Redundancy is the principle that keeps you safe from both attackers and accidents.
Trusted Contacts and Recovery Keys
Some services offer advanced recovery options beyond email and phone. These mechanisms are designed for scenarios where all your normal recovery methods are unavailable.
Trusted Contacts
Facebook and some other platforms let you designate trusted contacts who can help you regain access to your account. If you are locked out, the service provides parts of a recovery code to your trusted contacts, who then relay them to you. Choose contacts who are technically competent, trustworthy, and reachable through channels other than the platform itself (phone call, in person).
Recovery Keys
Apple, Google, and some other services offer a recovery key -- a long alphanumeric string that serves as a master override for account recovery. When you enable a recovery key, the service typically disables other recovery methods (like phone-based recovery), which makes your account more secure but also more dependent on that key. Lose it and you may be permanently locked out.
Apple Recovery Key example:
Recovery Key: XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX
This key is the ONLY way to recover your Apple ID if you
lose access to your trusted devices and phone number.
Store it:
1. In your password manager
2. Printed on paper in a safe
3. NEVER in iCloud (defeats the purpose)
Apple explicitly warns: "You're responsible for maintaining
access to your recovery key. If you lose it, you could be
permanently locked out of your account."
Enabling a recovery key often disables weaker recovery methods like phone-based recovery. This is more secure because it eliminates SIM swap attacks as a vector, but it also means losing the recovery key can result in permanent account loss. Only enable recovery keys if you have a reliable, redundant storage plan for the key itself.
Creating a Personal Recovery Plan
A recovery plan is a documented strategy that ensures you can regain access to your accounts under various disaster scenarios: lost phone, stolen laptop, house fire, forgotten passwords, or compromised accounts. The time to create this plan is now, not during the emergency.
Step 1: Inventory Your Accounts
List every account that matters to you. Your password manager likely has this list already. For each account, document what recovery options are configured: recovery email, phone number, backup codes, recovery key, trusted contacts, or security questions.
Step 2: Identify Single Points of Failure
Look for scenarios where losing one thing locks you out of everything. If your phone is your only 2FA device and your only recovery phone number, losing it compromises both your authentication and your recovery. Every critical path should have at least two independent ways to access it.
Step 3: Create Redundant Access Paths
- Password manager -- Memorize the master passphrase. Store an emergency copy in a sealed envelope in a safe. Consider keeping a backup of the vault file on an encrypted USB drive in a separate physical location.
- 2FA codes -- Store TOTP secrets in your password manager. Keep backup codes in two locations. If using hardware keys, register two keys per account.
- Recovery email -- Secure it independently with its own strong password, 2FA, and offline backup codes. This is the root of your recovery chain.
- Phone number -- Set a carrier PIN. Consider whether losing your phone number (theft, carrier issue) would lock you out of critical accounts.
Step 4: Test Your Recovery Plan
A recovery plan that has never been tested is just a theory. Periodically verify that your backup codes work, that your recovery email is still active and accessible, that you can access your password manager without your primary device, and that your physical backup (paper, USB drive) is still where you put it and still readable.
A good recovery plan should answer this question: if all your devices were destroyed today, could you regain access to your critical accounts using only what is stored in separate physical locations? If the answer is no, your plan has gaps that need to be addressed before disaster strikes.
Draw Your Own Recovery Map and Find the Master Key, in Five Steps
Almost everybody has thought about how strong their passwords are. Almost nobody has drawn the other diagram — the one showing which accounts can be used to reset which others — and that diagram is what an attacker works from. It usually has a single node at the bottom that controls everything above it, and for most people that node is not a password at all. In the next twenty minutes you will build the map, measure what falls when each piece is taken, find the lockout trap hiding inside a sensible-looking arrangement, and cut the map down to something you could survive. Every line of output below came from running these files.
Go: open a terminal in a folder you can write to — cd ~/Desktop on macOS or Linux, cd %USERPROFILE%\Desktop on Windows.
Do: save this as graph.py and run python3 graph.py.
"""Draw the map nobody draws: which account can reset which."""
# "X can be reset using Y"
RESETS = {
"bank": ["email", "phone"],
"shopping": ["email"],
"social": ["email", "phone"],
"work-vpn": ["work-email"],
"work-email": ["phone"],
"password-manager":["email"],
"email": ["phone"],
"phone": [], # the carrier account, reset in a shop
}
print("%-18s %s" % ("ACCOUNT", "CAN BE TAKEN OVER BY WHOEVER CONTROLS"))
print("-" * 62)
for account, sources in RESETS.items():
print("%-18s %s" % (account, ", ".join(sources) if sources else "(nothing listed)"))
def reachable_from(start):
"""Everything that falls if `start` is compromised."""
fallen, changed = {start}, True
while changed:
changed = False
for account, sources in RESETS.items():
if account not in fallen and any(s in fallen for s in sources):
fallen.add(account)
changed = True
return fallen - {start}
print()
for start in ("shopping", "email", "phone"):
fell = reachable_from(start)
print("compromise %-10s -> %d other account(s) fall: %s"
% (start, len(fell), ", ".join(sorted(fell)) or "none"))
You should see: one account bringing down all seven of the others:
ACCOUNT CAN BE TAKEN OVER BY WHOEVER CONTROLS
--------------------------------------------------------------
bank email, phone
shopping email
social email, phone
work-vpn work-email
work-email phone
password-manager email
email phone
phone (nothing listed)
compromise shopping -> 0 other account(s) fall: none
compromise email -> 4 other account(s) fall: bank, password-manager, shopping, social
compromise phone -> 7 other account(s) fall: bank, email, password-manager, shopping, social, work-email, work-vpn
The last three lines are the finding. Losing a shopping account costs you a shopping account. Losing the email account costs you four more, because everything is reset through it. And losing the phone number costs you everything, including the email that everything else depends on — because the phone sits underneath the email.
Notice that the strength of your passwords never entered the calculation. A recovery route is a way in that deliberately does not need the password, which is why the map matters independently of how good the passwords on it are.
If not: if compromise phone reports fewer than 7, an entry in
RESETS lost its dependency — the chain runs phone → email →
everything else, so removing any link shortens it. That experiment is worth doing deliberately
once you have seen the baseline.
Go: the same folder.
Do: save this as cycle.py and run python3 cycle.py.
"""The other failure: not being broken into, but being locked out."""
RESETS = {
"email-a": ["email-b"], # a very common arrangement
"email-b": ["email-a"],
}
def can_recover(lost, still_have):
return any(src in still_have for src in RESETS.get(lost, []))
print("email-a is recovered using:", RESETS["email-a"])
print("email-b is recovered using:", RESETS["email-b"])
print()
print("you lose access to email-a, but still have email-b")
print(" can you recover email-a?", can_recover("email-a", {"email-b"}))
print()
print("you lose access to BOTH (phone stolen, both apps on it)")
print(" can you recover email-a?", can_recover("email-a", set()))
print(" can you recover email-b?", can_recover("email-b", set()))
print()
def find_cycles(graph):
found = []
for a, sources in graph.items():
for b in sources:
if a in graph.get(b, []) and (b, a) not in found:
found.append((a, b))
return found
print("mutual-recovery pairs found:", find_cycles(RESETS))
print()
print("Two accounts that recover each other look like redundancy. They are")
print("a single point of failure wearing a disguise: nothing outside the")
print("pair can restore either one.")
You should see: a pair that recovers itself right up until it cannot:
email-a is recovered using: ['email-b']
email-b is recovered using: ['email-a']
you lose access to email-a, but still have email-b
can you recover email-a? True
you lose access to BOTH (phone stolen, both apps on it)
can you recover email-a? False
can you recover email-b? False
mutual-recovery pairs found: [('email-a', 'email-b')]
Two accounts that recover each other look like redundancy. They are
a single point of failure wearing a disguise: nothing outside the
pair can restore either one.
Two email accounts that each list the other as their recovery address is an extremely common arrangement, and it feels careful. It is not: nothing outside the pair can restore either member, so a single event that takes both — a stolen phone with both apps on it, a lost password-manager vault, one provider closing your account — leaves you with no route back at all.
A recovery route must terminate somewhere outside the system. Something on paper, something in a safe, or a person you trust — not another account of yours that depends on the one you have lost.
If not: if mutual-recovery pairs found prints an empty list, one direction of
the pair was removed from RESETS; both accounts must list each other for the cycle to
exist, which is exactly the shape being demonstrated.
Go: the same folder.
Do: save this as codes.py and run python3 codes.py.
"""Backup codes: the only recovery route that needs no other account."""
import hashlib, secrets
def make_codes(n=8):
codes = ["-".join(secrets.token_hex(2) for _ in range(2)) for _ in range(n)]
# The service stores hashes, never the codes themselves.
stored = {hashlib.sha256(c.encode()).hexdigest(): False for c in codes}
return codes, stored
def use(stored, code):
h = hashlib.sha256(code.encode()).hexdigest()
if h not in stored:
return "not a valid code"
if stored[h]:
return "REJECTED -- that code has already been used"
stored[h] = True
return "accepted"
codes, stored = make_codes()
print("codes issued :", len(codes))
print("what the site stores :", "sha256 hashes, one per code")
print("used so far :", sum(stored.values()))
print()
print("first code, first use :", use(stored, codes[0]))
print("first code, again :", use(stored, codes[0]))
print("second code :", use(stored, codes[1]))
print("a code you invented :", use(stored, "0000-0000"))
print()
print("codes remaining :", sum(1 for v in stored.values() if not v))
print()
print("Single-use is what makes a written-down code safe to write down:")
print("anyone who photographs the list gets codes that stop working as")
print("soon as you use them, and you can see how many are gone.")
You should see: a code working once and then never again:
codes issued : 8
what the site stores : sha256 hashes, one per code
used so far : 0
first code, first use : accepted
first code, again : REJECTED -- that code has already been used
second code : accepted
a code you invented : not a valid code
codes remaining : 6
Single-use is what makes a written-down code safe to write down:
anyone who photographs the list gets codes that stop working as
soon as you use them, and you can see how many are gone.
Backup codes are the only common recovery method with no dependency on another account, no phone number, and no third party who can be talked into anything. Two design details make them safe: the service stores hashes, so a breach of the service does not reveal working codes, and each code is single-use, so a photographed list decays as you use it and you can see how many remain.
Where to keep them: printed, on paper, somewhere you would keep a passport. Not in the password manager whose account they recover, and not photographed into the phone that they exist to replace — both of those recreate exactly the cycle from step 2.
If not: the codes are randomly generated so they differ every run and are deliberately not
printed; what must match is the sequence accepted, REJECTED,
accepted, not a valid code, and 6 remaining.
Go: the same folder.
Do: save this as harden.py and run python3 harden.py.
"""Cut the weak edges, then measure again."""
BEFORE = {
"bank": ["email", "phone"],
"shopping": ["email"],
"social": ["email", "phone"],
"work-vpn": ["work-email"],
"work-email": ["phone"],
"password-manager":["email"],
"email": ["phone"],
"phone": [],
}
# Three changes: remove SMS recovery everywhere it is optional, put the email
# account behind backup codes instead of the phone, and give work its own route.
AFTER = {
"bank": ["email"],
"shopping": ["email"],
"social": ["email"],
"work-vpn": ["work-email"],
"work-email": ["backup-codes"],
"password-manager":["backup-codes"],
"email": ["backup-codes"],
"phone": [],
"backup-codes": [], # paper, in a drawer
}
def blast(graph, start):
fallen, changed = {start}, True
while changed:
changed = False
for acct, sources in graph.items():
if acct not in fallen and any(s in fallen for s in sources):
fallen.add(acct)
changed = True
return len(fallen) - 1
print("%-16s %10s %10s" % ("IF THIS FALLS", "BEFORE", "AFTER"))
print("-" * 40)
for start in ("phone", "email", "work-email", "shopping"):
print("%-16s %10d %10d" % (start, blast(BEFORE, start), blast(AFTER, start)))
print()
print("The phone number stops being the master key. Losing it now costs you")
print("a phone, not every account you own.")
print()
print("Nothing here required new technology -- only removing recovery routes")
print("you never chose and probably never noticed were switched on.")
You should see: the phone number going from seven accounts to none:
IF THIS FALLS BEFORE AFTER
----------------------------------------
phone 7 0
email 4 3
work-email 1 1
shopping 0 0
The phone number stops being the master key. Losing it now costs you
a phone, not every account you own.
Nothing here required new technology -- only removing recovery routes
you never chose and probably never noticed were switched on.
Three changes did that, and none of them required buying anything. SMS recovery was removed wherever the service allowed it; the accounts that everything else depends on were put behind printed backup codes instead of the phone; and work was given a route that does not run through personal infrastructure.
Note that email still brings down three. That is unavoidable and correct —
email genuinely is the hub of a normal person's digital life. The goal is not to remove
the hub but to make sure nothing sits underneath it, so that protecting one
account actually protects everything.
If not: if phone still shows a non-zero figure in the AFTER column, an entry in
AFTER still lists phone as a source; the point of the exercise is that
no account does.
Go: the same folder.
Do: save this as drill.py and run python3 drill.py.
"""A recovery plan you have not tested is a guess."""
CHECKS = [
("Do you know where your backup codes are, right now?", False),
("Have you ever used one, to confirm they work?", False),
("If your phone vanished today, can you read your email?", False),
("Is your recovery email an address you still control?", True),
("Does any account list a phone number you no longer use?", True),
]
print("%-58s %s" % ("QUESTION", "TYPICAL ANSWER"))
print("-" * 76)
for q, typical in CHECKS:
print("%-58s %s" % (q, "yes" if typical else "no"))
unknown = sum(1 for _, t in CHECKS if not t)
print()
print("questions most people cannot answer:", unknown, "of", len(CHECKS))
print()
print("Every one of them is answerable in ten minutes, today, calmly.")
print("The alternative is answering them for the first time on the day")
print("something has already gone wrong.")
You should see: the three questions almost nobody can answer:
QUESTION TYPICAL ANSWER
----------------------------------------------------------------------------
Do you know where your backup codes are, right now? no
Have you ever used one, to confirm they work? no
If your phone vanished today, can you read your email? no
Is your recovery email an address you still control? yes
Does any account list a phone number you no longer use? yes
questions most people cannot answer: 3 of 5
Every one of them is answerable in ten minutes, today, calmly.
The alternative is answering them for the first time on the day
something has already gone wrong.
The last two rows are the ones that cause real trouble, because they fail silently for years. A recovery address you lost access to in 2019 is still listed as the way back into your bank; an old phone number is still on file and may by now belong to a stranger, who receives your reset codes.
Do this now, in this order, and it takes about ten minutes. Open your main email account's security settings and read the recovery email address and phone number listed there — not what you think they are, what the page says. Generate backup codes and print them. Then repeat for your password manager and your bank. That is the whole exercise, and it is the difference between having a recovery plan and having an assumption.
If not: this prints a fixed table; it is a checklist rather than a measurement, and the “typical answer” column is a claim about people in general, not about you. If you can answer all five, the ten minutes above are already done.
Without scrolling up: someone has a very strong, unique password on their bank account and two-factor authentication switched on, but their bank account can be reset via their email, and their email can be reset via SMS to their mobile number. How much of that protection survives an attacker who successfully transfers their phone number to a new SIM, and why? Answer: almost none of it. The recovery chain runs phone → email → bank, and every link is a route designed to work without the password, so the strength of the password is never tested — step 1 measured exactly this shape and found seven accounts falling from one node. Two-factor authentication on the bank helps only if it is a factor the attacker cannot also obtain; if the second factor is an SMS code it goes to the same hijacked number, and if it is an authenticator app that can be re-enrolled through the email account, that route is open too. What would survive is a factor bound to something the attacker does not hold, such as a hardware key or a passkey on a device they do not have, plus backup codes on paper — and, above all, removing SMS as a recovery route for the email, which is the change step 4 measured going from seven to zero.
Now do it without the page: rewrite RESETS in graph.py with your own accounts —
six or seven is enough, and you will have to look several of them up rather than remember them,
which is itself the point. Run it and read the last three lines. Then make one change to shorten
the worst chain, and run it again to confirm the number moved. Most people find the exercise takes
twenty minutes and changes something they had assumed was fine for years.
Summary
Secure account recovery is about balancing accessibility and security. You need recovery paths that work when you need them but cannot be exploited by attackers. Planning ahead and creating redundancy prevents both lockout and compromise.
- Recovery options are attack surfaces -- An insecure recovery email or vulnerable phone number can bypass even the strongest password and 2FA
- Use a dedicated, highly secured recovery email that is not used for daily communication and has no circular dependencies
- Protect your phone number from SIM swapping by setting a carrier PIN and preferring non-SMS recovery methods
- Never answer security questions truthfully -- treat answers as random passwords and store them in your password manager
- Store backup codes in at least two locations -- your password manager and a secure physical location
- Recovery keys increase security but demand responsibility -- losing them can mean permanent lockout
- Create and test a personal recovery plan that eliminates single points of failure and ensures access under disaster scenarios
Spend an hour this week reviewing the recovery options on your most critical accounts. Ensure your recovery email is secure, your backup codes are stored safely, and your password manager vault has an offline backup. That single hour of preparation can save you days of frustration and potential permanent data loss in the future.