最新內容會更新在主站深入淺出區塊鏈社群
原文連結:使用 ethers.js 開發以太坊 Web 錢包 2 - 賬號 Keystore 檔案匯入匯出)
以太坊去中心化網頁錢包開發系列,將從零開始開發出一個可以實際使用的錢包,本系列文章是理論與實戰相結合,一共有四篇:建立錢包賬號、賬號Keystore檔案匯入匯出、展示錢包資訊及發起簽名交易、傳送Token(代幣),這是第二篇,主要介紹錢包賬號匯出與匯入,將對Keystore檔案的生成的原理進行介紹。
如何匯入Geth建立的賬號?
在上一篇文章,介紹瞭如何使用私鑰及助記詞來建立賬號,如果是使用已有的私鑰及助記詞,這其實也是賬號匯入的過程。
有一些同學會問,我的賬號是Geth生成的,如何匯入到錢包呢?使用Geth的同學,應該知道Geth在建立賬號時會生成一個對應keystore JSON檔案,Keystore檔案儲存加密後的私鑰資訊,因此我們需要做的就是匯入這個Keystore檔案,這個檔案通常在同步區塊資料的目錄下的keystore資料夾(如: ~/.ethereum/keystore)裡。
儘管在ethers.js 中,簡單的使用一個函式就可以完成keystore檔案的匯入,不過理解Keystore 檔案的作用及原理還是非常有必要的,當然如果你是在沒有興趣,可以直接跳到本文最後一節:使用ethers.js 實現賬號匯出匯入。
詳細解讀 Keystore 檔案
為什麼需要 Keystore 檔案
通過這篇文章理解開發HD 錢包涉及的 BIP32、BIP44、BIP39,私鑰其實就代表了一個賬號,最簡單的保管賬號的方式就是直接把私鑰儲存起來,如果私鑰檔案被人盜取,我們的數字資產將洗劫一空。
Keystore 檔案就是一種以加密的方式儲存金鑰的檔案,這樣的發起交易的時候,先從Keystore 檔案是使用密碼解密出私鑰,然後進行簽名交易。這樣做之後就會安全的多,因為只有黑客同時盜取 keystore 檔案和密碼才能盜取我們的數字資產。
Keystore 檔案如何生成的
以太坊是使用對稱加密演算法來加密私鑰生成Keystore檔案,因此對稱加密祕鑰(注意它其實也是發起交易時需要的解密祕鑰)的選擇就非常關鍵,這個祕鑰是使用KDF演算法推導派生而出。因此在完整介紹Keystore 檔案如何生成前,有必要先介紹一下KDF。
使用 KDF 生成祕鑰
密碼學KDF(key derivation functions),其作用是通過一個密碼派生出一個或多個祕鑰,即從 password 生成加密用的 key。
其實在理解開發HD 錢包涉及的 BIP32、BIP44、BIP39中介紹助記詞推匯出種子的PBKDF2演算法就是一種KDF函式,其原理是加鹽以及增加雜湊迭代次數。
而在Keystore中,是用的是Scrypt演算法,用一個公式來表示的話,派生的Key生成方程為:
DK = Scrypt(salt, dk_len, n, r, p)
其中的 salt 是一段隨機的鹽,dk_len 是輸出的雜湊值的長度。n 是 CPU/Memory 開銷值,越高的開銷值,計算就越困難。r 表示塊大小,p 表示並行度。
Litecoin 就使用 scrypt 作為它的 POW 演算法
實際使用中,還會加上一個密碼進行計算,用一張圖來表示這個過程就是:
對私鑰進行對稱加密
上面已經用KDF演算法生成了一個祕鑰,這個祕鑰就是接著進行對稱加密的祕鑰,這裡使用的對稱加密演算法是 aes-128-ctr,aes-128-ctr 加密演算法還需要用到一個引數初始化向量 iv。
Keystore檔案
好了,我們現在結合具體 Keystore檔案的內容,就很容易理解了Keystore 檔案怎麼產生的了。
{
"address":"856e604698f79cef417aab...",
"crypto":{
"cipher":"aes-128-ctr",
"ciphertext":"13a3ad2135bef1ff228e399dfc8d7757eb4bb1a81d1b31....",
"cipherparams":{
"iv":"92e7468e8625653f85322fb3c..."
},
"kdf":"scrypt",
"kdfparams":{
"dklen":32,
"n":262144,
"p":1,
"r":8,
"salt":"3ca198ce53513ce01bd651aee54b16b6a...."
},
"mac":"10423d837830594c18a91097d09b7f2316..."
},
"id":"5346bac5-0a6f-4ac6-baba-e2f3ad464f3f",
"version":3
}
來解讀一下各個欄位:
- address: 賬號地址
- version: Keystore檔案的版本,目前為第3版,也稱為V3 KeyStore。
- id : uuid
- crypto: 加密推倒的相關配置.
- cipher 是用於加密以太坊私鑰的對稱加密演算法。用的是 aes-128-ctr 。
- cipherparams 是 aes-128-ctr 加密演算法需要的引數。在這裡,用到的唯一的引數 iv。
- ciphertext 是加密演算法輸出的密文,也是將來解密時的需要的輸入。
- kdf: 指定使用哪一個演算法,這裡使用的是 scrypt。
- kdfparams: scrypt函式需要的引數
- mac: 用來校驗密碼的正確性, mac= sha3(DK[16:32], ciphertext) 下面一個小節單獨分析。
我們來完整梳理一下 Keystore 檔案的產生:
- 使用scrypt函式 (根據密碼 和 相應的引數) 生成祕鑰
- 使用上一步生成的祕鑰 + 賬號私鑰 + 引數 進行對稱加密。
- 把相關的引數 和 輸出的密文 儲存為以上格式的 JSON 檔案
如何確保密碼是對的?
當我們在使用Keystore檔案來還原私鑰時,依然是使用kdf生成一個祕鑰,然後用祕鑰對ciphertext進行解密,其過程如下:
此時細心的同學會發現,無論使用說明密碼,來進行這個操作,都會生成一個私鑰,但是最終計算的以太坊私鑰到底是不是正確的,卻不得而知。
這就是 keystore 檔案中 mac 值的作用。mac 值是 kdf輸出 和 ciphertext 密文進行SHA3-256運算的結果,顯然密碼不同,計算的mac 值也不同,因此可以用來檢驗密碼的正確性。檢驗過程用圖表示如下:
現在我們以解密的角度完整的梳理下流程,就可以得到以下圖:
用ethers.js 實現賬號匯出匯入
ethers.js 直接提供了載入keystore JSON來建立錢包物件以及加密生成keystore檔案的方法,方法如下:
// 匯入keystore Json
ethers.Wallet.fromEncryptedJson(json, password, [progressCallback]).then(function(wallet) {
// wallet
});
// 使用錢包物件 匯出keystore Json
wallet.encrypt(pwd, [progressCallback].then(function(json) {
// 儲存json
});
現在結合介面來完整的實現賬號匯出及匯入,先看看匯出,UI圖如下:
HTML 程式碼如下:
<h3>KeyStore 匯出:</h3>
<table>
<tr>
<th>密碼:</th>
<td><input type="text" placeholder="(password)" id="save-keystore-file-pwd" /></td>
</tr>
<tr>
<td> </td>
<td>
<div id="save-keystore" class="submit">匯出</div>
</td>
</tr>
</table>
上面主要定義了一個密碼輸入框和一個匯出按鈕,點選“匯出”後,處理邏輯程式碼如下:
// "匯出" 按鈕,執行exportKeystore函式
$('#save-keystore').click(exportKeystore);
exportKeystore: function() {
// 獲取密碼
var pwd = $('#save-keystore-file-pwd');
// wallet 是上一篇文章中生成的錢包物件
wallet.encrypt(pwd.val()).then(function(json) {
var blob = new Blob([json], {type: "text/plain;charset=utf-8"});
// 使用了FileSaver.js 進行檔案儲存
saveAs(blob, "keystore.json");
});
}
FileSaver.js 是可以用來在頁面儲存檔案的一個庫。
再來看看匯入keystore 檔案, UI圖如下:
<h2>載入賬號Keystore檔案</h2>
<table>
<tr>
<th>Keystore:</th>
<td><div class="file" id="select-wallet-drop">把Json檔案拖動到這裡</div><input type="file" id="select-wallet-file" /></td>
</tr>
<tr>
<th>密碼:</th>
<td><input type="password" placeholder="(password)" id="select-wallet-password" /></td>
</tr>
<tr>
<td> </td>
<td>
<div id="select-submit-wallet" class="submit disable">解密</div>
</td>
</tr>
</table>
上面主要定義了一個檔案輸入框、一個密碼輸入框及一個“解密“按鈕,因此處理邏輯包含兩部分,一是讀取檔案,二是解析載入賬號,關鍵程式碼如下:
// 使用FileReader讀取檔案,
var fileReader = new FileReader();
fileReader.onload = function(e) {
var json = e.target.result;
// 從載入
ethers.Wallet.fromEncryptedJson(json, password).then(function(wallet) {
}, function(error) {
});
};
fileReader.readAsText(inputFile.files[0]);
哈哈,恭喜大家,到這裡這裡就完整的實現了一個基於以太坊去中心化網頁錢包。
這是一條硬廣,歡迎訂閱深入淺出區塊鏈技術小專欄,目前僅需69元, 訂閱就可以檢視完整原始碼,還有其他驚喜哦~。
戳連結收看詳細的視訊課程講解。
參考文件
深入淺出區塊鏈 - 系統學習區塊鏈,打造最好的區塊鏈技術部落格。
深入淺出區塊鏈知識星球最專業技術問答社群,加入社群還可以在微信群裡和300多位區塊鏈技術愛好者一起交流。