|
|
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: |
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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' |
|
|
|
|
|
|
|
|
|
|
|
st.set_page_config(page_title="Wallet.dat Cracker (Background)", layout="wide") |
|
|
st.title("Bitcoin Wallet.dat 密码破解 (后台运行)") |
|
|
|
|
|
st.warning( |
|
|
""" |
|
|
**重要声明:** |
|
|
1. 此工具仅用于恢复 **您自己** 的钱包密码。 |
|
|
2. 破解过程 **极其缓慢**,对复杂密码几乎无效。 |
|
|
3. **请勿将您的真实钱包文件上传到不受信任的在线服务。此工具应在本地运行。** |
|
|
4. 对于任何数据丢失或法律问题,作者概不负责。 |
|
|
""" |
|
|
) |
|
|
|
|
|
|
|
|
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"✅ 钱包文件未加密。") |
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
pass |
|
|
|
|
|
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("免责声明:此工具仅供教育和个人恢复目的。请负责任地使用。") |