How to Encrypt a Seed Phrase: AES, XOR and Steganography
You can encrypt a seed phrase with AES under a key derived from a strong password, split it into parts with XOR, or hide it inside an ordinary file with steganography. None of these makes the secret disappear: each moves the risk to a password, a key or a method that you have to back up as carefully as the phrase itself. For most people, a BIP39 passphrase, SLIP-39 shares or a multisig wallet does the same job with wallet support and less room for error. This guide shows how each method works, with Python examples run on public test phrases, and when an encrypted copy makes sense next to a paper or metal backup.
Encryption Methods at a Glance
| Method | Protects against | What you must also keep | Main risk |
|---|---|---|---|
| AES with a password | Someone who finds the file or the printout | The password and the encryption parameters | A weak or forgotten password |
| XOR split (Seed XOR) | Someone who finds one of the parts | Every part, since all are needed | Losing any single part |
| Steganography | Someone who doesn’t know where to look | The carrier file and the method | No secrecy once the carrier is found |
| BIP39 passphrase | Someone who finds the words | The passphrase | Forgetting it; a typo opens another wallet |
| SLIP-39 shares | Loss or theft of fewer shares than the threshold | Enough shares to reach the threshold | Fewer wallets support it |
Why Encrypt a Seed Phrase at All?
Your seed phrase is a human-readable form of the master key that controls your cryptocurrency. Unlike traditional banking systems, which offer account recovery and fraud protection, a blockchain determines ownership solely by control of the private keys.
According to Chainalysis (December 2025), more than $3.4 billion was stolen from January to early December 2025, and compromises of personal wallets reached 158,000 incidents with at least 80,000 victims, who lost $713 million. The immutable nature of blockchain means that once assets are transferred, they cannot be reclaimed without the new owner’s consent.
A plain backup on paper or metal is readable by anyone who finds it. Encryption is one way to make a found copy useless, alongside the built-in options compared at the end of this guide.
What Encryption Does and Doesn’t Protect
Encryption transforms readable data (plaintext) into an encoded format (ciphertext) that can only be read after decryption with the right key. Applied to a seed phrase, it protects the stored copy: a thief who finds the ciphertext learns nothing without the key. It does not protect the phrase while you decrypt and use it, since malware on that computer sees everything, and it adds a key that you can lose. The three methods in this guide work differently:
- AES (Advanced Encryption Standard): a symmetric cipher whose key comes from your password
- XOR: a bitwise operation that, with a random key as long as the secret, splits it into parts that are useless alone
- Steganography: hiding the data inside an ordinary file, so that nobody knows there is a secret to look for
How to Encrypt a Seed Phrase With AES
AES is the standard symmetric cipher, adopted by governments and security professionals worldwide. NIST selected the Rijndael algorithm for it in October 2000, after a public competition, and published the standard, FIPS 197, in November 2001. AES supports 128-, 192- and 256-bit keys and continues to withstand scrutiny from the cryptographic community, so when you encrypt a seed phrase, the cipher is rarely the weak point: the password is.
Step by Step
- Choose a strong password: long, random and stored apart from the ciphertext.
- Generate a random salt, so the same password never produces the same key twice.
- Derive the key with a slow function. OWASP’s password storage guidance recommends 600,000 iterations for PBKDF2-HMAC-SHA256, or a memory-hard function such as scrypt or Argon2id.
- Encrypt in an authenticated mode. GCM (Galois/Counter Mode) provides both confidentiality and integrity, so a damaged or altered ciphertext fails to decrypt instead of returning wrong words.
- Store the ciphertext with the salt, nonce, tag and parameters. You need all of them to decrypt.
# Python example using PyCryptodome (simplified)
from Crypto.Cipher import AES
from Crypto.Hash import SHA256
from Crypto.Protocol.KDF import PBKDF2
from Crypto.Random import get_random_bytes
import base64
# User's secret password
password = "your_strong_password_here"
# Seed phrase to encrypt (the public BIP39 test phrase)
seed_phrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
# Generate a salt and derive a 256-bit key
salt = get_random_bytes(16)
key = PBKDF2(password, salt, dkLen=32, count=600_000, hmac_hash_module=SHA256)
# Encrypt
cipher = AES.new(key, AES.MODE_GCM)
nonce = cipher.nonce
ciphertext, tag = cipher.encrypt_and_digest(seed_phrase.encode())
# Encode for storage
encoded_salt = base64.b64encode(salt).decode()
encoded_nonce = base64.b64encode(nonce).decode()
encoded_ciphertext = base64.b64encode(ciphertext).decode()
encoded_tag = base64.b64encode(tag).decode()
# Store these values securely
print(f"Salt: {encoded_salt}")
print(f"Nonce: {encoded_nonce}")
print(f"Ciphertext: {encoded_ciphertext}")
print(f"Tag: {encoded_tag}")
Tools That Do It for You
Several well-vetted tools can apply AES encryption to a seed phrase:
| Tool | Platform | Key Features | Security Considerations |
|---|---|---|---|
| OpenSSL | Cross-platform (CLI) | Industry standard, highly configurable | Command line complexity, requires technical knowledge |
| VeraCrypt | Windows, macOS, Linux | Creates encrypted containers, plausible deniability | Ensure clean shutdown to prevent memory attacks |
| KeePassXC | Cross-platform | Password manager with secure notes | Master password becomes a single point of failure |
| GnuPG | Cross-platform | Industry standard for file encryption | Key management complexity |
When using AES, the security of your encrypted seed phrase is only as strong as your encryption password. A 256-bit AES encryption with a weak password might be easier to break than a 128-bit encryption with a strong passphrase.
Wallet standards apply the same recipe to single keys. BIP38 encrypts a Bitcoin private key with scrypt and AES-256 into a string that starts with 6P (BIP38 specification), and Ethereum’s keystore files hold a key encrypted with AES-128-CTR under a key derived with scrypt or PBKDF2 (Web3 Secret Storage).
How Does XOR Splitting Work?
The XOR (exclusive OR) operation combines each bit of the secret with a bit of a key: equal bits give 0, different bits give 1. Applying the same key twice returns the original, (A ⊕ B) ⊕ B = A, so one function both encrypts and decrypts. With a truly random key as long as the secret, used only once, the result is a one-time pad: the ciphertext alone reveals nothing.
That condition is easy to break. A short password repeated over the text of a phrase is weak, because the plaintext is predictable: lowercase words from a public list, separated by spaces. Anyone who knows a few of those characters can work out the matching part of the key. The example below shows the operation itself; for a real backup, XOR the entropy rather than the text, as Seed XOR does.
# Python example of XOR on text (illustration only: a repeated key is weak)
def xor_bytes(message, key):
# Repeat the key to the length of the message
extended_key = (key * (len(message) // len(key) + 1))[:len(message)]
return bytes(m ^ k for m, k in zip(message, extended_key))
seed_phrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about".encode()
key = "YourSecretKey12345".encode()
encrypted = xor_bytes(seed_phrase, key)
print(f"Encrypted: {encrypted.hex()}")
decrypted = xor_bytes(encrypted, key) # the same function decrypts
print(f"Decrypted: {decrypted.decode()}")
Seed XOR: Splitting the Entropy Into Valid Phrases
Seed XOR, created by Coinkite for its COLDCARD devices and documented at seedxor.com, applies XOR to the entropy a phrase encodes instead of its text. You XOR your phrase’s entropy with the entropy of one or more random phrases, and every part is again a normal BIP39 phrase of 12 or 24 words. All parts are needed to rebuild the original, each part on its own works as a small decoy wallet, and the arithmetic can even be done on paper with a worksheet.
Three official BIP39 test vectors show the idea, because 7f XOR 80 gives ff in every byte:
part 1 legal winner thank year wave sausage worth useful legal winner thank yellow 7f7f…7f
part 2 letter advice cage absurd amount doctor acoustic avoid letter advice cage above 8080…80
XOR zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong ffff…ff
In real use, one part must come from a secure random generator; these patterned vectors only make the arithmetic visible, and all three phrases are public. In Python, with the reference implementation of BIP39:
# Seed XOR with python-mnemonic: combine two parts into the original phrase
from mnemonic import Mnemonic
m = Mnemonic("english")
part_1 = m.to_entropy("legal winner thank year wave sausage worth useful legal winner thank yellow")
part_2 = m.to_entropy("letter advice cage absurd amount doctor acoustic avoid letter advice cage above")
combined = bytes(a ^ b for a, b in zip(part_1, part_2))
print(m.to_mnemonic(combined)) # zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong
To split a phrase instead, draw a random part with os.urandom(16) (32 bytes for 24 words), turn it into words with to_mnemonic, and XOR it with your phrase’s entropy to get the other part. The BIP39 visualizer shows how entropy maps to words.
Advantages and Limitations
XOR splitting offers several benefits:
- Simplicity: easy to understand and to perform with minimal tools, even on paper
- Perfect secrecy: with a truly random part of equal length, used once, a single part reveals nothing about the original
- No special software: each Seed XOR part restores in any BIP39 wallet
However, it also has important limitations:
- Every part is required: lose one and the original is gone, unlike SLIP-39, where any threshold of shares is enough
- No integrity check: a miscopied part silently yields a different, valid phrase
- Pattern vulnerability: a reused or predictable key leaks the secret
Can You Hide a Seed Phrase With Steganography?
While encryption makes your seed phrase unreadable, steganography hides the fact that a secret exists at all. It conceals the data inside ordinary-looking carriers such as text, images, audio or video files. Its advantage is that it attracts no attention; its weakness is that once someone suspects the carrier and the method, the data is easy to extract, so encrypt the phrase before you hide it. For images, Subhedar and Mankar’s survey of image steganography (Computer Science Review, 2014) reviews the main techniques. Methods that change the least significant bits of pixels do not survive lossy recompression, for example when a messaging app converts the picture to JPEG, so keep the original file.
Text-Based Steganography
Text-based steganography methods are particularly relevant for seed phrases, as they allow you to hide your mnemonic within seemingly innocent text content. These techniques include:
-
Linguistic steganography — Encoding information within the structure of natural language
-
Format-based methods — Using spacing, formatting, or invisible characters
-
Acrostic techniques — Where the first letters of words or lines spell out the hidden message
-
Semantic methods — Embedding information within the meaning of text
A practical approach for seed phrase protection is to create a meaningful text where specific words or patterns encode your seed phrase words. This could be a story, poem, or recipe where predetermined positions contain the actual seed words or clues to them.
# Example of a simple acrostic approach in Python
def create_acrostic(seed_words, template_text):
"""Create a text where the first letter of each word reveals the seed phrase."""
seed_indices = [0] * len(seed_words)
result_text = []
# Find words in template text that start with the same letter as each seed word
words = template_text.split()
seed_index = 0
for i, word in enumerate(words):
if seed_index < len(seed_words) and word[0].lower() == seed_words[seed_index][0]:
seed_indices[seed_index] = i # position of the word carrying the next letter
result_text.append(word.upper())
seed_index += 1
else:
result_text.append(word)
if seed_index < len(seed_words):
raise ValueError("The template text is too short for this seed phrase")
# One letter is only a clue: BIP39 words are unique by their first four letters
return " ".join(result_text), seed_indices
Should You Combine Several Layers?
Combining methods creates defense in depth only if each layer has its own, separately stored secret. A layered approach might look like:
- First layer: AES encryption with a strong password
- Second layer: XOR of the ciphertext with a separate random key of the same length, stored elsewhere
- Third layer: steganography to hide the result in an ordinary file
- Fourth layer: physical security for the carrier and the keys, possibly across multiple locations
Every layer is also one more secret to lose. An XOR layer with a short repeating key adds little on top of AES; with a random key of full length kept in a different place, it turns the backup into a 2-of-2 split.
# Conceptual Python example of a multi-layered approach
import os
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
# Original seed phrase (the public BIP39 test phrase)
seed_phrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
# Layer 1: AES encryption
password = "strong_password_for_aes_layer"
salt = os.urandom(16)
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=600_000,
)
key = kdf.derive(password.encode()) # 32 raw bytes = an AES-256 key
# Perform AES-GCM encryption (simplified)
iv = os.urandom(12)
cipher = Cipher(algorithms.AES(key), modes.GCM(iv))
encryptor = cipher.encryptor()
aes_ciphertext = encryptor.update(seed_phrase.encode()) + encryptor.finalize()
tag = encryptor.tag # needed for decryption, detects tampering
# Layer 2: XOR with a random key of the same length, stored separately
xor_key = os.urandom(len(aes_ciphertext))
xor_result = bytes(c ^ k for c, k in zip(aes_ciphertext, xor_key))
# Layer 3: Steganography (conceptual)
def embed_in_image(data, image_path, output_path):
"""Conceptual function to embed data in an image using LSB steganography"""
# A real implementation would modify pixel values
print(f"Embedded {len(data)} bytes in {image_path}, saved to {output_path}")
return output_path
# Complete the multilayer protection
output_file = embed_in_image(xor_result, "innocent_looking_image.png", "protected_image.png")
print(f"Seed phrase protected with multiple layers and stored in: {output_file}")
print("Store separately: AES password, salt, IV, GCM tag, XOR key and the steganography method")
When implementing multilayered security, keep a recovery path for each layer and document your method securely: the more complex the scheme, the greater the risk of locking yourself out of your own assets.
What Can Go Wrong With an Encrypted Backup?
While encryption adds valuable protection to your seed phrase, it also introduces new risks that must be carefully managed.
Security vs. Recoverability Trade-off
Every layer of protection adds complexity to the recovery process. This fundamental trade-off means that as security increases, the risk of permanently losing access due to forgotten passwords, lost keys, or procedural errors also increases.
Research by Chainalysis estimated in 2018 that between 2.3 and 3.7 million bitcoin, or roughly 13-22% of the coins then in circulation, were likely lost forever due to lost private keys, representing billions in inaccessible assets.
Key Considerations
-
Inheritance planning — Consider how authorized parties could access your assets in case of emergency or death; see crypto inheritance
-
Memory dependency — Avoid systems that rely solely on memorized information
-
Technological obsolescence — Ensure your encryption methods and tools will remain accessible in the future
-
Testability — Regularly verify that your recovery process works as expected
| Risk Factor | Description | Mitigation Strategy |
|---|---|---|
| Password loss | Forgetting encryption passwords or keys | Create secure password hints, consider secure sharing mechanisms |
| Process complexity | Too many steps leading to procedural errors | Document processes clearly, test recovery periodically |
| Single points of failure | Critical components with no redundancy | Create backup systems and redundant storage |
| Software dependency | Reliance on specific software that may become unavailable | Prefer open standards and widely adopted tools |
Passphrase, SLIP-39 or Your Own Encryption: Which to Use?
Wallet standards already offer encryption-like protection that the wallet itself understands:
- A BIP39 passphrase is mixed into the seed calculation, so the words alone open a different wallet. The BIP39 specification notes that every passphrase generates a valid seed, which gives plausible deniability but also means that a typo opens an empty wallet.
- SLIP-39 splits the secret into shares with a threshold, such as any 2 of 3, and can also encrypt the master secret with a passphrase: its specification uses a four-round Feistel network with PBKDF2 as the round function, and every passphrase again produces a valid seed.
- Multisig removes the single secret altogether: several seed phrases, stored apart, are needed to spend.
Home-made encryption makes the most sense as an extra copy on top of such a setup, for example an AES-encrypted file in a second location. For your main backup, a method that any compatible wallet can restore is safer than one that depends on your own scripts.
For Beginners
-
Start simple — One strong method, such as AES with a password kept apart from the ciphertext
-
Test thoroughly — Practice the entire encryption and decryption process with a test phrase and small amounts first
-
Document clearly — Create simple, clear recovery instructions and store them securely
-
Use established tools — Avoid creating custom systems until you’re experienced
For Advanced Users
-
Implement defense in depth — Combine methods, each with an independent key stored separately
-
Create geographic redundancy — Store encrypted backups in multiple physical locations
-
Consider key sharding — Split encryption keys using Shamir’s Secret Sharing
-
Establish inheritance protocols — Create secure mechanisms for authorized access in emergencies
The 3-2-1 rule of general data backup applies to encrypted seed phrases too: keep at least three copies, on two different types of media, with one copy stored off-site.
Universal Recommendations
-
Never store unencrypted seed phrases digitally — Not even temporarily
-
Use air-gapped devices — Perform encryption on devices without network connectivity
-
Test recovery periodically — Verify that your process works at least annually
-
Keep physical security — Encryption doesn’t eliminate the need for physical protection
-
Balance security and usability — The most secure system is worthless if too complex to use