Web3Hacker.World 是一个集合程序员黑客、对新事物好奇的种子用户、及 vc 投资者组合的围绕
万物皆可 Web3
的理念打造的生态体系,目前已有付费会员 19 人
我是来自
Web3Hacker.World
的 Bruce ,22 年 5 月 All In Web3 ,后面连续参加了 10 多个黑客松并三个月内拿到 15 个左右的赛道奖及 Grants ,累积产值 70 万 RMB 左右(含后续参与其他团队获得)。 后面又经历休息、搬家到海边(懒散了几个月)、从新学习了大量新技术。现在又开始新的一轮的 Web3 掘金。 这波主要围绕我创建的BuidlerProtocol
打造万物皆可 Web3
的跨链的Web3 App Store
生态平台来制作各种不同的实际 RWA(Real World Assets) 应用场景的 DApp 参加各种不同的黑客松比赛并尝试获得 RWA 的早期种子用户。 在这个过程中,我会用 Web3 的技术开发一系列产品,同步输出开发这些产品的一些相关技术资料总结及分享。
本文主要两大部分,第一部分是周报内容+ 最新的一些 Web3 开发相关技术栈更新,第二部分则是 MetaMask 是怎么保存你的钱包秘钥的文章翻译,这块是因为我们要在项目中实现一整套 Web3 钱包的功能而做的研究。
本周开始报名了波卡黑客松比赛,这次规划做一个 NFT-Fi-Twitter 的浏览器插件,可以让 Twitter 用户去发布 NFT-Gating 的 twitter 或者评论,而要查看加密的内容的用户则需要支付 $BST 才能解锁(付费解锁时会附带获得对应的 NFT 作为支付凭证),在做的过程中延伸出了一系列的技术扩展需要研究。
所有的相关信息内容都可以通过我们的 github 的 discussion 板块查看及留言讨论,欢迎访问:https://github.com/orgs/Web3Hacker-World/discussions
原文链接: https://www.wispwisp.com/index.php/2020/12/25/how-metamask-stores-your-wallet-secret/
作为区块链领域的安全工程师 /渗透测试人员,我在研究和客户参与期间测试了许多加密钱包应用程序。在看到在不同钱包中处理秘密的不同方式后,我很好奇 MetaMask (该领域最著名的加密钱包之一)是如何做到的。好吧,如果你问我为什么不早点看 MetaMask ,因为我只是假设它是安全的,而且我发现任何问题的机会非常低。
他们的扩展和移动应用程序以及应用程序中使用的由 MetaMask 创建的许多其他模块都是开源的。代码库是迄今为止我见过的所有加密钱包中最大的。但它并不难理解,因为代码写得很好并且有很多注释。
这篇文章解释了应用程序中与秘钥存储相关的不同组件。在文章的最后,我包含了一个非常粗略的代码逻辑,描述了这个浏览器扩展是怎么创建一个新钱包。请注意,本文中的钱包秘钥指的是助记词(mnemonic)和私钥。
Keyring 是 MetaMask 中秘密存储和账户管理系统的核心概念。KeyringController是密钥环的实现。引用自 KeyringController README:
一个用于管理以太坊账户组的模块,称为“Keyrings”,最初是为 MetaMask 的多账户类型功能定义的。
KeyringController 具有三个主要职责:
- 初始化和使用(签名)以太坊帐户组(“密钥环”)。
- 跟踪这些个人帐户的本地昵称。
- 提供密码加密保存和恢复秘密信息。
这是密钥环结构的可视化参考:
圆环表示用于生成公钥-私钥对的助记词。挂在环上的每把钥匙就是一个个人钱包帐户,这些私钥都是通过这个助记词生成的。种子短语和所有帐户数据捆绑在一起,使用从用户密码生成的加密密钥加密,并存储在扩展中。
KeyringController 使用 obs-store
类来存储数据。obs-store
代表 ObservableStore ,它是单个值的同步内存存储。该代码还将 obs-store 引用为 Vault
。让我们仔细看看它是如何实现的:在 KeyringController 构造函数中创建了两个 ObservableStore
对象,一个名为 this.store
,另一个名为 this.memStore
:
constructor (opts) {
super()
const initState = opts.initState || {}
this.keyringTypes = opts.keyringTypes ? keyringTypes.concat(opts.keyringTypes) : keyringTypes
this.store = new ObservableStore(initState)
this.memStore = new ObservableStore({
isUnlocked: false,
keyringTypes: this.keyringTypes.map((krt) => krt.type),
keyrings: [],
})
this.encryptor = opts.encryptor || encryptor
this.keyrings = []
}
this.store
存储加密后的钱包秘钥。this.store
的数据会被放入 chrome 扩展的本地存储中进行持久化数据存储。用户可以通过在扩展开发控制台中输入以下代码来访问数据。(似乎不行,译者注)
chrome.storage.local.get('data', result => {
var vault = result.data.KeyringController.vault
console.log(vault)
})
而 this.memStore
则是存储解密后的钱包秘钥。该对象中的数据保留在内存中,不会放入浏览器的持久存储中。当您打开 MetaMask 扩展程序并输入您的密码时,您解密后的帐户私钥将存储在这个 this.memStore
对象中以备将来使用。 参考代码 1, 参考代码 2
在 KeyringController 类内部,加密和解密操作由加密器 对象执行。
例子 1. 加密秘钥环数据:
return this.encryptor.encrypt(this.password, serializedKeyrings)
例子 2. 解密此加密保险库
const vault = await this.encryptor.decrypt(password, encryptedVault)
加密器对象在 KeyringController 构造函数中分配
constructor (opts) {
super()
const initState = opts.initState || {}
this.keyringTypes = opts.keyringTypes ? keyringTypes.concat(opts.keyringTypes) : keyringTypes
this.store = new ObservableStore(initState)
this.memStore = new ObservableStore({
isUnlocked: false,
keyringTypes: this.keyringTypes.map((krt) => krt.type),
keyrings: [],
})
this.encryptor = opts.encryptor || encryptor
this.keyrings = []
}
扩展程序和移动应用程序使用不同的加密器。该扩展使用 browser-passworder模块,其源代码可在 Github 上获得。移动应用程序有自己的 加密器 类。它们的工作原理几乎相同,除了[PBKDF2](<https://en.wikipedia.org/wiki/PBKDF2 )迭代和> AES 模式。
browser-passworder: 根据 password 生成 enc_key: PBKDF2, 10000 iteration AES mode: AES-GCM
Mobile app encryptor: 根据 password 生成 enc_key: PBKDF2, 5000 iteration AES mode: AES-CBC
与扩展类似,移动应用程序采用密钥环结构来存储秘钥和管理帐户。对于持久存储,应用程序使用 persistConfig 文件中定义的 async-storage 模块存储加密数据。我能找到的唯一描述 异步存储
工作原理的官方文档是这样说的:
在 iOS 上,AsyncStorage 由本机代码支持,该代码将小值存储在序列化字典中,将较大值存储在单独的文件中。
在 Android 上,AsyncStorage 将根据可用情况使用 RocksDB 或 SQLite 。
移动钱包提供 记住我
和 使用触摸 ID/设备密码解锁
选项。用户无需每次在移动设备上打开应用程序时都输入密码。
为实现这一点,MetaMask 移动应用程序使用 SecureKeychain 模块将用户密码存储在设备中,该模块建立在 react-native-keychain
之上。用户密码用于生成密钥来解密持久存储中的加密钱包秘密。
关于 react-native-keychain
如何在移动设备中存储数据的快速说明:
代码中的注释解释了 SecureKeychain 的作用:
/**
* react-native-keychain 内包裹 Keychain 的类
* 抽象 metamask 特定的功能及设置
* 同时在写入手机的 keychain 前添加额外的层
*/
class SecureKeychain {
...
}
抽象 metamask 特定的功能及设置
是指 记住我
和 使用触摸 ID/设备密码登录
功能。查看 resetGenericPassword
,getGenericPassword
和 setGenericPassword
函数以了解它是如何实现的。
至于“增加一层额外的加密”,我很好奇这里使用的加密密钥是什么来执行加密?这是我找到的相关代码路径:
code
用作加密密钥。(来源)init(salt)
函数中,使用参数 salt
创建 SecureKeychain 对象(来源)init
使用参数 props.foxCode( Source )
调用该函数(来源)foxCode
又是什么?我在 手机钱包仓库 和 MetaMask 组织 下的所有公共仓库中搜索 ,但没有结果。嗯🤔,应用程序二进制文件怎么样?
我用这个工具下载了 Metamask APK ,用 jadx 解包,搜索字符串 foxCode
,发现了这个:
等等,所以 foxCode 等于字符串“encrypt”?使用硬编码字符串加密某些内容有什么意义?🤔
我给 Metamask 安全团队发了一封邮件,询问为什么用硬编码字符串( foxCode )encrypt
加密的用户密码,该团队回复说:
我非常同意他的看法。但我仍然想知道为什么之前会将此类代码放入代码库中。
文章到此结束。我希望它能解释 MetaMask 钱包如何存储你的钱包秘密的基础知识。如果您想了解更多详细信息,可以在他们的 GitHub 存储库 https://github.com/MetaMask 中阅读其源代码。
==========================🦊附录🦊======================= ===
用于创建新钱包的非常粗略的代码路径:
const seedPhrase = await createNewAccount(password)
createNewAccount: (password) => dispatch(createNewVaultAndGetSeedPhrase(password))
export function createNewVaultAndGetSeedPhrase(password){ .... await createNewVault(password) const seedWords = await verifySeedPhrase() .... }
async createNewVaultAndKeychain(password){ vault = await this.keyringController.createNewVaultAndKeychain(password) }
createNewVaultAndKeychain (password) { return this.persistAllKeyrings(password) .then(this.createFirstKeyTree.bind(this)) .then(this.persistAllKeyrings.bind(this, password)) .then(this.setUnlocked.bind(this)) .then(this.fullUpdate.bind(this)) }
createFirstKeyTree () { ... return this.addNewKeyring('HD Key Tree', { numberOfAccounts: 1 }) ... }
addNewKeyring (type, opts) { ... const Keyring = this.getKeyringClassForType(type) const keyring = new Keyring(opts) ... }
addAccounts (numberOfAccounts = 1) {
...
this._initFromMnemonic(bip39.generateMnemonic())
...
}