docs: update go account management page (#25189)
* update go accounts page * refine draft * apply suggestions from code review rewords description of key encryption and adds links to Scrypt docs. Adds warning that best practise is to use Clef.
This commit is contained in:
parent
b222b2e507
commit
8d276f51bf
|
@ -3,70 +3,48 @@ title: Go Account Management
|
|||
sort_key: D
|
||||
---
|
||||
|
||||
To provide Ethereum integration for your native applications, the very first thing you
|
||||
should be interested in doing is account management.
|
||||
Geth provides a simple, yet thorough accounts package that includes all the tools developers
|
||||
need to leverage all the security of Geth's crypto implementation in a Go native application.
|
||||
The account management is done client side with all sensitive data held inside the application.
|
||||
This gives the user control over access permissions without relying on any third party.
|
||||
|
||||
Although all current leading Ethereum implementations provide account management built in,
|
||||
it is ill advised to keep accounts in any location that is shared between multiple
|
||||
applications and/or multiple people. The same way you do not entrust your ISP (who is
|
||||
after all your gateway into the internet) with your login credentials; you should not
|
||||
entrust an Ethereum node (who is your gateway into the Ethereum network) with your
|
||||
credentials either.
|
||||
**Note Geth's built-in account management is convenient and straightforward to use, but
|
||||
best practise is to use the external tool *Clef* for key management.**
|
||||
|
||||
The proper way to handle user accounts in your native applications is to do client side
|
||||
account management, everything self-contained within your own application. This way you
|
||||
can ensure as fine grained (or as coarse) access permissions to the sensitive data as
|
||||
deemed necessary, without relying on any third party application's functionality and/or
|
||||
vulnerabilities.
|
||||
{:toc}
|
||||
|
||||
To support this, `go-ethereum` provides a simple, yet thorough accounts package that gives
|
||||
you all the tools to do properly secured account management via encrypted keystores and
|
||||
passphrase protected accounts. You can leverage all the security of the `go-ethereum`
|
||||
crypto implementation while at the same time running everything in your own application.
|
||||
- this will be removed by the toc
|
||||
|
||||
## Encrypted keystores
|
||||
|
||||
Although handling accounts locally to an application does provide certain security
|
||||
guarantees, access keys to Ethereum accounts should never lay around in clear-text form.
|
||||
As such, we provide an encrypted keystore that provides the proper security guarantees for
|
||||
you without requiring a thorough understanding from your part of the associated
|
||||
cryptographic primitives.
|
||||
Access keys to Ethereum accounts should never be stored in plain-text. Instead, they should be
|
||||
stored encrypted so that even if the mobile device is accessed by a malicious third party the
|
||||
keys are still hidden under an additional layer of security. Geth provides a keystore that enables
|
||||
developers to store keys securely. The Geth keystore uses [Scrypt][scrypt-docs] to store keys that are encoded
|
||||
using the [`secp256k1`][secp256k1] elliptic curve. Accounts are stored on disk in the
|
||||
[Web3 Secret Storage][wss] format. Developers should be aware of these implementation details
|
||||
but are not required to deeply understand the cryptographic primitives in order to use the keystore.
|
||||
|
||||
The important thing to know when using the encrypted keystore is that the cryptographic
|
||||
primitives used within can operate either in *standard* or *light* mode. The former
|
||||
provides a higher level of security at the cost of increased computational burden and
|
||||
resource consumption:
|
||||
One thing that should be understood, though, is that the cryptographic primitives underpinning the
|
||||
keystore can operate in light or standard mode. Light mode is computationally cheaper, while standard
|
||||
mode has extra security. Light mode is appropriate for mobile devices, but developers should be
|
||||
aware that there is a security trade-off.
|
||||
|
||||
* *standard* needs 256MB memory and 1 second processing on a modern CPU to access a key
|
||||
* *light* needs 4MB memory and 100 millisecond processing on a modern CPU to access a key
|
||||
* standard needs 256MB memory and 1 second processing on a modern CPU to access a key
|
||||
* light needs 4MB memory and 100 millisecond processing on a modern CPU to access a key
|
||||
|
||||
As such, *standard* is more suitable for native applications, but you should be aware of
|
||||
the trade-offs nonetheless in case you you're targeting more resource constrained
|
||||
environments.
|
||||
|
||||
*For those interested in the cryptographic and/or implementation details, the key-store
|
||||
uses the `secp256k1` elliptic curve as defined in the [Standards for Efficient
|
||||
Cryptography](https://www.secg.org/sec2-v2.pdf), implemented by the [`libsecp256k`](https://github.com/bitcoin-core/secp256k1) library and wrapped by
|
||||
[`github.com/ethereum/go-ethereum/accounts`](https://godoc.org/github.com/ethereum/go-ethereum/accounts). Accounts are stored on disk in
|
||||
the [Web3 Secret Storage](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) format.*
|
||||
|
||||
### Keystores from Go
|
||||
|
||||
The encrypted keystore is implemented by the
|
||||
[`accounts.Manager`](https://godoc.org/github.com/ethereum/go-ethereum/accounts#Manager)
|
||||
struct from the
|
||||
[`github.com/ethereum/go-ethereum/accounts`](https://godoc.org/github.com/ethereum/go-ethereum/accounts)
|
||||
package, which also contains the configuration constants for the *standard* or *light*
|
||||
security modes described above. Hence to do client side account management from Go, you'll
|
||||
need to import only the `accounts` package into your code:
|
||||
The encrypted keystore is implemented by the [`accounts.Manager`][accounts-manager] struct
|
||||
from the [`accounts`][accounts-pkg] package, which also contains the configuration constants for the
|
||||
*standard* or *light* security modes described above. Hence client side account management
|
||||
simply requires importing the `accounts` package into the application code.
|
||||
|
||||
```go
|
||||
import "github.com/ethereum/go-ethereum/accounts"
|
||||
import "github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
import "github.com/ethereum/go-ethereum/common"
|
||||
```
|
||||
|
||||
Afterwards you can create a new encrypted account manager via:
|
||||
Afterwards a new encrypted account manager can be created via:
|
||||
|
||||
```go
|
||||
ks := keystore.NewKeyStore("/path/to/keystore", keystore.StandardScryptN, keystore.StandardScryptP)
|
||||
|
@ -74,69 +52,60 @@ am := accounts.NewManager(&accounts.Config{InsecureUnlockAllowed: false}, ks)
|
|||
```
|
||||
|
||||
The path to the keystore folder needs to be a location that is writable by the local user
|
||||
but non-readable for other system users (for security reasons obviously), so we'd
|
||||
recommend placing it either inside your user's home directory or even more locked down for
|
||||
backend applications.
|
||||
but non-readable for other system users, such as inside the user's home directory.
|
||||
|
||||
The last two arguments of [`keystore.NewKeyStore`][keystore] are the crypto parameters defining
|
||||
how resource-intensive the keystore encryption should be. The options are
|
||||
[`accounts.StandardScryptN, accounts.StandardScryptP`, `accounts.LightScryptN,
|
||||
accounts.LightScryptP`][pkg-constants] or custom values (requiring understanding of the underlying
|
||||
cryptography). The *standard* version is recommended.
|
||||
|
||||
The last two arguments of
|
||||
[`keystore.NewKeyStore`](https://godoc.org/github.com/ethereum/go-ethereum/accounts/keystore#NewKeyStore)
|
||||
are the crypto parameters defining how resource-intensive the keystore encryption should
|
||||
be. You can choose between [`accounts.StandardScryptN, accounts.StandardScryptP`,
|
||||
`accounts.LightScryptN,
|
||||
accounts.LightScryptP`](https://godoc.org/github.com/ethereum/go-ethereum/accounts#pkg-constants)
|
||||
or specify your own numbers (please make sure you understand the underlying cryptography
|
||||
for this). We recommend using the *standard* version.
|
||||
|
||||
## Account lifecycle
|
||||
|
||||
Having created an encrypted keystore for your Ethereum accounts, you can use this account
|
||||
manager for the entire account lifecycle requirements of your native application. This
|
||||
includes the basic functionality of creating new accounts and deleting existing ones; as
|
||||
well as the more advanced functionality of updating access credentials, exporting existing
|
||||
accounts, and importing them on another device.
|
||||
Once an encrypted keystore for Ethereum accounts exists it, it can be used to manage accounts for the
|
||||
entire account lifecycle requirements of a Go native application. This includes the basic functionality
|
||||
of creating new accounts and deleting existing ones as well as updating access credentials,
|
||||
exporting existing accounts, and importing them on other devices.
|
||||
|
||||
Although the keystore defines the encryption strength it uses to store your accounts,
|
||||
there is no global master password that can grant access to all of them. Rather each
|
||||
account is maintained individually, and stored on disk in its [encrypted
|
||||
format](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition)
|
||||
individually, ensuring a much cleaner and stricter separation of credentials.
|
||||
Although the keystore defines the encryption strength it uses to store accounts, there is no global master
|
||||
password that can grant access to all of them. Rather each account is maintained individually, and stored on
|
||||
disk in its [encrypted format][wss] individually, ensuring a much cleaner and stricter separation of
|
||||
credentials.
|
||||
|
||||
This individuality however means that any operation requiring access to an account will
|
||||
need to provide the necessary authentication credentials for that particular account in
|
||||
the form of a passphrase:
|
||||
This individuality means that any operation requiring access to an account will need to provide the
|
||||
necessary authentication credentials for that particular account in the form of a passphrase:
|
||||
|
||||
* When creating a new account, the caller must supply a passphrase to encrypt the account
|
||||
with. This passphrase will be required for any subsequent access, the lack of which
|
||||
will forever forfeit using the newly created account.
|
||||
|
||||
* When deleting an existing account, the caller must supply a passphrase to verify
|
||||
ownership of the account. This isn't cryptographically necessary, rather a protective
|
||||
measure against accidental loss of accounts.
|
||||
|
||||
* When updating an existing account, the caller must supply both current and new
|
||||
passphrases. After completing the operation, the account will not be accessible via the
|
||||
old passphrase any more.
|
||||
|
||||
* When exporting an existing account, the caller must supply both the current passphrase
|
||||
to decrypt the account, as well as an export passphrase to re-encrypt it with before
|
||||
returning the key-file to the user. This is required to allow moving accounts between
|
||||
machines and applications without sharing original credentials.
|
||||
|
||||
* When importing a new account, the caller must supply both the encryption passphrase of
|
||||
the key-file being imported, as well as a new passhprase with which to store the
|
||||
account. This is required to allow storing account with different credentials than used
|
||||
for moving them around.
|
||||
|
||||
*Please note, there is no recovery mechanisms for losing the passphrases. The
|
||||
cryptographic properties of the encrypted keystore (if using the provided parameters)
|
||||
guarantee that account credentials cannot be brute forced in any meaningful time.*
|
||||
***Please note, there are no recovery mechanisms for lost passphrases. The
|
||||
cryptographic properties of the encrypted keystore (using the provided parameters)
|
||||
guarantee that account credentials cannot be brute forced in any meaningful time.***
|
||||
|
||||
### Accounts from Go
|
||||
|
||||
An Ethereum account is implemented by the
|
||||
[`accounts.Account`](https://godoc.org/github.com/ethereum/go-ethereum/accounts#Account)
|
||||
struct from the
|
||||
[`github.com/ethereum/go-ethereum/accounts`](https://godoc.org/github.com/ethereum/go-ethereum/accounts)
|
||||
package. Assuming we already have an instance of an
|
||||
[`accounts.Manager`](https://godoc.org/github.com/ethereum/go-ethereum/accounts#Manager)
|
||||
called `am` from the previous section, we can easily execute all of the described
|
||||
lifecycle operations with a handful of function calls (error handling omitted).
|
||||
An Ethereum account is implemented by the [`accounts.Account`][accounts-account] struct from
|
||||
the Geth [accounts][accounts-pkg] package. Assuming an instance of an
|
||||
[`accounts.Manager`][accounts-manager] called `am` exists, all of the described lifecycle
|
||||
operations can be executed with a handful of function calls (error handling omitted).
|
||||
|
||||
```go
|
||||
// Create a new account with the specified encryption passphrase.
|
||||
|
@ -158,58 +127,47 @@ _ = ks.Delete(newAcc, "Update password")
|
|||
impAcc, _ := ks.Import(jsonAcc, "Export password", "Import password")
|
||||
```
|
||||
|
||||
*Although instances of
|
||||
[`accounts.Account`](https://godoc.org/github.com/ethereum/go-ethereum/accounts#Account)
|
||||
can be used to access various information about specific Ethereum accounts, they do not
|
||||
contain any sensitive data (such as passphrases or private keys), rather act solely as
|
||||
identifiers for client code and the keystore.*
|
||||
*Although instances of [`accounts.Account`][accounts-account] can be used to access various information about
|
||||
specific Ethereum accounts, they do not contain any sensitive data (such as passphrases or private keys),
|
||||
rather they act solely as identifiers for client code and the keystore.*
|
||||
|
||||
## Signing authorization
|
||||
|
||||
As mentioned above, account objects do not hold the sensitive private keys of the
|
||||
associated Ethereum accounts, but are merely placeholders to identify the cryptographic
|
||||
keys with. All operations that require authorization (e.g. transaction signing) are
|
||||
performed by the account manager after granting it access to the private keys.
|
||||
Account objects do not hold the sensitive private keys of the associated Ethereum accounts.
|
||||
Account objects are placeholders that identify the cryptographic keys. All operations that
|
||||
require authorization (e.g. transaction signing) are performed by the account manager after
|
||||
granting it access to the private keys.
|
||||
|
||||
There are a few different ways one can authorize the account manager to execute signing
|
||||
There are a few different ways to authorize the account manager to execute signing
|
||||
operations, each having its advantages and drawbacks. Since the different methods have
|
||||
wildly different security guarantees, it is essential to be clear on how each works:
|
||||
|
||||
* **Single authorization**: The simplest way to sign a transaction via the account
|
||||
manager is to provide the passphrase of the account every time something needs to be
|
||||
signed, which will ephemerally decrypt the private key, execute the signing operation
|
||||
and immediately throw away the decrypted key. The drawbacks are that the passphrase
|
||||
needs to be queried from the user every time, which can become annoying if done
|
||||
frequently; or the application needs to keep the passphrase in memory, which can have
|
||||
security consequences if not done properly; and depending on the keystore's configured
|
||||
strength, constantly decrypting keys can result in non-negligible resource
|
||||
requirements.
|
||||
* **Multiple authorizations**: A more complex way of signing transactions via the account
|
||||
manager is to unlock the account via its passphrase once, and allow the account manager
|
||||
to cache the decrypted private key, enabling all subsequent signing requests to
|
||||
complete without the passphrase. The lifetime of the cached private key may be managed
|
||||
manually (by explicitly locking the account back up) or automatically (by providing a
|
||||
timeout during unlock). This mechanism is useful for scenarios where the user may need
|
||||
to sign many transactions or the application would need to do so without requiring user
|
||||
input. The crucial aspect to remember is that **anyone with access to the account
|
||||
manager can sign transactions while a particular account is unlocked** (e.g.
|
||||
application running untrusted code).
|
||||
* **Single authorization**: The simplest way to sign a transaction via the account
|
||||
manager is to provide the passphrase of the account every time something needs to be
|
||||
signed, which will ephemerally decrypt the private key, execute the signing operation
|
||||
and immediately throw away the decrypted key. The drawbacks are that the passphrase
|
||||
needs to be queried from the user every time, which can become annoying if done
|
||||
frequently or the application needs to keep the passphrase in memory, which can have
|
||||
security consequences if not done properly. Depending on the keystore's configured
|
||||
strength, constantly decrypting keys can result in non-negligible resource
|
||||
requirements.
|
||||
|
||||
*Note, creating transactions is out of scope here, so the remainder of this section will
|
||||
assume we already have a transaction hash to sign, and will focus only on creating a
|
||||
cryptographic signature authorizing it. Creating an actual transaction and injecting the
|
||||
authorization signature into it will be covered later.*
|
||||
* **Multiple authorizations**: A more complex way of signing transactions via the account
|
||||
manager is to unlock the account via its passphrase once, and allow the account manager
|
||||
to cache the decrypted private key, enabling all subsequent signing requests to
|
||||
complete without the passphrase. The lifetime of the cached private key may be managed
|
||||
manually (by explicitly locking the account back up) or automatically (by providing a
|
||||
timeout during unlock). This mechanism is useful for scenarios where the user may need
|
||||
to sign many transactions or the application would need to do so without requiring user
|
||||
input. The crucial aspect to remember is that **anyone with access to the account
|
||||
manager can sign transactions while a particular account is unlocked** (e.g.
|
||||
application running untrusted code).
|
||||
|
||||
### Signing from Go
|
||||
|
||||
Assuming we already have an instance of an
|
||||
[`accounts.Manager`](https://godoc.org/github.com/ethereum/go-ethereum/accounts#Manager)
|
||||
called `am` from the previous sections, we can create a new account to sign transactions
|
||||
with via it's already demonstrated
|
||||
[`NewAccount`](https://godoc.org/github.com/ethereum/go-ethereum/accounts#Manager.NewAccount)
|
||||
method; and to avoid going into transaction creation for now, we can hard-code a random
|
||||
[`common.Hash`](https://godoc.org/github.com/ethereum/go-ethereum/common#Hash) to sign
|
||||
instead.
|
||||
Assuming an instance of an [`accounts.Manager`][accounts-manager] called `am` exists, a new
|
||||
account can be created to sign transactions using [`NewAccount`][new-account]. Creating transactions
|
||||
is out of scope for this page so instead a random [`common.Hash`][common-hash] will be signed instead.
|
||||
For information on creating transactions in Go native applications see the [Go API page](/docs/dapp/native).
|
||||
|
||||
```go
|
||||
// Create a new account to sign transactions with
|
||||
|
@ -217,7 +175,7 @@ signer, _ := ks.NewAccount("Signer password")
|
|||
txHash := common.HexToHash("0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
|
||||
```
|
||||
|
||||
With the boilerplate out of the way, we can now sign transaction using the authorization
|
||||
With the boilerplate out of the way, the transaction can be signed using the authorization
|
||||
mechanisms described above:
|
||||
|
||||
```go
|
||||
|
@ -234,19 +192,32 @@ _ = ks.TimedUnlock(signer, "Signer password", time.Second)
|
|||
signature, _ = ks.SignHash(signer, txHash.Bytes())
|
||||
```
|
||||
|
||||
You may wonder why
|
||||
[`SignWithPassphrase`](https://godoc.org/github.com/ethereum/go-ethereum/accounts#Manager.SignWithPassphrase)
|
||||
takes an
|
||||
[`accounts.Account`](https://godoc.org/github.com/ethereum/go-ethereum/accounts#Account)
|
||||
as the signer, whereas
|
||||
[`Sign`](https://godoc.org/github.com/ethereum/go-ethereum/accounts#Manager.Sign) takes
|
||||
only a
|
||||
[`common.Address`](https://godoc.org/github.com/ethereum/go-ethereum/common#Address). The
|
||||
reason is that an
|
||||
[`accounts.Account`](https://godoc.org/github.com/ethereum/go-ethereum/accounts#Account)
|
||||
object may also contain a custom key-path, allowing
|
||||
[`SignWithPassphrase`](https://godoc.org/github.com/ethereum/go-ethereum/accounts#Manager.SignWithPassphrase)
|
||||
to sign using accounts outside of the keystore; however
|
||||
[`Sign`](https://godoc.org/github.com/ethereum/go-ethereum/accounts#Manager.Sign) relies
|
||||
on accounts already unlocked within the keystore, so it cannot specify custom paths.
|
||||
Note that [`SignWithPassphrase`][sign-w-phrase] takes an [`accounts.Account`][accounts-account] as the
|
||||
signer, whereas [`Sign`][accounts-sign] takes only a [`common.Address`][common-address]. The reason
|
||||
for this is that an [`accounts.Account`][accounts-account] object may also contain a custom key-path, allowing
|
||||
[`SignWithPassphrase`][sign-w-phrase] to sign using accounts outside of the keystore; however
|
||||
[`Sign`][accounts-sign] relies on accounts already unlocked within the keystore, so it cannot specify custom paths.
|
||||
|
||||
|
||||
## Summary
|
||||
|
||||
Account management is a fundamental pillar of Ethereum development. Geth's Go API provides the tools required
|
||||
to integrate best-practise account security into Go native applications using a simple set of Go functions.
|
||||
|
||||
|
||||
[accounts-sign]: (https://godoc.org/github.com/ethereum/go-ethereum/accounts#Manager.Sign)
|
||||
[common-address]: https://godoc.org/github.com/ethereum/go-ethereum/common#Address
|
||||
[accounts-sign]: https://godoc.org/github.com/ethereum/go-ethereum/accounts#Manager.Sign
|
||||
[sign-w-phrase]: https://godoc.org/github.com/ethereum/go-ethereum/accounts#Manager.SignWithPassphrase
|
||||
[secp256k1]: https://www.secg.org/sec2-v2.pdf
|
||||
[libsecp256k1]: https://github.com/bitcoin-core/secp256k1
|
||||
[wss]:https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition
|
||||
[go-accounts]:https://godoc.org/github.com/ethereum/go-ethereum/accounts
|
||||
[accounts-manager]: https://godoc.org/github.com/ethereum/go-ethereum/accounts#Manager
|
||||
[accounts-pkg]: https://godoc.org/github.com/ethereum/go-ethereum/accounts
|
||||
[keystore]: https://godoc.org/github.com/ethereum/go-ethereum/accounts/keystore#NewKeyStore
|
||||
[pkg-constants]: https://godoc.org/github.com/ethereum/go-ethereum/accounts#pkg-constants
|
||||
[accounts-account]:https://godoc.org/github.com/ethereum/go-ethereum/accounts#Account
|
||||
[new-account]: https://godoc.org/github.com/ethereum/go-ethereum/accounts#Manager.NewAccount
|
||||
[common-hash]: https://godoc.org/github.com/ethereum/go-ethereum/common#Hash
|
||||
[scrypt-docs]: https://pkg.go.dev/golang.org/x/crypto/scrypt
|
||||
|
|
Loading…
Reference in New Issue