- Introduced API guidelines for interface contracts and request handling. - Added design tokens usage guidelines for consistent styling across the project. - Established DTO guidelines for defining request parameters and response data types. - Created frontend structure guidelines to clarify directory organization and code placement rules. - Compiled a comprehensive frontend development guideline document covering various aspects of the development process. - Implemented quality guidelines to ensure code maintainability and adherence to best practices.
9.4 KiB
MonoProxy 订阅信息获取工作流
本文记录如何从 macOS 版 MonoProxy 的本地配置中获取自有账户的节点信息,并生成不包含节点密码的 Clash YAML 列表。
适用范围
- 应用路径:
/Applications/MonoProxyMac.app - 配置路径:
~/Library/Application Support/MonoProxy/config.json - 已验证日期:2026-07-24
- 输出字段:
name、server、port、type、cipher、udp - 明确排除:节点
password、登录令牌、刷新令牌和账户信息
仅应处理自己拥有或获授权访问的账户与配置。不要上传或公开分享原始 config.json,其中还包含账户令牌和加密后的节点密码。
结论
MonoProxy 将节点数组保存在 config.json 的 mn_service_<service-id>_servers 字段中。该字段是 Base64 字符串,解码后的数据布局为:
salt(16 字节)
+ IV(16 字节)
+ HMAC-SHA256(32 字节)
+ AES-256-CBC ciphertext(剩余字节)
密钥派生参数:
算法:PBKDF2-HMAC-SHA256
迭代次数:100000
密钥长度:32 字节
配置封装口令:MonoProxyMac.MNLocalManager.Services.v1
这里的“配置封装口令”是应用二进制中用于保护本地配置结构的固定值,不是 Shadowsocks 节点的 password。
解密流程必须先验证 HMAC,再执行 AES 解密。HMAC 不匹配时应立即停止,不能忽略校验继续处理。
步骤一:让 MonoProxy 刷新本地配置
- 启动 MonoProxy 并登录自己的账户。
- 等待节点列表完成刷新;是否开启系统代理不影响离线读取,但刷新过程需要网络。
- 检查配置文件是否刚刚更新:
stat -f '%N | modified=%Sm | size=%z' \
-t '%Y-%m-%d %H:%M:%S %z' \
"$HOME/Library/Application Support/MonoProxy/config.json"
如果文件不存在,先确认应用是否已登录并成功获取服务信息。
步骤二:确认节点字段
只查看字段名称,不输出令牌或节点密码:
jq -r 'keys[] | select(test("^mn_service_.*_servers$"))' \
"$HOME/Library/Application Support/MonoProxy/config.json"
正常情况下会得到类似:
mn_service_2462_servers
服务 ID 可能随账户或后端迁移而变化,因此提取脚本不应硬编码数字部分。
步骤三:使用离线脚本生成无密码 YAML
下面的脚本仅使用 Node.js 内置的 fs 和 crypto 模块,不需要安装第三方依赖,也不会联网。脚本会:
- 自动查找
mn_service_<id>_servers字段; - Base64 解码数据;
- 使用 PBKDF2-SHA256 派生密钥;
- 验证 HMAC-SHA256;
- 使用 AES-256-CBC 解密节点 JSON;
- 输出不含
password的 Clash YAML。
保存为 extract-monoproxy.js:
const fs = require("fs");
const crypto = require("crypto");
const configPath =
process.argv[2] ||
`${process.env.HOME}/Library/Application Support/MonoProxy/config.json`;
const configEnvelopePassword =
"MonoProxyMac.MNLocalManager.Services.v1";
/**
* 使用 YAML 单引号格式转义字符串,避免节点名称中的特殊字符破坏 YAML。
* @param {unknown} value 需要编码的值。
* @returns {string} 可安全写入 YAML 的单引号字符串。
*/
function yamlString(value) {
return `'${String(value).replaceAll("'", "''")}'`;
}
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
const serverKey = Object.keys(config).find((key) =>
/^mn_service_.*_servers$/.test(key),
);
if (!serverKey) {
throw new Error("未找到 mn_service_<id>_servers 字段");
}
const envelope = Buffer.from(config[serverKey], "base64");
if (envelope.length <= 64) {
throw new Error("节点密文长度异常");
}
const salt = envelope.subarray(0, 16);
const iv = envelope.subarray(16, 32);
const storedHmac = envelope.subarray(32, 64);
const ciphertext = envelope.subarray(64);
const key = crypto.pbkdf2Sync(
configEnvelopePassword,
salt,
100000,
32,
"sha256",
);
const calculatedHmac = crypto
.createHmac("sha256", key)
.update(Buffer.concat([salt, iv, ciphertext]))
.digest();
if (
storedHmac.length !== calculatedHmac.length ||
!crypto.timingSafeEqual(storedHmac, calculatedHmac)
) {
throw new Error(
"HMAC 校验失败:配置可能损坏,或 MonoProxy 已更改加密格式",
);
}
const decipher = crypto.createDecipheriv("aes-256-cbc", key, iv);
const plaintext = Buffer.concat([
decipher.update(ciphertext),
decipher.final(),
]).toString("utf8");
const nodes = JSON.parse(plaintext);
if (!Array.isArray(nodes)) {
throw new Error("解密结果不是节点数组");
}
console.log("proxies:");
for (const node of nodes) {
if (!node.alias || !node.hostname || !node.port || !node.encryption) {
throw new Error("节点缺少 alias/hostname/port/encryption 字段");
}
console.log(` - name: ${yamlString(node.alias)}`);
console.log(` server: ${yamlString(node.hostname)}`);
console.log(` port: ${Number(node.port)}`);
console.log(" type: ss");
console.log(` cipher: ${yamlString(node.encryption)}`);
console.log(" udp: true");
}
执行:
node extract-monoproxy.js \
"$HOME/Library/Application Support/MonoProxy/config.json" \
> monoproxy-subscription-without-password.yaml
检查输出中没有密码字段:
rg -n 'password|access_token|refresh_token' \
monoproxy-subscription-without-password.yaml
正常结果应无任何输出。再检查 YAML 的节点数量:
rg -c '^ - name:' monoproxy-subscription-without-password.yaml
当前快照应输出 15。
字段映射
| MonoProxy 节点字段 | Clash YAML 字段 | 说明 |
|---|---|---|
alias |
name |
节点显示名称 |
hostname |
server |
节点域名或地址 |
port |
port |
节点端口 |
| 服务类型 Shadowsocks | type: ss |
type 不在每个节点对象中单独存储 |
encryption |
cipher |
当前均为 chacha20-ietf-poly1305 |
| 未单独存储 | udp: true |
按现有 Clash Shadowsocks 配置补充 |
password |
不输出 | 本工作流明确排除 |
当前完整 YAML 快照(不含 password)
数据来自 2026-07-24 15:03:55 更新的本地 config.json。HMAC 校验、AES 解密及 JSON 解析均已通过。
proxies:
- name: 'Relay-HK1'
server: 'scott.mydarkcloud.info'
port: 1904
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'Relay-HK2'
server: 'andrew.mydarkcloud.info'
port: 2004
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'Relay-HK3'
server: 'ethan.mydarkcloud.info'
port: 3204
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'Relay-HK4'
server: 'lucas.mydarkcloud.info'
port: 999
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'Relay-SG1'
server: 'tyler.mydarkcloud.info'
port: 2604
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'Relay-SG2'
server: 'tyler.mydarkcloud.info'
port: 2704
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'Relay-JP1'
server: 'patrick.mydarkcloud.info'
port: 1504
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'Relay-JP2'
server: 'ava.mydarkcloud.info'
port: 995
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'Relay-TW1'
server: 'kevin.mydarkcloud.info'
port: 2104
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'Relay-TW2'
server: 'kevin.mydarkcloud.info'
port: 2204
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'Relay-US1'
server: 'nathan.mydarkcloud.info'
port: 1204
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'Relay-US2'
server: 'nathan.mydarkcloud.info'
port: 1304
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'JP3'
server: 'noah.mydarkcloud.info'
port: 999
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'TW1'
server: 'tw1.mydarkcloud.info'
port: 999
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'TR1'
server: 'tr1.mydarkcloud.info'
port: 999
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
验证与故障处理
HMAC 校验失败
不要跳过校验。常见原因:
config.json正在被应用写入,读取到了不完整内容;- MonoProxy 更新后更改了封装口令、迭代次数或加密格式;
- 读取了其他应用或旧版本生成的配置文件。
先等待应用完成刷新并重新执行。如果仍失败,需要重新检查当前二进制中的:
MNLocalManager -_encryptedJSONObjectForKey:Ctor +d:p:e:CCKeyDerivationPBKDF参数AES256CBCDecryptData:key:iv:error:HMACSHA256WithData:key:
输出节点为空或字段缺失
- 确认账户仍有有效服务;
- 确认找到的是当前
mn_service_<id>_servers字段; - 不要把旧版
~/Library/Preferences/com.MonoCloud.MonoProxyMac.plist当作最新数据源; - 优先以刚刷新过的
~/Library/Application Support/MonoProxy/config.json为准。
YAML 无法直接连接
本文输出刻意删除了 password,因此它是用于审阅、比对和更新 server/port 的安全快照,并不是可直接连接的完整凭据文件。需要实际连接时,应在本地私密环境中补回自己已有的密码,且不要提交到 Git 或同步到公开笔记库。