Euthenticator: 打造一款纯本地的TOTP身份验证器
1. 为什么要开发自己的身份验证器应用
在当今数字时代,双因素认证(2FA)已成为保护在线账户安全的标准配置。然而,使用手机作为唯一的验证码生成工具存在着诸多不便。
实际需求驱动的开发
开发Euthenticator的初衷源于日常使用中的不便:
-
某些网站无法保持登录状态:很多网站为了安全考虑,即使勾选了"记住我",仍会要求定期输入2FA验证码
-
手机不在身边的窘境:想象一下正专注工作时,网站突然要求验证,而手机恰好不在身边的情况
-
多设备同步的烦恼:切换手机后重新设置所有2FA账户的繁琐过程
当我意识到需要一个桌面端的TOTP验证器时,市面上已有的应用要么功能过度复杂,要么界面设计不符合桌面使用习惯,更不用说众多应用暗藏的数据上传风险。既然如此,为什么不自己开发一个,完全符合个人需求的验证器呢?
学习Go语言的绝佳实践
作为一名开发者,持续学习新技术是保持竞争力的关键。这个项目也是我深入学习Go语言的绝佳机会:
-
Go语言以其简洁、高效的特性非常适合开发这类安全工具
-
结合Wails框架可以创建轻量级的跨平台桌面应用
-
实践中遇到的问题往往比教程中学到的知识更加深刻
正所谓"实践出真知",通过开发一个完整的应用,我能够全面理解Go语言的特性、并发模型、错误处理等核心概念。
2. Euthenticator项目介绍
Euthenticator是一款纯本地的TOTP(基于时间的一次性密码)安全令牌生成器,专为桌面用户设计,提供简洁易用的界面,确保您的账户安全。
核心特性
-
🔒 纯本地存储:所有验证码数据均加密存储在本地,无需担心数据被云服务获取
-
🔄 多源导入支持:兼容Google Authenticator和Steam令牌的导入功能
-
👁️ 灵活的显示控制:可一键显示/隐藏所有验证码,平衡便利与安全
-
📋 便捷复制功能:点击即可复制验证码到剪贴板,减少输入错误
-
🖥️ 跨平台兼容:支持Windows和Linux系统,满足不同用户需求
-
🎨 现代化界面:专为桌面设计的UI,符合现代应用设计理念
技术架构
Euthenticator采用前后端分离的架构:
-
前端:Vue.js + Vite构建响应式界面,提供流畅的用户体验
-
后端:Go语言处理核心逻辑,包括TOTP算法实现、密钥加密存储等
-
桌面框架:Wails将前端与Go后端无缝集成,打造原生应用体验
安全设计理念
应用的核心设计理念是"安全第一":
-
所有密钥经过AES加密后存储在本地数据库
-
验证码默认隐藏,需要主动点击显示
-
无网络请求,杜绝数据泄露可能性
-
支持自定义主密码保护数据库
3. 开发过程中遇到的挑战与解决方案
Go语言的可见性规则
在Go语言中,我遇到的第一个困扰是关于方法和变量的可见性规则:
问题:当从前端尝试调用后端函数时,发现某些方法无法被调用。
解决方案:了解到Go语言中变量和方法的首字母必须大写才能被外部包访问。这是Go语言的核心设计原则之一,用于控制标识符的可见性。例如:
// 可被外部访问
func GetSecretsList() []model.Secret {
// 实现代码
}
// 不可被外部访问
func getSecretsList() []model.Secret {
// 实现代码
}
跨平台UI适配问题
问题:在Windows和Linux平台上测试时发现窗口控制按钮(最小化、最大化、关闭)的行为不一致。
解决方案:使用Wails提供的运行时API统一处理窗口行为,而不是依赖HTML/CSS的默认行为:
// 窗口最小化
async function minimizeWindow() {
await runtime.WindowMinimise();
}
// 窗口最大化/还原
async function toggleMaximizeWindow() {
isMaximized.value = !isMaximized.value;
await runtime.WindowToggleMaximise();
}
密钥安全存储
问题:如何安全地存储用户的TOTP密钥,确保即使数据库文件被获取也不会泄露原始密钥。
解决方案:实现了基于AES-256的加密存储方案,结合用户主密码派生密钥:
func Encrypt(data []byte) (string, error) {
// 从主密码派生加密密钥
key := deriveKey()
// 使用AES-GCM模式加密
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
// 加密实现...
return base64.StdEncoding.EncodeToString(ciphertext), nil
}
二维码识别与解析
问题:导入Google Authenticator数据时,需要解析特殊格式的otpauth-migration://
URI。
解决方案:使用Protocol Buffers解析Google的迁移格式,成功提取出每个账户的信息:
func ExtractOtpFromUrl(otpURL string) ([]OtpEntry, error) {
// 解析URL
parsedURL, err := url.Parse(otpURL)
if err != nil {
return nil, fmt.Errorf("URL解析失败: %v", err)
}
// 获取data参数并Base64解码
// ...
// 解析protobuf数据
payload := &pb.MigrationPayload{}
if err := proto.Unmarshal(rawData, payload); err != nil {
return nil, fmt.Errorf("Protobuf解析失败: %v", err)
}
// 提取每个账户信息
// ...
}
系统浏览器集成
问题:在"关于"页面中的项目仓库链接,默认会在内置WebView中打开,而不是用户的默认浏览器。
解决方案:使用Wails提供的runtime.BrowserOpenURL
函数,实现调用系统默认浏览器打开链接:
// 打开项目仓库链接
function openGithubRepo() {
runtime.BrowserOpenURL("https://github.com/AmamiyaHotaru/Auth");
}
然后在模板中绑定事件:
<a @click.prevent="openGithubRepo" class="detail-link" style="cursor: pointer;">
github.com/AmamiyaHotaru/Auth
</a>
总结与展望
开发Euthenticator的过程既是解决实际需求的过程,也是学习Go语言和桌面应用开发的宝贵经历。通过这个项目,我深入理解了Go语言的诸多特性,以及如何将Web技术与Go结合打造现代桌面应用。
未来,我计划进一步完善Euthenticator,添加更多功能:
-
数据导出与备份功能
-
更多身份验证协议的支持
-
暗黑模式界面
-
完善的账户管理系统
如果你也有兴趣参与这个项目,欢迎访问我们的GitHub仓库,通过Issue或Pull Request贡献你的想法和代码。
这篇文章记录了我开发Euthenticator的心路历程,希望能对有类似需求或学习兴趣的开发者提供一些启发。