Upload app.py
Browse files
app.py
CHANGED
|
@@ -16,7 +16,11 @@ def try_decrypt_wallet(wallet_data_bytes, password_attempt):
|
|
| 16 |
if w.is_encrypted():
|
| 17 |
return w.decrypt(password_attempt)
|
| 18 |
else:
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
return True
|
| 21 |
except Exception:
|
| 22 |
return False
|
|
@@ -28,10 +32,6 @@ def generate_passwords_bruteforce(charset, max_len):
|
|
| 28 |
|
| 29 |
# --- 后台工作函数 ---
|
| 30 |
def background_cracker_task(wallet_bytes_data, passwords_iterable, task_id, method_name):
|
| 31 |
-
"""
|
| 32 |
-
在后台线程中运行破解过程。
|
| 33 |
-
更新 st.session_state 来反映进度和结果。
|
| 34 |
-
"""
|
| 35 |
st.session_state[f'task_{task_id}_status'] = 'running'
|
| 36 |
st.session_state[f'task_{task_id}_found_password'] = None
|
| 37 |
st.session_state[f'task_{task_id}_error_message'] = None
|
|
@@ -42,14 +42,7 @@ def background_cracker_task(wallet_bytes_data, passwords_iterable, task_id, meth
|
|
| 42 |
found = False
|
| 43 |
processed_passwords = 0
|
| 44 |
|
| 45 |
-
# 特殊处理暴力破解,因为它是一个生成器,无法预知总数
|
| 46 |
-
is_bruteforce = (method_name == "bruteforce")
|
| 47 |
-
total_passwords_for_progress = 0
|
| 48 |
-
if not is_bruteforce and isinstance(passwords_iterable, list):
|
| 49 |
-
total_passwords_for_progress = len(passwords_iterable)
|
| 50 |
-
|
| 51 |
for pwd in passwords_iterable:
|
| 52 |
-
# 检查是否应该停止 (如果用户启动了新任务)
|
| 53 |
if st.session_state.get('current_task_id') != task_id:
|
| 54 |
st.session_state[f'task_{task_id}_error_message'] = "任务被另一个操作中止。"
|
| 55 |
st.session_state[f'task_{task_id}_status'] = 'failed'
|
|
@@ -58,30 +51,20 @@ def background_cracker_task(wallet_bytes_data, passwords_iterable, task_id, meth
|
|
| 58 |
processed_passwords += 1
|
| 59 |
st.session_state[f'task_{task_id}_attempts'] = processed_passwords
|
| 60 |
|
| 61 |
-
# 为了避免过于频繁地更新session_state (可能导致性能问题)
|
| 62 |
-
# 可以在这里添加一些逻辑,例如每隔N次尝试或每隔X秒更新一次用于显示的尝试计数
|
| 63 |
-
# 不过对于最终结果的设置,这个不是大问题
|
| 64 |
-
|
| 65 |
if try_decrypt_wallet(wallet_bytes_data, pwd):
|
| 66 |
st.session_state[f'task_{task_id}_found_password'] = pwd
|
| 67 |
st.session_state[f'task_{task_id}_status'] = 'success'
|
| 68 |
found = True
|
| 69 |
break
|
| 70 |
-
# time.sleep(0.
|
| 71 |
|
| 72 |
end_time = time.time()
|
| 73 |
st.session_state[f'task_{task_id}_time_taken'] = end_time - start_time
|
| 74 |
|
| 75 |
-
if not found and st.session_state[f'task_{task_id}_status'] == 'running':
|
| 76 |
st.session_state[f'task_{task_id}_error_message'] = "未找到密码。"
|
| 77 |
st.session_state[f'task_{task_id}_status'] = 'failed'
|
| 78 |
|
| 79 |
-
# 主动触发一次 Streamlit 刷新,让UI能立刻感知到状态变化
|
| 80 |
-
# 这个在线程中调用需要小心,但对于更新session_state然后让Streamlit自己刷新是安全的
|
| 81 |
-
# st.experimental_rerun() # 旧版,如果需要可以尝试
|
| 82 |
-
# Streamlit 通常会自动检测 session_state 的变化并重新运行脚本
|
| 83 |
-
|
| 84 |
-
|
| 85 |
# --- Streamlit UI ---
|
| 86 |
st.set_page_config(page_title="Wallet.dat Cracker (Background)", layout="wide")
|
| 87 |
st.title("Bitcoin Wallet.dat 密码破解尝试 (后台运行)")
|
|
@@ -96,23 +79,20 @@ st.warning(
|
|
| 96 |
"""
|
| 97 |
)
|
| 98 |
|
| 99 |
-
# 初始化 session_state 变量
|
| 100 |
if 'current_task_id' not in st.session_state:
|
| 101 |
st.session_state.current_task_id = None
|
| 102 |
-
if 'task_counter' not in st.session_state:
|
| 103 |
st.session_state.task_counter = 0
|
| 104 |
if 'info_message' not in st.session_state:
|
| 105 |
st.session_state.info_message = None
|
| 106 |
|
| 107 |
|
| 108 |
-
# --- 文件上传 ---
|
| 109 |
uploaded_wallet_file = st.file_uploader("1. 上传你的 wallet.dat 文件", type=["dat"])
|
| 110 |
|
| 111 |
if uploaded_wallet_file:
|
| 112 |
wallet_bytes = uploaded_wallet_file.getvalue()
|
| 113 |
st.success(f"已上传 {uploaded_wallet_file.name} ({len(wallet_bytes)} bytes)")
|
| 114 |
|
| 115 |
-
# --- 破解方法选择 ---
|
| 116 |
crack_method = st.radio(
|
| 117 |
"2. 选择破解方法:",
|
| 118 |
('使用密码列表', '尝试单个密码', '基本暴力破解 (极慢,后台运行)')
|
|
@@ -123,22 +103,16 @@ if uploaded_wallet_file:
|
|
| 123 |
if current_task_id:
|
| 124 |
task_status = st.session_state.get(f'task_{current_task_id}_status')
|
| 125 |
|
| 126 |
-
# --- 显示任务状态和结果 ---
|
| 127 |
if task_status == 'running':
|
| 128 |
attempts = st.session_state.get(f'task_{current_task_id}_attempts', 0)
|
| 129 |
with st.spinner(f"正在后台破解... 已尝试 {attempts} 个密码。请勿关闭此页面。"):
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
# 并且依赖于任务完成后 session_state 的变化来刷新整个页面。
|
| 138 |
-
# 或者,我们可以在这里放一个占位符,后台线程完成后,其设置的session_state会导致页面刷新
|
| 139 |
-
st.empty() # 只是为了保持 spinner 可见
|
| 140 |
-
time.sleep(1) # 让streamlit有机会重跑脚本并显示spinner
|
| 141 |
-
st.rerun() # 强制重跑以更新spinner中的计数(如果做了计数更新)
|
| 142 |
|
| 143 |
|
| 144 |
elif task_status == 'success':
|
|
@@ -149,8 +123,11 @@ if uploaded_wallet_file:
|
|
| 149 |
st.balloons()
|
| 150 |
st.info(f"总耗时: {time_taken:.2f} 秒。尝试密码数: {attempts}。")
|
| 151 |
if st.button("清除结果并重试", key="clear_success"):
|
| 152 |
-
st.session_state.current_task_id = None
|
| 153 |
-
|
|
|
|
|
|
|
|
|
|
| 154 |
|
| 155 |
|
| 156 |
elif task_status == 'failed':
|
|
@@ -160,15 +137,19 @@ if uploaded_wallet_file:
|
|
| 160 |
st.error(f"破解失败: {error_msg}")
|
| 161 |
st.info(f"总耗时: {time_taken:.2f} 秒。尝试密码数: {attempts}。")
|
| 162 |
if st.button("清除结果并重试", key="clear_failed"):
|
| 163 |
-
st.session_state.current_task_id = None
|
| 164 |
-
|
|
|
|
|
|
|
|
|
|
| 165 |
|
|
|
|
| 166 |
if st.session_state.get('info_message'):
|
| 167 |
st.info(st.session_state.info_message)
|
| 168 |
st.session_state.info_message = None # 显示后清除
|
| 169 |
|
| 170 |
-
|
| 171 |
-
if task_status not in ['running', 'success', 'failed']
|
| 172 |
passwords_to_try_list = []
|
| 173 |
brute_force_params = {}
|
| 174 |
can_start = False
|
|
@@ -193,7 +174,6 @@ if uploaded_wallet_file:
|
|
| 193 |
else:
|
| 194 |
st.info("请上传密码列表文件。")
|
| 195 |
|
| 196 |
-
|
| 197 |
elif crack_method == '尝试单个密码':
|
| 198 |
single_password = st.text_input("输入要尝试的密码:", type="password", key="single_pwd_input")
|
| 199 |
if single_password:
|
|
@@ -204,26 +184,29 @@ if uploaded_wallet_file:
|
|
| 204 |
else:
|
| 205 |
st.info("请输入要尝试的密码。")
|
| 206 |
|
| 207 |
-
|
| 208 |
elif crack_method == '基本暴力破解 (极慢,后台运行)':
|
| 209 |
charset_input = st.text_input("字符集:", DEFAULT_CHARSET, key="charset_input")
|
| 210 |
max_len_input = st.number_input(
|
| 211 |
"最大密码长度 (警告: 长度大于4会非常慢):",
|
| 212 |
min_value=1, max_value=8, value=MAX_BRUTEFORCE_LEN, key="maxlen_input"
|
| 213 |
)
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
|
|
|
|
|
|
|
|
|
| 221 |
|
| 222 |
|
| 223 |
if can_start and st.button(start_button_label, key=f"start_button_{method_name_for_task}"):
|
| 224 |
st.session_state.task_counter += 1
|
| 225 |
new_task_id = st.session_state.task_counter
|
| 226 |
st.session_state.current_task_id = new_task_id
|
|
|
|
| 227 |
|
| 228 |
if method_name_for_task == "bruteforce":
|
| 229 |
passwords_iterable = generate_passwords_bruteforce(
|
|
@@ -232,14 +215,16 @@ if uploaded_wallet_file:
|
|
| 232 |
else:
|
| 233 |
passwords_iterable = passwords_to_try_list
|
| 234 |
|
| 235 |
-
# 启动后台线程
|
| 236 |
thread = threading.Thread(
|
| 237 |
target=background_cracker_task,
|
| 238 |
args=(wallet_bytes, passwords_iterable, new_task_id, method_name_for_task)
|
| 239 |
)
|
| 240 |
-
thread.daemon = True
|
| 241 |
thread.start()
|
| 242 |
-
|
|
|
|
|
|
|
|
|
|
| 243 |
|
| 244 |
else:
|
| 245 |
st.info("请先上传 wallet.dat 文件。")
|
|
|
|
| 16 |
if w.is_encrypted():
|
| 17 |
return w.decrypt(password_attempt)
|
| 18 |
else:
|
| 19 |
+
# 使用 session_state 存储需要在主线程显示的消息
|
| 20 |
+
if 'info_message' not in st.session_state:
|
| 21 |
+
st.session_state.info_message = "钱包文件未加密。"
|
| 22 |
+
elif st.session_state.info_message is None: # 避免覆盖已有的消息直到它被显示
|
| 23 |
+
st.session_state.info_message = "钱包文件未加密。"
|
| 24 |
return True
|
| 25 |
except Exception:
|
| 26 |
return False
|
|
|
|
| 32 |
|
| 33 |
# --- 后台工作函数 ---
|
| 34 |
def background_cracker_task(wallet_bytes_data, passwords_iterable, task_id, method_name):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
st.session_state[f'task_{task_id}_status'] = 'running'
|
| 36 |
st.session_state[f'task_{task_id}_found_password'] = None
|
| 37 |
st.session_state[f'task_{task_id}_error_message'] = None
|
|
|
|
| 42 |
found = False
|
| 43 |
processed_passwords = 0
|
| 44 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
for pwd in passwords_iterable:
|
|
|
|
| 46 |
if st.session_state.get('current_task_id') != task_id:
|
| 47 |
st.session_state[f'task_{task_id}_error_message'] = "任务被另一个操作中止。"
|
| 48 |
st.session_state[f'task_{task_id}_status'] = 'failed'
|
|
|
|
| 51 |
processed_passwords += 1
|
| 52 |
st.session_state[f'task_{task_id}_attempts'] = processed_passwords
|
| 53 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
if try_decrypt_wallet(wallet_bytes_data, pwd):
|
| 55 |
st.session_state[f'task_{task_id}_found_password'] = pwd
|
| 56 |
st.session_state[f'task_{task_id}_status'] = 'success'
|
| 57 |
found = True
|
| 58 |
break
|
| 59 |
+
# time.sleep(0.00001) # 极小的延时,CPU密集型任务中效果有限
|
| 60 |
|
| 61 |
end_time = time.time()
|
| 62 |
st.session_state[f'task_{task_id}_time_taken'] = end_time - start_time
|
| 63 |
|
| 64 |
+
if not found and st.session_state[f'task_{task_id}_status'] == 'running':
|
| 65 |
st.session_state[f'task_{task_id}_error_message'] = "未找到密码。"
|
| 66 |
st.session_state[f'task_{task_id}_status'] = 'failed'
|
| 67 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
# --- Streamlit UI ---
|
| 69 |
st.set_page_config(page_title="Wallet.dat Cracker (Background)", layout="wide")
|
| 70 |
st.title("Bitcoin Wallet.dat 密码破解尝试 (后台运行)")
|
|
|
|
| 79 |
"""
|
| 80 |
)
|
| 81 |
|
|
|
|
| 82 |
if 'current_task_id' not in st.session_state:
|
| 83 |
st.session_state.current_task_id = None
|
| 84 |
+
if 'task_counter' not in st.session_state:
|
| 85 |
st.session_state.task_counter = 0
|
| 86 |
if 'info_message' not in st.session_state:
|
| 87 |
st.session_state.info_message = None
|
| 88 |
|
| 89 |
|
|
|
|
| 90 |
uploaded_wallet_file = st.file_uploader("1. 上传你的 wallet.dat 文件", type=["dat"])
|
| 91 |
|
| 92 |
if uploaded_wallet_file:
|
| 93 |
wallet_bytes = uploaded_wallet_file.getvalue()
|
| 94 |
st.success(f"已上传 {uploaded_wallet_file.name} ({len(wallet_bytes)} bytes)")
|
| 95 |
|
|
|
|
| 96 |
crack_method = st.radio(
|
| 97 |
"2. 选择破解方法:",
|
| 98 |
('使用密码列表', '尝试单个密码', '基本暴力破解 (极慢,后台运行)')
|
|
|
|
| 103 |
if current_task_id:
|
| 104 |
task_status = st.session_state.get(f'task_{current_task_id}_status')
|
| 105 |
|
|
|
|
| 106 |
if task_status == 'running':
|
| 107 |
attempts = st.session_state.get(f'task_{current_task_id}_attempts', 0)
|
| 108 |
with st.spinner(f"正在后台破解... 已尝试 {attempts} 个密码。请勿关闭此页面。"):
|
| 109 |
+
st.empty()
|
| 110 |
+
time.sleep(1)
|
| 111 |
+
try:
|
| 112 |
+
st.experimental_rerun()
|
| 113 |
+
except AttributeError:
|
| 114 |
+
st.warning("您的 Streamlit 版本较旧,spinner 状态可能不会实时更新。请考虑升级 Streamlit 或手动刷新页面。")
|
| 115 |
+
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
|
| 117 |
|
| 118 |
elif task_status == 'success':
|
|
|
|
| 123 |
st.balloons()
|
| 124 |
st.info(f"总耗时: {time_taken:.2f} 秒。尝试密码数: {attempts}。")
|
| 125 |
if st.button("清除结果并重试", key="clear_success"):
|
| 126 |
+
st.session_state.current_task_id = None
|
| 127 |
+
try:
|
| 128 |
+
st.experimental_rerun()
|
| 129 |
+
except AttributeError:
|
| 130 |
+
st.warning("您的 Streamlit 版本较旧。请考虑升级 Streamlit 或手动刷新页面。")
|
| 131 |
|
| 132 |
|
| 133 |
elif task_status == 'failed':
|
|
|
|
| 137 |
st.error(f"破解失败: {error_msg}")
|
| 138 |
st.info(f"总耗时: {time_taken:.2f} 秒。尝试密码数: {attempts}。")
|
| 139 |
if st.button("清除结果并重试", key="clear_failed"):
|
| 140 |
+
st.session_state.current_task_id = None
|
| 141 |
+
try:
|
| 142 |
+
st.experimental_rerun()
|
| 143 |
+
except AttributeError:
|
| 144 |
+
st.warning("您的 Streamlit 版本较旧。请考虑升级 Streamlit 或手动刷新页面。")
|
| 145 |
|
| 146 |
+
# 显示来自 try_decrypt_wallet 的一次性消息
|
| 147 |
if st.session_state.get('info_message'):
|
| 148 |
st.info(st.session_state.info_message)
|
| 149 |
st.session_state.info_message = None # 显示后清除
|
| 150 |
|
| 151 |
+
|
| 152 |
+
if not task_status or task_status not in ['running', 'success', 'failed']:
|
| 153 |
passwords_to_try_list = []
|
| 154 |
brute_force_params = {}
|
| 155 |
can_start = False
|
|
|
|
| 174 |
else:
|
| 175 |
st.info("请上传密码列表文件。")
|
| 176 |
|
|
|
|
| 177 |
elif crack_method == '尝试单个密码':
|
| 178 |
single_password = st.text_input("输入要尝试的密码:", type="password", key="single_pwd_input")
|
| 179 |
if single_password:
|
|
|
|
| 184 |
else:
|
| 185 |
st.info("请输入要尝试的密码。")
|
| 186 |
|
|
|
|
| 187 |
elif crack_method == '基本暴力破解 (极慢,后台运行)':
|
| 188 |
charset_input = st.text_input("字符集:", DEFAULT_CHARSET, key="charset_input")
|
| 189 |
max_len_input = st.number_input(
|
| 190 |
"最大密码长度 (警告: 长度大于4会非常慢):",
|
| 191 |
min_value=1, max_value=8, value=MAX_BRUTEFORCE_LEN, key="maxlen_input"
|
| 192 |
)
|
| 193 |
+
if charset_input: # 确保字符集不为空
|
| 194 |
+
estimated_total = sum(len(charset_input)**i for i in range(1, max_len_input + 1))
|
| 195 |
+
st.warning(f"使用字符集 '{charset_input}' 和最大长度 {max_len_input} 进行暴力破解,"
|
| 196 |
+
f"可能需要尝试 {estimated_total} 个密码。这会��常耗时。")
|
| 197 |
+
brute_force_params = {'charset': charset_input, 'max_len': max_len_input}
|
| 198 |
+
can_start = True
|
| 199 |
+
start_button_label = "开始暴力破解 (后台)"
|
| 200 |
+
method_name_for_task = "bruteforce"
|
| 201 |
+
else:
|
| 202 |
+
st.error("字符集不能为空。")
|
| 203 |
|
| 204 |
|
| 205 |
if can_start and st.button(start_button_label, key=f"start_button_{method_name_for_task}"):
|
| 206 |
st.session_state.task_counter += 1
|
| 207 |
new_task_id = st.session_state.task_counter
|
| 208 |
st.session_state.current_task_id = new_task_id
|
| 209 |
+
st.session_state.info_message = None # 清除任何旧的钱包未加密消息
|
| 210 |
|
| 211 |
if method_name_for_task == "bruteforce":
|
| 212 |
passwords_iterable = generate_passwords_bruteforce(
|
|
|
|
| 215 |
else:
|
| 216 |
passwords_iterable = passwords_to_try_list
|
| 217 |
|
|
|
|
| 218 |
thread = threading.Thread(
|
| 219 |
target=background_cracker_task,
|
| 220 |
args=(wallet_bytes, passwords_iterable, new_task_id, method_name_for_task)
|
| 221 |
)
|
| 222 |
+
thread.daemon = True
|
| 223 |
thread.start()
|
| 224 |
+
try:
|
| 225 |
+
st.experimental_rerun()
|
| 226 |
+
except AttributeError:
|
| 227 |
+
st.warning("您的 Streamlit 版本较旧。请考虑升级 Streamlit 或手动刷新页面。")
|
| 228 |
|
| 229 |
else:
|
| 230 |
st.info("请先上传 wallet.dat 文件。")
|