MetaMask employs a robust encryption mechanism to protect users' private keys and seed phrases. By analyzing MetaMask's implementation, we can understand its core security mechanisms.
MetaMask uses the following combination of cryptographic techniques:
- Algorithm: PBKDF2 (Password-Based Key Derivation Function 2)
- Hash Function: SHA-256
- Iterations: 600,000 (significantly increased from 10,000)
- Derived Key Length: 256 bits (32 bytes)
# Key derivation example code
from Crypto.Protocol.KDF import PBKDF2
from Crypto.Hash import SHA256
from base64 import b64decode
# MetaMask derives encryption key from user password
salt = b64decode(vault["salt"])
password = "your_secure_password" # ⚠️ For demonstration only! Should be obtained securely, never use weak passwords
iterations = 600000 # Current iteration count used by MetaMask
# Derive 32-byte encryption key
encryption_key = PBKDF2(
password,
salt,
dkLen=32, # Key length
count=iterations, # Iteration count
hmac_hash_module=SHA256
)Why is the iteration count important?
- Higher iteration counts make brute-force attacks more time-consuming
- 600,000 iterations means each password attempt requires 600,000 hash calculations
- Significantly reduces cracking speed (e.g., from 57,736 H/s to 968 H/s)
- Algorithm: AES (Advanced Encryption Standard)
- Mode: GCM (Galois/Counter Mode)
- Key Length: 256 bits
- Authentication Tag: 16 bytes (128 bits)
from Crypto.Cipher import AES
# Decryption process example
iv = b64decode(vault["iv"]) # Initialization Vector
encrypted_data = b64decode(vault["data"])
# AES-GCM appends a 16-byte authentication tag to the encrypted data
tag = encrypted_data[-16:] # Extract authentication tag
ciphertext = encrypted_data[:-16] # Extract ciphertext
# Decrypt using AES-GCM
cipher = AES.new(encryption_key, AES.MODE_GCM, iv)
plaintext = cipher.decrypt_and_verify(ciphertext, tag)Advantages of AES-GCM:
- Confidentiality: Encrypts data to prevent unauthorized access
- Integrity: Detects any tampering through authentication tag
- Authentication: Ensures data comes from a trusted source
- Efficiency: Can be parallelized, suitable for large amounts of data
MetaMask stores encrypted wallet information in a JSON structure called a "vault":
{
"data": "base64-encoded encrypted data...",
"iv": "3/XB0MgtPNz8yFQ0GhkRZQ==",
"salt": "jReEwKFNQ5y3hfwOSQtTALWcKtlB26y5Tu1mk7LkJA8=",
"keyMetadata": {
"algorithm": "PBKDF2",
"params": {
"iterations": 600000
}
}
}Field Descriptions:
- data: Wallet data (seed phrase, private keys, etc.) encrypted with AES-GCM + authentication tag
- iv: Initialization vector, ensures the same plaintext produces different ciphertext in different encryptions
- salt: Password salt, ensures the same password derives different keys for different users
- keyMetadata: Contains metadata about the key derivation algorithm, particularly the iteration count
User Password + Salt (randomly generated)
↓
PBKDF2-SHA256
(600,000 iterations)
↓
256-bit Encryption Key
↓
AES-GCM Encryption
(using random IV)
↓
Wallet Data (seed phrase/private keys) → Encrypted Data + Auth Tag
↓
Store Locally
macOS:
~/Library/Application Support/Google/Chrome/Default/Local Extension Settings/nkbihfbeogaeaoehlefnkodbefgpgknn/
Windows:
%USERPROFILE%\AppData\Local\Google\Chrome\User Data\Default\Local Extension Settings\nkbihfbeogaeaoehlefnkodbefgpgknn\
Linux:
~/.config/google-chrome/Default/Local Extension Settings/nkbihfbeogaeaoehlefnkodbefgpgknn/
Where nkbihfbeogaeaoehlefnkodbefgpgknn is the MetaMask extension ID.
By analyzing MetaMask's encryption mechanism, we can derive the following best practices for private key management, applicable to various bot scripts and automated applications.
❌ Wrong Approach:
# Never do this!
PRIVATE_KEY = "0x1234567890abcdef..."✅ Correct Approach:
import os
from dotenv import load_dotenv
# Read from environment variables or encrypted config files
load_dotenv()
PRIVATE_KEY = os.getenv('BOT_PRIVATE_KEY')Following MetaMask's approach, encrypt stored private keys:
from Crypto.Protocol.KDF import PBKDF2
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from Crypto.Hash import SHA256
import base64
import json
def encrypt_private_key(private_key: str, password: str) -> dict:
"""
Encrypt private key using MetaMask-like method
"""
# Generate random salt and IV
salt = get_random_bytes(32)
iv = get_random_bytes(16)
# Derive key from password using PBKDF2
key = PBKDF2(
password,
salt,
dkLen=32,
count=600000, # Use high iteration count
hmac_hash_module=SHA256
)
# Encrypt using AES-GCM
cipher = AES.new(key, AES.MODE_GCM, nonce=iv)
ciphertext, tag = cipher.encrypt_and_digest(private_key.encode())
# Return encrypted data structure
return {
'data': base64.b64encode(ciphertext + tag).decode(),
'iv': base64.b64encode(iv).decode(),
'salt': base64.b64encode(salt).decode(),
'iterations': 600000
}
def decrypt_private_key(encrypted_data: dict, password: str) -> str:
"""
Decrypt private key
"""
# Decode base64
salt = base64.b64decode(encrypted_data['salt'])
iv = base64.b64decode(encrypted_data['iv'])
data = base64.b64decode(encrypted_data['data'])
# Separate ciphertext and tag
tag = data[-16:]
ciphertext = data[:-16]
# Derive key
key = PBKDF2(
password,
salt,
dkLen=32,
count=encrypted_data['iterations'],
hmac_hash_module=SHA256
)
# Decrypt
cipher = AES.new(key, AES.MODE_GCM, nonce=iv)
plaintext = cipher.decrypt_and_verify(ciphertext, tag)
return plaintext.decode()| Solution | Security | Convenience | Use Case |
|---|---|---|---|
| Environment Variables | ⭐⭐ | ⭐⭐⭐⭐ | Development, temporary scripts |
| Encrypted Config Files | ⭐⭐⭐ | ⭐⭐⭐ | Small production environments |
| Key Management Service (AWS KMS, Azure Key Vault) | ⭐⭐⭐⭐⭐ | ⭐⭐ | Enterprise production |
| Hardware Security Module (HSM) | ⭐⭐⭐⭐⭐ | ⭐ | High-security requirements |
# .env file (must be added to .gitignore)
BOT_PRIVATE_KEY=your_encrypted_private_key_here
ENCRYPTION_PASSWORD=your_strong_password# bot.py
import os
from dotenv import load_dotenv
load_dotenv()
# Get encrypted private key
encrypted_key = os.getenv('BOT_PRIVATE_KEY')
password = os.getenv('ENCRYPTION_PASSWORD')
# Decrypt and use
private_key = decrypt_private_key(encrypted_key, password).gitignore:
.env
*.key
config/secrets/
import boto3
import base64
import os
def get_private_key_from_kms():
"""
Retrieve and decrypt private key from AWS KMS
"""
# Get region configuration from environment variable
kms_client = boto3.client('kms', region_name=os.getenv('AWS_REGION', 'us-east-1'))
# Encrypted private key stored in KMS
encrypted_key = os.getenv('ENCRYPTED_PRIVATE_KEY')
# Decrypt using KMS
response = kms_client.decrypt(
CiphertextBlob=base64.b64decode(encrypted_key),
KeyId=os.getenv('KMS_KEY_ID') # Get KMS key ID from environment variable
)
return response['Plaintext'].decode()import hvac
def get_private_key_from_vault():
"""
Retrieve private key from HashiCorp Vault
"""
client = hvac.Client(url='https://vault.example.com')
client.token = os.getenv('VAULT_TOKEN')
# Read private key from Vault
secret = client.secrets.kv.v2.read_secret_version(
path='bot/ethereum/private-key'
)
return secret['data']['data']['key']Regular key rotation reduces long-term exposure risk:
import schedule
import time
def rotate_private_key():
"""
Private key rotation process
"""
# 1. Generate new key pair
new_private_key = generate_new_key()
# 2. Transfer assets to new address
transfer_assets(old_key, new_key)
# 3. Update private key in configuration
update_encrypted_config(new_private_key)
# 4. Securely destroy old private key
securely_delete(old_private_key)
print("Key rotation completed")
# Rotate every 30 days
schedule.every(30).days.do(rotate_private_key)import time
class KeyManager:
"""
Key manager implementing access control
Note: This is a conceptual example. Actual use requires full implementation of all helper functions.
"""
def __init__(self, audit_logger):
self._private_key = None
self._logger = audit_logger
self._access_count = 0
self._max_access = 1000 # Maximum access count
def get_key(self, requester: str) -> str:
"""
Get private key (with auditing)
"""
# Log access
self._logger.log({
'action': 'key_access',
'requester': requester,
'timestamp': time.time(),
'ip': self._get_client_ip() # To be implemented: get client IP
})
# Check access count
self._access_count += 1
if self._access_count > self._max_access:
raise RuntimeError("Key access limit exceeded, possible leak")
return self._private_key
def sign_transaction(self, tx_data: dict) -> str:
"""
Sign transaction (without exposing private key)
"""
# Use private key internally for signing, don't return the key itself
signature = self._sign_with_key(self._private_key, tx_data) # To be implemented: signing function
self._logger.log({
'action': 'transaction_signed',
'tx_hash': self._hash_transaction(tx_data), # To be implemented: hash function
'timestamp': time.time()
})
return signature
def _get_client_ip(self) -> str:
"""Get client IP (implement based on actual environment)"""
import socket
return socket.gethostbyname(socket.gethostname())
def _sign_with_key(self, key: str, data: dict) -> str:
"""Sign with private key (implement based on specific blockchain)"""
# Ethereum example: from web3 import Web3; w3.eth.account.sign_transaction(...)
# Bitcoin example: from bitcoin import ecdsa_sign
# Solana example: from solana.keypair import Keypair
raise NotImplementedError("Implement signing logic for target blockchain, see libraries above")
def _hash_transaction(self, tx_data: dict) -> str:
"""Calculate transaction hash (implement based on specific blockchain)"""
import hashlib
import json
return hashlib.sha256(json.dumps(tx_data, sort_keys=True).encode()).hexdigest()Security of private keys in memory is equally important:
# Note: Python strings are immutable and cannot be completely erased from memory
# Best practice is to use bytes objects and delete references as soon as possible
def secure_handle_private_key():
"""
Example of secure private key handling
"""
# Use bytes instead of str
private_key_bytes = get_decrypted_key_as_bytes()
try:
# Perform necessary operations with the private key
result = sign_transaction(private_key_bytes)
return result
finally:
# Attempt to overwrite memory (limited effectiveness in Python)
if isinstance(private_key_bytes, bytearray):
for i in range(len(private_key_bytes)):
private_key_bytes[i] = 0
# Delete reference, let garbage collector handle it
del private_key_bytes
# Better approach: Use context manager
class SecureKey:
"""
Secure key context manager
"""
def __init__(self, key_bytes: bytearray):
self.key = key_bytes
def __enter__(self):
return self.key
def __exit__(self, exc_type, exc_val, exc_tb):
# Zero out memory
for i in range(len(self.key)):
self.key[i] = 0
del self.key
# Usage example
with SecureKey(get_key_as_bytearray()) as key:
# Use the key
sign_transaction(key)
# Automatically cleaned up when leaving scopeImportant Notes:
- Python's memory management makes it difficult to completely erase sensitive data from memory
- Strings (str) are immutable and cannot be directly modified
- Using
bytearrayprovides better control - Consider using specialized key management libraries like
cryptographyor hardware security modules
import logging
# Configure logging (without sensitive information)
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('bot.log'),
logging.StreamHandler()
]
)
def safe_log_transaction(tx):
"""
Safely log transaction information (hiding private key)
"""
# ❌ Don't do this
# logging.info(f"Signing with key: {private_key}")
# ✅ Correct approach
logging.info(f"Transaction signed: {tx.hash}")
logging.info(f"From address: {tx.from_address}")
logging.info(f"To address: {tx.to_address}")Before deploying bot scripts, ensure:
- Private keys are never hardcoded in the code
- Private key files are added to .gitignore
- Strong encryption (AES-256-GCM) is used to protect stored private keys
- Key derivation uses sufficient iterations (≥100,000)
- Access audit logging is implemented
- Key rotation strategy is configured
- Error logs don't contain sensitive information
- Private keys are cleared from memory after use
- Key management service (KMS/Vault) is used in production
- Access permissions to private keys are restricted (file permissions 600)
- Multi-factor authentication (MFA) is enabled for key management systems
- Strong Key Derivation: PBKDF2-SHA256 + 600,000 iterations
- Authenticated Encryption: AES-GCM provides both confidentiality and integrity
- Randomization: Uses random salt and IV to ensure different encryption results each time
- Metadata Storage: Saves algorithm parameters to support future upgrades
- Never Store in Plaintext: Always encrypt private keys
- Defense in Depth: Combine multiple security mechanisms
- Minimal Exposure: Only access private keys when necessary
- Regular Rotation: Reduce long-term exposure risk
- Audit Trail: Log all critical operations
- Secure Deletion: Clear immediately after use
Disclaimer: This document is for educational and security research purposes only. Private key management involves real asset security. Please consult professional security personnel in production environments.