PlainApp 安全详解

PlainApp 如何在本地 Wi-Fi 上保护你的数据 —— 所有内容均可在开源代码中验证。

为什么本地网络安全很重要?

当你用一个 App 通过电脑浏览器管理 Android 手机时,你的短信、照片、通讯录和文件都在 Wi-Fi 网络上传输。大多数 App 把这些数据以明文发送,同一网络上的任何人都能看到。

你可能会想:"这是我家的 Wi-Fi,谁会偷看?" 但你想想这些场景:

  • 公共 Wi-Fi — 咖啡馆、机场、酒店 —— 附近任何人都能抓包
  • 共享网络 — 室友、客人或者被入侵的智能设备
  • 路由器漏洞 — 过时的固件可能有已知的安全漏洞

如果一个 App 通过 HTTP 明文发送你的短信,任何人用免费工具(比如 Wireshark)就能读取。这不是理论风险 —— 操作非常简单。

大多数手机管理 App 就是这样做的。打开 Chrome 开发者工具(F12 → 网络标签),你可以看到每一个 API 调用的明文 JSON —— 短信内容、文件路径、联系人姓名,一览无余。

PlainApp 有什么不同

PlainApp 使用多层加密和认证机制。以下用通俗的语言解释每一层的作用。

Security Architecture Comparison
1

GraphQL API —— 只获取需要的数据

大多数 App 使用老式的 REST API:一个资源对应一个 URL,不管你需不需要,整个对象全部返回。PlainApp 使用 GraphQL —— 一种现代化的方式,浏览器只请求它需要的字段,不多不少。

这为什么重要:

  • 更少的数据暴露 — 如果你只需要文件名,就只传文件名
  • 单一入口 — 只有一个 /graphql 地址,而不是几十个接口,减少了攻击面
  • 按查询控制权限 — 每个查询单独检查权限(例如读取短信需要 READ_SMS 权限,即使你已经登录了也要单独验证)
2

加密的 API 调用(XChaCha20-Poly1305)

这是 PlainApp 和大多数竞品最大的区别。

每一个 API 请求和响应——包括 HTTP 和 WebSocket 流量——都使用 XChaCha20-Poly1305 加密,HTTP 和 HTTPS 模式均如此。这是 WireGuard VPN、Cloudflare 和 Google Tink 都在使用的现代 AEAD 加密算法。

简单来说:

  1. 1你的浏览器构建一个请求(比如"显示我的短信")
  2. 2附加一个时间戳和随机 nonce,用于防重放保护
  3. 3用 XChaCha20-Poly1305 将整个请求体加密成不可读的二进制数据
  4. 4加密后的二进制数据直接在网络上传输——不转 base64,不包 JSON,纯二进制
  5. 5只有你的手机能解密和验证

即使有人截获了流量,他们看到的只是无意义的二进制数据 —— 不是可读的 JSON。

Encrypted API Flow

加密细节:

  • 256 位密钥(极强的安全性)
  • 192 位随机数(防止某些影响其他加密算法的攻击类型)
  • 内置篡改检测 —— 如果有人在传输中修改了数据,解密会失败
  • Android 端:使用 Google Tink
  • Web 端:使用 @noble/ciphers(经过审计的零依赖 JavaScript 加密库)
3

数字签名(Ed25519)—— 设备间通信

当两台 PlainApp 设备互相通信时(例如手机对手机聊天),仅加密还不够——每台设备需要验证消息确实来自对方,而非伪造的。

时间戳

接收端设备使用发送方的公钥验证签名,并拒绝时间戳超过 5 分钟的请求。这同时防止了伪造和重放攻击。

4

安全的设备配对(ECDH 密钥交换)

当两台 PlainApp 设备互相配对时(比如两部手机),它们需要一个共享的加密密钥。但把密钥通过网络发送就失去了意义。

PlainApp 用 ECDH(椭圆曲线 Diffie-Hellman)密钥交换来解决 —— 这和 TLS、Apple Secure Enclave、WebAuthn 使用的是同样的数学原理:

  1. 1每台设备生成一对密钥(公钥 + 私钥)
  2. 2他们只交换公钥
  3. 3每台设备用自己的私钥和对方的公钥进行计算
  4. 4双方得到同一个共享密钥 —— 但密钥本身从未在网络上传输过
ECDH Pairing Flow
5

URL 加密 —— 隐藏文件路径

很多人没注意到的一个细节:URL 路径也会泄露信息。

当你下载文件时,URL 告诉服务器你要哪个文件。在很多 App 里,URL 长这样:

/fs?path=/sdcard/DCIM/Camera/photo_2024.jpg

任何抓包的人(或者查看浏览器历史记录的人)都能看到你访问了什么文件,以及你手机的文件夹结构。

PlainApp 对 URL 中的文件路径进行了加密:

/fs?id=AxK9f2mQ7vR3xP8nWz...

id 参数是用 XChaCha20-Poly1305 加密后的文件路径,加密密钥称为 URL Token。默认情况下,这个密钥是永久性的,即使重启 App 也不会改变,因此分享过的链接始终有效。没有密钥,谁也猜不出真实路径,无法枚举你的文件,也无法伪造下载链接。

URL Encryption Comparison
6

HTTPS 与自签名证书

PlainApp 在你的手机上生成一个自签名 TLS 证书,建立加密的 HTTPS 通道 —— 和银行、网站使用的技术一样。

证书参数:

  • ECDSA P-256 曲线(和主流网站相同)
  • SHA256 签名
  • 存储在 App 的私有目录中

结合应用层的 XChaCha20 加密,这提供了两层独立的加密。攻破其中一层,没有另一层也无济于事。

7

登录保护

PlainApp 限制登录尝试次数,防止密码暴力破解:

  • 每个 IP 地址,每 60 秒最多 5 次尝试
  • 超过之后连接被拒绝
  • 过期的限制记录会自动清理

HTTP 和 HTTPS 模式:实话实说

PlainApp 同时提供 HTTP 和 HTTPS 两种模式。以下是真实的区别:

HTTP 模式HTTPS 模式
文件路径 URL加密(XChaCha20)加密(XChaCha20)
API 调用加密(XChaCha20)加密(XChaCha20 + TLS)
文件下载传输时不加密TLS 加密
适合谁新手(不会看到浏览器 SSL 警告)注重隐私的用户

两种模式都用 XChaCha20-Poly1305 加密 API 调用 —— 你的短信、通讯录和其他数据无论 HTTP 还是 HTTPS 都是加密的。

HTTP 模式存在的原因:自签名证书会触发浏览器警告("您的连接不是私密连接")。对于不太懂技术的用户来说,这个警告很吓人。HTTP 模式避免了这个问题。

但 HTTP 模式不会加密文件传输。如果你在共享或公共网络上,请务必使用 HTTPS。

建议:尽量使用 HTTPS。浏览器提示的自签名证书警告是正常的,对于本地网络 App 来说是预期行为,点击继续访问即可。

8

供应链安全 —— 更少的依赖,更低的风险

很多用户没想到的一个安全威胁:供应链攻击。当一个 App 使用了几百个第三方库,其中任何一个都可能被攻击者污染 —— 注入窃取数据、挖矿或者开后门的恶意代码。

这在 JavaScript 生态中尤其危险。近年来,event-stream、ua-parser-js、colors 等流行的 npm 包被劫持,向数百万项目注入了恶意代码。如果一个手机管理 App 的 Web 界面引入了几百个 JS 包,每一个都是潜在的攻击入口。

PlainApp 的做法是:尽量少用依赖,能自己写的就自己写。

Android App(plain-app)

  • 不使用依赖注入框架(没有 Hilt,没有 Dagger)—— 用简单的单例模式
  • 加密只使用 3 个知名库:Google Tink、Bouncy Castle 和内置的 Java Cryptography Extension
  • 服务器运行在 Ktor(JetBrains 官方 Kotlin 框架)上 —— 没有冷门的第三方服务器库

Web 界面(plain-desktop)

只有 28 个运行时依赖 —— 远少于典型的 Web 应用。好几个通常需要 npm 包的功能,PlainApp 选择了从零构建:

功能普通 App 的做法PlainApp 的做法
提示音howler.js / tone.js自己用 Web Audio API 合成
UUID 生成uuid npm 包自己实现 RFC 4122
文件上传uppy / axios自己写分块上传和队列管理
WebRTC 视频Twilio / Daily.co SDK自己实现 PeerConnection
HTTP 请求axios / ky原生浏览器 fetch

Web 端的加密使用 @noble/ciphers —— 一个经过审计的、零依赖的 JavaScript 加密库,而不是引入庞大臃肿的替代品。

为什么这很重要

你添加的每个依赖,都是你无法控制的代码。PlainApp 的做法意味着:

  • 恶意代码更难藏身
  • 更小的攻击面
  • 更容易审计(整个代码库都是开源的)
  • 不会受到被遗弃或被劫持的 npm 包的影响

竞品的常见问题

这里不针对某个具体 App,而是这个品类中的常见模式:

安全方面PlainApp常见竞品做法
API 流量加密(XChaCha20),HTTP 和 HTTPS 都加密明文 JSON over HTTP
WebSocket 流量加密二进制帧(XChaCha20)明文 JSON 或不使用 WebSocket
认证 Token 处理用作加密密钥,从不放在请求头中放在 HTTP 请求头中(容易泄露)
文件 URL加密的路径 ID明文路径暴露
密钥交换ECDH (P-256)
请求签名Ed25519(仅设备间通信)
防重放保护30 秒窗口 + nonce(Web);5 分钟 + 签名(设备间)
登录保护频率限制 + 双重确认无限制
数据路由100% 本地,不经过云端部分经过云端
依赖数量精简,使用知名库数百个 npm 包
源代码开源 (GPL-3.0)大多闭源

检验任何 App 最简单的方法:在使用 App 的时候打开 Chrome 开发者工具(F12)→ 网络标签。如果你能看到明文 JSON 格式的 API 响应,说明这个 App 没有加密。

自己验证

PlainApp 完全开源,本文所有内容都可以在代码中验证。

github.com/plainhub/plain-app (GPL-3.0)
可验证的发布完整性

自动化、可追溯、抗篡改的发布流水线

所有安装包均由 GitHub 与 F-Droid 基础设施自动构建。构建过程可追溯、安全,并且没有人工打包干预痕迹。PlainApp 是少数同时提供可验证构建(SLSA)与 VirusTotal 扫描发布的 Android 应用之一。

GitHub 与 F-Droid 自动构建

发布产物由 CI 流水线生成,而不是本地手工打包。每个产物都可以追溯到源代码提交与可复现构建流程。

SLSA Provenance(3 级)+ VirusTotal 扫描

每个发布都包含 SLSA 来源证明和 VirusTotal 扫描结果,使供应链可审计,并帮助用户在安装前验证完整性。

常见问题

使用 HTTP 模式时 PlainApp 真的加密了吗?

是的。所有 API 调用(短信、通讯录、文件列表等)在 HTTP 和 HTTPS 两种模式下都用 XChaCha20-Poly1305 加密。HTTP 模式只有文件下载没有加密。如果需要完全保护,请使用 HTTPS。

为什么浏览器会显示安全警告?

PlainApp 使用自签名 TLS 证书进行加密。因为不是公共证书机构签发的,浏览器会显示警告。这对于本地网络 App 是正常和预期的。点击「高级」然后「继续访问」即可。

同一 Wi-Fi 上的人能看到我的数据吗?

使用 PlainApp 的话,不能。API 调用使用 XChaCha20-Poly1305 加密,在 HTTPS 模式下文件传输也受 TLS 保护。但使用大多数竞品 App 的话,可以 —— 它们的 API 调用是明文 JSON。

PlainApp 和 VPN 相比怎么样?

VPN 加密的是你的设备和 VPN 服务器之间的流量。PlainApp 加密的是你的手机和浏览器在本地网络上的流量。它们用途不同 —— PlainApp 保护本地设备管理,VPN 保护互联网流量。

代码真的是开源的吗?

是的。PlainApp 采用 GPL-3.0 许可。完整源代码可以在 github.com/plainhub/plain-app 查看。你可以检查每一个加密实现,从源码编译,或者参与贡献。

重新掌控你的手机。

没有云端中间商。没有月费。只有你的手机和你的浏览器。