Eth Keystore

Overview

这是一个以太坊 keystore 文件的内容,我刚刚拿工具生成的:

{

    "address": "0x7579e895f588d668c181710d74cc4c0ed4c8871b",

    "crypto": {

        "cipher": "aes-128-ctr",

        "ciphertext": "d7e10d319e6a03bd6d4461da69c818418fc25f138bf98b854baf6fdd6feeeea5",

        "cipherparams": {

            "iv": "deb0e25a32ad2c8f28fa3d83c90c6a37"

        },

        "kdf": "scrypt",

        "kdfparams": {

            "dklen": 32,

            "n": 262144,

            "p": 1,

            "r": 8,

            "salt": "4595597c7002cfc47db91cbd1bf6faa9d9d4b45a205563537eb4e8bc33b0720b"

        },

        "mac": "2968ba58ac928feb6f95564e0083f186c4a605edfdffbbe35a7d47e0f32ed03c"

    },

    "id": "48258957-776e-40b7-9ebd-068dd8f1156a",

    "version": 3

}

在了解每个字段含义之前,后面不会再解释的基础名词如下:

  • 私钥(private key): 长度为 256 bit = 32 bytes 的随机的密钥,一切加密的源头
  • 公钥(public key): 和私钥对应的公钥, 长度为 512 bit = 64 bytes , 由 私钥 + 椭圆曲线加密 计算出来。可以由私钥推出公钥,无法从公钥推出私钥。具体的椭圆曲线算法参数 secp256k1 和比特币一样。
  • 账户地址(address): 由公钥进行 keccak256 hash 运算后取后20个字节,以太坊给账号作标记的地址。
  • 密码(password): 用于加密 keystore 文件的密码,可以是任意长度的字符串。保护 keystore 文件不那么容易被破解。

再来解释上面 JSON 文件里每个字段的含义:

{

    "address": "地址",

    "crypto": {

        "cipher": "使用的(对称加密的)加密方法名,默认使用对称 [AES 加密] 中的某一种",

        "ciphertext": "私钥 + 密码 被该 cipher 加密方式加密后的结果",

        "cipherparams": {

            "iv": "该 cipher 加密方式需要的参数 (随机初始向量) `initialisation vector` "

        },

        "kdf": "使用的(基于密码的)加密方法名,`Key-derivation function` 一般用 [Scrypt] 也支持 [PBKDF2]",

        "kdfparams": { // 该 kdf 加密方式需要的参数,除了盐值是随机生成的,其他参数推荐使用默认。

            "dklen": 32, // 生成的 key 的 bytes 长度

            "n": 262144, // 迭代次数,线性影响计算难度和内存占用

            "p": 1, // 并行计算数

            "r": 8, // 块大小,和 n 一起线性影响计算难度和内存占用

            "salt": "盐值,随机生成的混淆参数"

        },

        "mac": "message authentication code 消息验证码,用于校验 password 是否正确"

    },

    "id": "随机生成的 UUID,和 keystore 加密本身关系不大",

    "version": 3 // 版本号

}

整个 keystore 的加密,通过两类加密模式(cipher + kdf)来加密密码和私钥。从而达到的效果是:只有知道密码的用户才能利用 keystore 文件解出私钥,而想要暴力破解 keystore 也比较困难。

其中涉及到的加密算法 WIKI 链接:也许未来会进一步学习下啊巴啊巴

Generate Keystore

来看看生成 keystore 的流程,目标是一个 keystore 文件和我们心中的密码,比如"some_password",它蕴含了一个32字节的私钥。私钥是生成 keystore 的数据源头,所以是整个算法过程中的参数。

除此之外,加上上面 JSON 文件里的众多参数里,可以分为三类:

随机的输入参数,由随机器/用户定义生成的内容

  • 用户定义,不在文件里的私钥
  • 用户定义,不在文件里的密码
  • 随机生成: cipherparams.iv cipher 初始向量
  • 随机生成: kdfparams.salt kdf 盐值

算法需要的参数或固定参数,控制了加密的强度或者其他标记

  • kdfparams.dklen/n/p/r
  • version
  • id

算法的计算结果,由上面两类数据生成,写入文件为了校验/使用

  • address: 对应了公钥
  • ciphertext: 私钥加密结果
  • mac: 验证码,用于在解密时验证密码是否正确

除掉相对固定的参数,整个方法可以认为是 \(F(prikey, password, iv, salt) \rightarrow mac, ciphertext\)

另外有 \(G(prikey) \rightarrow pubkey, address\) ,单独就可以生成。

Generate 算法步骤

私钥推出公钥和地址

\[G(prikey) \rightarrow pubkey, address\]

password + salt 经过 kdf 算法(Scrypt) 得到中间计算结果 key (256bits 32bytes)

\[Scrypt(password, salt, kdfparams) \rightarrow key\]

prikey 作为初始数据,经过对称加密 cipher 算法(AES128) ,应用数据:key 的前16字节,iv 的前16字节,得到加密后的 ciphertext

\[AES128(prikey, key[0..16], iv[0..16]) \rightarrow ciphertext\]

key[16..32] + ciphertext 合起来,经过 keccak256算法 作 hash256,得到结果 mac

\[keccak256(key[16..32] + ciphertext) \rightarrow mac\]

经过这四步,即把私钥和密码加密出来,计算出了校验用的 ciphertextmac

Decrypt Keystore

解码 Keystore 需要 keystore 文件和密码,密码正确的结果是解出来的密钥,密码错误会出错。

Decrypt 算法步骤

根据输入的 password' 计算 key' ,此步骤算法同上面的 #2,因为输入和中间结果 key 不一定正确,后面加了一个 ' 做区分

\[Scrypt(password', salt, kdfparams) \rightarrow key'\]

key'[16..32] + ciphertext 合起来,经过 keccak256算法 作 hash256,得到结果 mac', 和上面的步骤4一样,计算出 mac' 校验密码是否正确。

\[keccak256(key'[16..32] + ciphertext) \rightarrow mac'\]

验证 mac' 是否等于 mac

\[(mac'==mac)?continue:exit\]

ciphertext 作为初始数据,经过对称加密 cipher 算法(AES128) ,应用数据:key' 的前16字节,iv 的前16字节,恢复出私钥

\[AES128(ciphertext, key'[0..16], iv[0..16]) \rightarrow prikey\]

实现

加密和解密的方法,拿 Rust 实现了一下: 仓库链接

总结

完整的思维导图

2022-07/keystore.png

推荐拓展阅读:

  • https://julien-maffre.medium.com/what-is-an-ethereum-keystore-file-86c8c5917b97
  • https://betterprogramming.pub/understanding-ethereum-cryptography-3ef7429eddce
  • https://cryptobook.nakov.com/symmetric-key-ciphers/