b / app.py
aianyu's picture
Upload app.py
6ba34a3 verified
import streamlit as st
from pywallet import wallet # 确保这个库能正常工作,并且你知道它的局限性
import time
import itertools
import string
import threading
# --- 配置 ---
DEFAULT_CHARSET = string.ascii_lowercase + string.digits
MAX_BRUTEFORCE_LEN = 4 # 保持较小以进行测试,实际破解中此长度几乎无效
# --- 辅助函数 ---
WALLET_NOT_ENCRYPTED_SENTINEL = "WALLET_NOT_ENCRYPTED" # 定义一个哨兵值
def try_decrypt_wallet(wallet_data_bytes, password_attempt):
"""
尝试用给定的密码解密钱包。
返回 True 如果成功, False 如果密码错误, WALLET_NOT_ENCRYPTED_SENTINEL 如果未加密。
"""
try:
w = wallet.Wallet.from_binary(wallet_data_bytes)
if w.is_encrypted():
if w.decrypt(password_attempt):
return True # 解密成功
else:
return False # 密码错误
else:
# 钱包未加密,返回哨兵值
return WALLET_NOT_ENCRYPTED_SENTINEL
except Exception as e:
# print(f"Decryption error with password '{password_attempt}': {e}") # 调试时取消注释
return False
def generate_passwords_bruteforce(charset, max_len):
"""
(非常不推荐) 基本的暴力破解密码生成器
"""
for length in range(1, max_len + 1):
for item in itertools.product(charset, repeat=length):
yield "".join(item)
# --- 后台工作函数 ---
def background_cracker_task(wallet_bytes_data, passwords_iterable, task_id, method_name):
"""
在后台线程中运行破解过程。
更新 st.session_state 来反映进度和结果。
"""
# 初始化任务状态
st.session_state[f'task_{task_id}_status'] = 'running'
st.session_state[f'task_{task_id}_found_password'] = None
st.session_state[f'task_{task_id}_error_message'] = None
st.session_state[f'task_{task_id}_attempts'] = 0
st.session_state[f'task_{task_id}_time_taken'] = 0
start_time = time.time()
found = False
processed_passwords = 0
for pwd_count, pwd in enumerate(passwords_iterable):
if st.session_state.get('current_task_id') != task_id:
st.session_state[f'task_{task_id}_error_message'] = "任务被中止。"
st.session_state[f'task_{task_id}_status'] = 'failed'
return
processed_passwords = pwd_count + 1
st.session_state[f'task_{task_id}_attempts'] = processed_passwords
decryption_status = try_decrypt_wallet(wallet_bytes_data, pwd)
if decryption_status is True: # 密码正确
st.session_state[f'task_{task_id}_found_password'] = pwd
st.session_state[f'task_{task_id}_status'] = 'success'
found = True
break
elif decryption_status == WALLET_NOT_ENCRYPTED_SENTINEL: # 钱包未加密
st.session_state.info_message = "钱包文件未加密。" # 在这里设置
st.session_state[f'task_{task_id}_found_password'] = WALLET_NOT_ENCRYPTED_SENTINEL # 使用哨兵值标记
st.session_state[f'task_{task_id}_status'] = 'success' # 同样视为成功
found = True
break
# else: decryption_status is False, 密码错误,继续循环
end_time = time.time()
st.session_state[f'task_{task_id}_time_taken'] = end_time - start_time
if not found and st.session_state.get(f'task_{task_id}_status') == 'running':
st.session_state[f'task_{task_id}_error_message'] = "未找到密码。"
st.session_state[f'task_{task_id}_status'] = 'failed'
# --- Streamlit UI ---
st.set_page_config(page_title="Wallet.dat Cracker (Background)", layout="wide")
st.title("Bitcoin Wallet.dat 密码破解 (后台运行)")
st.warning(
"""
**重要声明:**
1. 此工具仅用于恢复 **您自己** 的钱包密码。
2. 破解过程 **极其缓慢**,对复杂密码几乎无效。
3. **请勿将您的真实钱包文件上传到不受信任的在线服务。此工具应在本地运行。**
4. 对于任何数据丢失或法律问题,作者概不负责。
"""
)
# 初始化 session_state 变量 (如果它们不存在)
if 'current_task_id' not in st.session_state:
st.session_state.current_task_id = None
if 'task_counter' not in st.session_state:
st.session_state.task_counter = 0
if 'info_message' not in st.session_state:
st.session_state.info_message = None
# --- 文件上传 ---
uploaded_wallet_file = st.file_uploader("1. 上传你的 wallet.dat 文件", type=["dat"])
if uploaded_wallet_file:
wallet_bytes_data_main = uploaded_wallet_file.getvalue()
st.success(f"已上传 {uploaded_wallet_file.name} ({len(wallet_bytes_data_main)} bytes)")
crack_method = st.radio(
"2. 选择破解方法:",
('使用密码列表', '尝试单个密码', '基本暴力破解 (极慢,后台运行)'),
key="crack_method_radio"
)
current_task_id = st.session_state.get('current_task_id')
task_status = None
if current_task_id:
task_status = st.session_state.get(f'task_{current_task_id}_status')
if task_status == 'running':
attempts = st.session_state.get(f'task_{current_task_id}_attempts', 0)
with st.spinner(f"正在后台破解... 已尝试 {attempts} 个密码。请勿关闭此页面。"):
st.empty()
time.sleep(0.5)
try:
st.experimental_rerun()
except AttributeError:
st.warning("您的 Streamlit 版本较旧,spinner 状态可能不会实时更新。")
elif task_status == 'success':
found_pwd_or_status = st.session_state.get(f'task_{current_task_id}_found_password')
time_taken = st.session_state.get(f'task_{current_task_id}_time_taken', 0)
attempts = st.session_state.get(f'task_{current_task_id}_attempts', 0)
if found_pwd_or_status == WALLET_NOT_ENCRYPTED_SENTINEL:
st.success(f"✅ 钱包文件未加密。")
# info_message 应该已经被后台任务设置,这里不需要再次设置
else:
st.success(f"🎉 密码找到: **{found_pwd_or_status}** 🎉")
st.balloons()
st.info(f"总耗时: {time_taken:.2f} 秒。尝试密码数: {attempts}。")
if st.button("清除结果并开始新任务", key="clear_success"):
st.session_state.current_task_id = None
st.session_state.info_message = None
try:
st.experimental_rerun()
except AttributeError:
pass
elif task_status == 'failed':
error_msg = st.session_state.get(f'task_{current_task_id}_error_message', "未知错误")
time_taken = st.session_state.get(f'task_{current_task_id}_time_taken', 0)
attempts = st.session_state.get(f'task_{current_task_id}_attempts', 0)
st.error(f"破解失败: {error_msg}")
st.info(f"总耗时: {time_taken:.2f} 秒。尝试密码数: {attempts}。")
if st.button("清除结果并开始新任务", key="clear_failed"):
st.session_state.current_task_id = None
st.session_state.info_message = None
try:
st.experimental_rerun()
except AttributeError:
pass
# 显示来自后台任务的一次性消息 (例如 "钱包未加密")
# 这个消息会在 'success' 状态时由后台任务设置
# 确保它只在相关任务完成后显示一次,并且在清除任务时也被清除
if st.session_state.get('info_message') and task_status == 'success' and st.session_state.get(
f'task_{current_task_id}_found_password') == WALLET_NOT_ENCRYPTED_SENTINEL:
# st.info(st.session_state.info_message) # 这行其实可以省略,因为 "钱包文件未加密" 已经在 success 块中显示
pass # The message is handled within the success block now
if not task_status or task_status in ['success', 'failed']:
passwords_to_try_iterable = None
can_start = False
start_button_label = "开始破解"
method_name_for_task = ""
if crack_method == '使用密码列表':
uploaded_password_list = st.file_uploader(
"上传密码列表文件 ( .txt, 每行一个密码)", type=["txt"], key="pwdlist_uploader"
)
if uploaded_password_list:
try:
passwords_list = [
line.strip() for line in uploaded_password_list.read().decode().splitlines() if line.strip()
]
if passwords_list:
st.info(f"密码列表加载了 {len(passwords_list)} 个密码。")
passwords_to_try_iterable = passwords_list
can_start = True
start_button_label = "开始使用列表破解"
method_name_for_task = "list"
else:
st.warning("上传的密码列表为空或无效。")
except Exception as e:
st.error(f"读取密码列表文件错误: {e}")
else:
st.info("请上传密码列表文件。")
elif crack_method == '尝试单个密码':
single_password = st.text_input("输入要尝试的密码:", type="password", key="single_pwd_input")
if single_password:
passwords_to_try_iterable = [single_password]
can_start = True
start_button_label = "尝试此密码"
method_name_for_task = "single"
else:
st.info("请输入要尝试的密码。")
elif crack_method == '基本暴力破解 (极慢,后台运行)':
charset_input = st.text_input("字符集:", DEFAULT_CHARSET, key="charset_input")
max_len_input = st.number_input(
"最大密码长度 (警告: 长度大于4会非常慢):",
min_value=1, max_value=8, value=MAX_BRUTEFORCE_LEN, key="maxlen_input"
)
if charset_input:
estimated_total = sum(len(charset_input) ** i for i in range(1, max_len_input + 1))
st.warning(f"使用字符集 '{charset_input}' 和最大长度 {max_len_input} 进行暴力破解,"
f"可能需要尝试 {estimated_total} 个密码。这会非常耗时。")
passwords_to_try_iterable = generate_passwords_bruteforce(charset_input, max_len_input)
can_start = True
start_button_label = "开始暴力破解 (后台)"
method_name_for_task = "bruteforce"
else:
st.error("字符集不能为空。")
if can_start and st.button(start_button_label, key=f"start_button_{method_name_for_task}"):
st.session_state.task_counter += 1
new_task_id = st.session_state.task_counter
st.session_state.current_task_id = new_task_id
st.session_state.info_message = None # 清除旧的 "钱包未加密" 消息,准备新任务
thread = threading.Thread(
target=background_cracker_task,
args=(wallet_bytes_data_main, passwords_to_try_iterable, new_task_id, method_name_for_task)
)
thread.daemon = True
thread.start()
try:
st.experimental_rerun()
except AttributeError:
pass
else:
st.info("请先上传 wallet.dat 文件。")
st.markdown("---")
st.markdown("免责声明:此工具仅供教育和个人恢复目的。请负责任地使用。")