進入keystore管理以太坊私鑰的障礙很大,主要是因爲以太坊客戶端在直接的命令行或圖形界面下隱藏了大部分的密碼複雜性。
例如,用geth:
$ geth account new
Your new account is locked with a password.
Please give a password. Do not forget this password.
Passphrase:
Repeat passphrase:
Address: {008aeeda4d805471df9b2a5b0f38a0c3bcba786b}
$ geth account list Account #0: {8a1c4d573cc29a96547816522cfe0b266e88abac} keystore:~/.ethereum/keystore/UTC---- 008aeeda4d805471df9b2a5b0f38a0c3bcba786b
我只需要輸入3個單詞就能創建一個新賬戶。然後輸入兩遍密碼,就這麼簡單!我的以太坊keystore文件就創建了。 你需要把那些非常珍貴的keystore文件備份、存儲在一個或多個隱祕的位置,這樣就只有你能獲取這些文件並取到資金。 從經驗來看,當我沒有完全理解一個新概念的微妙之處,並且過分依賴抽象層和現有工具來讓我的生活更輕鬆時,我很有可能忘記一些事情,採取不必要的捷徑並且搞砸它。搞砸的時候,可能就是會以我辛苦掙來的以太幣被永久鎖定而告終(幸好它還沒有發生!)。 幸運的是,作爲一個以太坊用戶,你能搞砸的方式並不多: 你丟失了你的keystore文件 你忘記了和文件關聯的密碼 或者以上兩者你都搞砸了。
在本文中,我們將爲你介紹以太坊私鑰是如何從 keystore 文件中算出來的。我們將討論加密函數(對稱加密,密鑰生成函數,SHA3 哈希算法),但我們會儘可能的保證簡明直接地來解釋上述問題。
什麼是keystore文件?
以太坊的 keystore 文件(Linux 系統存儲在 ~/.ethereum/keystore 或者 Windows 系統存儲在 C:\Users\Appdata/Roaming/Ethereum/keystore)是你獨有的、用於簽署交易的以太坊私鑰的加密文件。如果你丟失了這個文件,你就丟失了私鑰,意味着你失去了簽署交易的能力,意味着你的資金被永久的鎖定在了你的賬戶裏。 當然,你可以直接把你的以太坊私鑰存儲在一個加密文件裏,但是這樣你的私鑰容易受到攻擊,攻擊者簡單的讀取你的文件、用你的私鑰簽署交易,把錢轉到他們的賬戶中。你的幣會在你意識到發生什麼了之前的短時間內丟失。 這就是以太坊 keystore 文件被創建的原因:它允許你以加密的方式存儲密鑰。這是安全性(一個攻擊者需要 keystore 文件和你的密碼才能盜取你的資金)和可用性(你只需要keystore文件和密碼就能用你的錢了)兩者之間完美的權衡。 爲了讓你發送一些以太幣,大多數的以太坊客戶端會讓你輸入密碼(與創建賬戶時密碼相同)以解密你的以太坊私鑰。一旦解密,客戶端程序就得到私鑰簽署交易,允許你移動資金。
Keystore文件是什麼樣子的?
如果你打開一個你的賬戶文件,它看起來像這樣(取自這裏):
$ cat ~/.ethereum/keystore/UTC---- 008aeeda4d805471df9b2a5b0f38a0c3bcba786b
{ "crypto" :
{
"cipher" : "aes-128-ctr",
"cipherparams" : {
"iv" : "83dbcc02d8ccb40e466191a123791e0e" },
"ciphertext" : "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c",
"kdf" : "scrypt",
"kdfparams" : {
"dklen" : 32,
"n" : 262144,
"r" : 1,
"p" : 8,
"salt" : "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19"
},
"mac" : "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097"
},
"id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6",
"version" : 3
}
一個有許多神奇的參數的粗笨的 JSON 文件,似乎與複雜的加密操作相關。這絕不吸引人。 讓我們深入理解一下 如果你看這個 keystore 文件的結構,你會看到大部分內容都是在“crypto”中的:
"crypto" : {
"cipher" : "aes-128-ctr",
"cipherparams" : {
"iv" : "83dbcc02d8ccb40e466191a123791e0e"
},
"ciphertext" : "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c",
"kdf" : "scrypt",
"kdfparams" : {
"dklen" : 32,
"n" : 262144,
"r" : 1,
"p" : 8,
"salt" : "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19"
},
"mac" : "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097"
},
這包括:
- cipher:對稱 AES 算法的名稱;
- cipherparams:上述 cipher 算法需要的參數;
- ciphertext:你的以太坊私鑰使用上述 cipher 算法進行加密;
- kdf:密鑰生成函數,用於讓你用密碼加密 keystore 文件;
- kdfparams:上述 kdf 算法需要的參數;
- Mac:用於驗證密碼的代碼。
讓我們看看他們是如何協同工作的,如何在你的密碼下保護 keystore 文件。
加密你的私鑰
就像之前提到的,一個以太坊賬戶就是用於加密簽署交易的一個私鑰 —公鑰對。爲了確保你的私鑰沒有在文件中明文存儲(即任何人只要能得到這個文件就能讀),使用強對稱算法(cipher)對其加密至關重要。 這些對稱算法使用密鑰來加密數據。加密後的數據可以使用相同的方法和同樣的密鑰來解密,因此算法命名爲對稱算法。在本文中,我們稱這個對稱密鑰爲解密密鑰,因爲它將用於對我們的以太坊私鑰進行解密。
以下是 cipher,cipherparams 和 ciphertext 對應的概念:
- Cipher 是用於加密以太坊私鑰的對稱加密算法。此處cipher用的是 aes-128-ctr 加密模式
- Cipherparams 是 aes-128-ctr 加密算法需要的參數。
在這裏,用到的唯一的參數 iv,是aes-128-ctr加密算法需要的初始化向量。
- Ciphertext 密文是 aes-128-ctr 函數的加密輸入。
所以,在這裏,你已經有了進行解密以太坊私鑰計算所需要的一切…等等。你需要首先取回你的解密密鑰。
1 -ciphertex 密文的對稱解密-
2. 用你的密碼來保護它
要確保解鎖你的賬戶很容易,你不需要記住你的每一個又長又非用戶友好型的用於解密 ciphertext 密文解密密鑰。相反,以太坊開發者選擇了基於密碼的保護,也就是說你只需要輸入密碼就能拿回解密密鑰。
爲了能做到這一點,以太坊用了一個密鑰生成函數,輸入密碼和一系列參數就能計算解密密鑰。 這就是 kdf 和 kdfparams 的用途:
- kdf 是一個密鑰生成函數,根據你的密碼計算(或者取回)解密密鑰。在這裏,kdf 用的是scrypt算法。
- kdfparams 是scrypt函數需要的參數。在這裏,簡單來說,dklen、n、r、p 和 salt 是 kdf 函數的參數。
更多關於 scrypt 函數的信息可以在這裏找到。 在這裏,用 kdfparams 參數對 scrypt 函數進行調整,反饋到我們的密碼中,你就會得到解密密鑰也就是密鑰生成函數的輸出。
2 -用密碼生成密鑰-
3. 確保你的密碼是對的
我們描述了用密碼和 keystore 文件生成以太坊私鑰所需要的所有東西。然而,如果解鎖賬戶的密碼錯誤會發生什麼?
根據迄今爲止我們所看到的,所有操作(密碼派生和解密)都會成功,但是最終計算的以太坊私鑰不是正確的,這首先違背了密鑰文件的使用初衷!
我們要保證輸入解鎖賬戶的密碼是正確的,和最初創建 keystore 文件時一樣(回想一下 geth 下創建新賬戶時兩次輸入的密碼)。 這就是 keystore 文件中 mac 值起作用的地方。在密鑰生成函數執行之後,它的輸出(解密密鑰)和 ciphertext 密文就被處理【注1】,並且和 mac(就像一種認可的印章)作比較。如果結果和 mac 相同,那麼密碼就是正確的,並且解密就可以開始了。
【注1】這裏有點簡略了。在和 mac 進行比較之前,解密密鑰(左起第二字節開始的16字節)要和 ciphertext 密文連接在一起,並進行哈希散列(用SHA3-256的方法)。 更多信息請訪問這裏。
把所有的都放到一起考慮 唷!
如果你已經做到了這一點,那麼恭喜你! 讓我們回顧一下我們描述的3個函數。
- 首先,你輸入了密碼,這個密碼作爲 kdf 密鑰生成函數的輸入,來計算解密密鑰。
- 然後,剛剛計算出的解密密鑰和 ciphertext 密文連接並進行處理,和 mac 比較來確保密碼是正確的。
- 最後,通過 cipher 對稱函數用解密密鑰對 ciphertext 密文解密。 瞧!解密的結果是你的以太坊私鑰。
你可以在這裏看看整個過程:
就像你從圖中可以看到的,整個過程可以看做一個黑盒(不過,圖中是個灰盒),你的密碼是惟一的輸入,你的以太坊私鑰是惟一的輸出。所需的其他信息都可以在你的以太坊賬戶創建時生成的keystore文件中獲得。 由於這個原因,請確保你的密碼足夠強(並且無論如何你要記住它!)才能保證即使攻擊者偷到了你的keystore文件也不能輕易得到你的私鑰。
測試數據
Details:
- Address:
008aeeda4d805471df9b2a5b0f38a0c3bcba786b
- ICAP:
XE542A5PZHH8PYIZUBEJEO0MFWRAPPIL67
- UUID:
3198bc9c-6672-5ab3-d9954942343ae5b6
- Password:
testpassword
- Secret:
7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d
PBKDF2-SHA-256
Test vector using AES-128-CTR and PBKDF2-SHA-256:
File contents of ~/.web3/keystore/3198bc9c-6672-5ab3-d9954942343ae5b6.json
:
{ "crypto" : { "cipher" : "aes-128-ctr", "cipherparams" : { "iv" : "6087dab2f9fdbbfaddc31a909735c1e6" }, "ciphertext" : "5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46", "kdf" : "pbkdf2", "kdfparams" : { "c" : 262144, "dklen" : 32, "prf" : "hmac-sha256", "salt" : "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd" }, "mac" : "517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2" }, "id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6", "version" : 3 }
Intermediates:
- Derived key:
f06d69cdc7da0faffb1008270bca38f5e31891a3a773950e6d0fea48a7188551
- MAC Body:
e31891a3a773950e6d0fea48a71885515318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46
- MAC
517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2
- Cipher key:
f06d69cdc7da0faffb1008270bca38f5
Scrypt
Test vector using AES-128-CTR and Scrypt:
{ "crypto" : { "cipher" : "aes-128-ctr", "cipherparams" : { "iv" : "83dbcc02d8ccb40e466191a123791e0e" }, "ciphertext" : "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c", "kdf" : "scrypt", "kdfparams" : { "dklen" : 32, "n" : 262144, "r" : 1, "p" : 8, "salt" : "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19" }, "mac" : "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097" }, "id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6", "version" : 3 }
Intermediates:
- Derived key:
fac192ceb5fd772906bea3e118a69e8bbb5cc24229e20d8766fd298291bba6bd
- MAC Body
bb5cc24229e20d8766fd298291bba6bdd172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c
- MAC:
2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097
- Cipher key:
fac192ceb5fd772906bea3e118a69e8b
從keystore提取Private Key的源代碼
package main
import (
"encoding/hex"
"flag"
"fmt"
"io/ioutil"
"os"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/crypto"
)
func main() {
ksfilefullpath := ""
kstype := ""
kspassword := ""
flags := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
flags.StringVar(&ksfilefullpath, "keystore-src", "", "`path` to keystore to be processed")
flags.StringVar(&kstype, "keystore-type", "", "keystore type: `pem`,`keystore`")
flags.StringVar(&kspassword, "keystore-password", "", "keystore password")
if err := flags.Parse(os.Args[1:]); err != nil {
fmt.Fprintln(os.Stderr, "Failed to parse flags:", err)
os.Exit(1)
}
keyjson, err := ioutil.ReadFile(ksfilefullpath)
if err != nil {
fmt.Println(err)
}
// Decrypt with the correct password
key, err := keystore.DecryptKey(keyjson, kspassword)
if err != nil {
fmt.Println("test : json key failed to decrypt: %v", err)
}
fmt.Println("Private Key= " + hex.EncodeToString(crypto.FromECDSA(key.PrivateKey)))
}
編譯
運行
提取的Private Key: ab979c2c4d092212af817802f31313bd6f4144b4b9cb289f659c29f5cabbd428
去https://www.myetherwallet.com/#view-wallet-info,上傳keystore
看看結果
Hola, 一模一樣