b
File size: 11,787 Bytes
48ccb12
8c24af7
48ccb12
 
 
b737f2b
48ccb12
 
b737f2b
8c24af7
48ccb12
8c24af7
6ba34a3
 
 
48ccb12
8c24af7
 
6ba34a3
8c24af7
48ccb12
 
8c24af7
48ccb12
8c24af7
 
 
 
48ccb12
6ba34a3
 
8c24af7
 
48ccb12
 
8c24af7
48ccb12
8c24af7
 
 
48ccb12
 
 
 
8c24af7
b737f2b
 
8c24af7
 
 
 
 
b737f2b
 
 
 
 
 
 
 
 
 
8c24af7
b737f2b
8c24af7
6ba34a3
b737f2b
 
8c24af7
b737f2b
 
6ba34a3
8c24af7
6ba34a3
b737f2b
 
 
 
6ba34a3
 
 
 
 
 
 
b737f2b
 
 
 
6ba34a3
b737f2b
 
 
8c24af7
48ccb12
b737f2b
8c24af7
48ccb12
 
 
 
 
 
 
 
 
 
 
8c24af7
b737f2b
 
6ba34a3
b737f2b
6ba34a3
b737f2b
 
8c24af7
48ccb12
 
 
6ba34a3
8c24af7
48ccb12
 
 
8c24af7
6ba34a3
48ccb12
 
b737f2b
 
 
 
 
 
 
 
6ba34a3
 
856d71b
 
6ba34a3
8c24af7
b737f2b
 
 
6ba34a3
b737f2b
 
6ba34a3
 
 
 
 
 
 
 
b737f2b
6ba34a3
8c24af7
856d71b
6ba34a3
856d71b
 
 
8c24af7
b737f2b
 
 
8c24af7
b737f2b
 
 
 
8c24af7
856d71b
8c24af7
856d71b
 
 
8c24af7
b737f2b
6ba34a3
 
 
 
 
 
 
b737f2b
8c24af7
6ba34a3
b737f2b
 
 
 
 
 
 
 
 
8c24af7
 
 
 
 
 
 
 
 
 
 
 
 
 
48ccb12
b737f2b
48ccb12
8c24af7
b737f2b
 
 
6ba34a3
b737f2b
 
 
 
 
48ccb12
8c24af7
b737f2b
 
 
 
 
 
8c24af7
 
856d71b
 
8c24af7
856d71b
 
 
 
 
b737f2b
 
 
 
 
8c24af7
b737f2b
 
 
8c24af7
b737f2b
6ba34a3
b737f2b
856d71b
6ba34a3
856d71b
6ba34a3
48ccb12
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
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("免责声明:此工具仅供教育和个人恢复目的。请负责任地使用。")