最新的一些 Web3 开发相关技术栈更新 && MetaMask 是怎么保存你的钱包秘钥的?

345 天前
 lymanlai

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 钱包的功能而做的研究。

周报 + 最新的一些 Web3 开发相关技术栈更新

本周开始报名了波卡黑客松比赛,这次规划做一个 NFT-Fi-Twitter 的浏览器插件,可以让 Twitter 用户去发布 NFT-Gating 的 twitter 或者评论,而要查看加密的内容的用户则需要支付 $BST 才能解锁(付费解锁时会附带获得对应的 NFT 作为支付凭证),在做的过程中延伸出了一系列的技术扩展需要研究。

所有的相关信息内容都可以通过我们的 github 的 discussion 板块查看及留言讨论​,欢迎访问:​https://github.com/orgs/Web3Hacker-World/discussions

TL;DR

细节版本

MetaMask 是怎么保存你的钱包秘钥的?

原文链接: https://www.wispwisp.com/index.php/2020/12/25/how-metamask-stores-your-wallet-secret/

作为区块链领域的安全工程师 /渗透测试人员,我在研究和客户参与期间测试了许多加密钱包应用程序。在看到在不同钱包中处理秘密的不同方式后,我很好奇 MetaMask (该领域最著名的加密钱包之一)是如何做到的。好吧,如果你问我为什么不早点看 MetaMask ,因为我只是假设它是安全的,而且我发现任何问题的机会非常低。

他们的扩展移动应用程序以及应用程序中使用的由 MetaMask 创建的许多其他模块都是开源的。代码库是迄今为止我见过的所有加密钱包中最大的。但它并不难理解,因为代码写得很好并且有很多注释。

这篇文章解释了应用程序中与秘钥存储相关的不同组件。在文章的最后,我包含了一个非常粗略的代码逻辑,描述了这个浏览器扩展是怎么创建一个新钱包。请注意,本文中的钱包秘钥指的是助记词(mnemonic)和私钥。

秘钥环(Keyring)

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

加密器(encryptor)

在 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)

代码中的注释解释了 SecureKeychain 的作用:

/**
* react-native-keychain 内包裹 Keychain 的类
* 抽象 metamask 特定的功能及设置
* 同时在写入手机的 keychain 前添加额外的层
*/
class SecureKeychain {
...
}

抽象 metamask 特定的功能及设置 是指 记住我使用触摸 ID/设备密码登录 功能。查看 resetGenericPassword,getGenericPasswordsetGenericPassword 函数以了解它是如何实现的。

至于“增加一层额外的加密”,我很好奇这里使用的加密密钥是什么来执行加密?这是我找到的相关代码路径:

  1. 构造函数中的 code 用作加密密钥。(来源
  2. init(salt) 函数中,使用参数 salt 创建 SecureKeychain 对象(来源
  3. init 使用参数 props.foxCode( Source ) 调用该函数(来源

foxCode 又是什么?我在 手机钱包仓库MetaMask 组织 下的所有公共仓库中搜索 ,但没有结果。嗯🤔,应用程序二进制文件怎么样?

我用这个工具下载了 Metamask APK ,用 jadx 解包,搜索字符串 foxCode,发现了这个:

等等,所以 foxCode 等于字符串“encrypt”?使用硬编码字符串加密某些内容有什么意义?🤔

我给 Metamask 安全团队发了一封邮件,询问为什么用硬编码字符串( foxCode )encrypt 加密的用户密码,该团队回复说:

我非常同意他的看法。但我仍然想知道为什么之前会将此类代码放入代码库中。

结束

文章到此结束。我希望它能解释 MetaMask 钱包如何存储你的钱包秘密的基础知识。如果您想了解更多详细信息,可以在他们的 GitHub 存储库 https://github.com/MetaMask 中阅读其源代码。

==========================🦊附录🦊======================= ===

用于创建新钱包的非常粗略的代码路径:

  1. metamask-extension/ui/app/pages/first-time-flow/first-time-flow.component.js
const seedPhrase = await createNewAccount(password)
  1. metamask-extension/ui/app/pages/first-time-flow/first-time-flow.container.js
createNewAccount: (password) => dispatch(createNewVaultAndGetSeedPhrase(password))
  1. metamask-extension/ui/app/store/actions.js
export function createNewVaultAndGetSeedPhrase(password){
   ....
   await createNewVault(password)
   const seedWords = await verifySeedPhrase()
   ....
}
  1. metamask-extension/app/scripts/metamask-controller.js
async createNewVaultAndKeychain(password){
   vault = await this.keyringController.createNewVaultAndKeychain(password)
}
  1. KeyringController/blob/master/index.js
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))
}
  1. MetaMask/KeyringController/blob/master/index.js
createFirstKeyTree () {
   ...
   return this.addNewKeyring('HD Key Tree', { numberOfAccounts: 1 })
   ...
}
  1. MetaMask/KeyringController/blob/master/index.js
addNewKeyring (type, opts) {
   ...
   const Keyring = this.getKeyringClassForType(type)
   const keyring = new Keyring(opts)
   ...
}
  1. MetaMask/eth-hd-keyring/blob/master/index.js
addAccounts (numberOfAccounts = 1) {
    ...
    this._initFromMnemonic(bip39.generateMnemonic())
    ...
}

往期回顾

1020 次点击
所在节点    Web3
1 条回复
lymanlai
345 天前

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/943938

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX