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:
Joseph Cook 2022-07-04 13:50:57 +01:00 committed by GitHub
parent b222b2e507
commit 8d276f51bf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -3,70 +3,48 @@ title: Go Account Management
sort_key: D sort_key: D
--- ---
To provide Ethereum integration for your native applications, the very first thing you Geth provides a simple, yet thorough accounts package that includes all the tools developers
should be interested in doing is account management. 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, **Note Geth's built-in account management is convenient and straightforward to use, but
it is ill advised to keep accounts in any location that is shared between multiple best practise is to use the external tool *Clef* for key management.**
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.
The proper way to handle user accounts in your native applications is to do client side {:toc}
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.
To support this, `go-ethereum` provides a simple, yet thorough accounts package that gives - this will be removed by the toc
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.
## Encrypted keystores ## Encrypted keystores
Although handling accounts locally to an application does provide certain security Access keys to Ethereum accounts should never be stored in plain-text. Instead, they should be
guarantees, access keys to Ethereum accounts should never lay around in clear-text form. stored encrypted so that even if the mobile device is accessed by a malicious third party the
As such, we provide an encrypted keystore that provides the proper security guarantees for keys are still hidden under an additional layer of security. Geth provides a keystore that enables
you without requiring a thorough understanding from your part of the associated developers to store keys securely. The Geth keystore uses [Scrypt][scrypt-docs] to store keys that are encoded
cryptographic primitives. 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 One thing that should be understood, though, is that the cryptographic primitives underpinning the
primitives used within can operate either in *standard* or *light* mode. The former keystore can operate in light or standard mode. Light mode is computationally cheaper, while standard
provides a higher level of security at the cost of increased computational burden and mode has extra security. Light mode is appropriate for mobile devices, but developers should be
resource consumption: aware that there is a security trade-off.
* *standard* needs 256MB memory and 1 second 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 * 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 The encrypted keystore is implemented by the [`accounts.Manager`][accounts-manager] struct
uses the `secp256k1` elliptic curve as defined in the [Standards for Efficient from the [`accounts`][accounts-pkg] package, which also contains the configuration constants for the
Cryptography](https://www.secg.org/sec2-v2.pdf), implemented by the [`libsecp256k`](https://github.com/bitcoin-core/secp256k1) library and wrapped by *standard* or *light* security modes described above. Hence client side account management
[`github.com/ethereum/go-ethereum/accounts`](https://godoc.org/github.com/ethereum/go-ethereum/accounts). Accounts are stored on disk in simply requires importing the `accounts` package into the application code.
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:
```go ```go
import "github.com/ethereum/go-ethereum/accounts" import "github.com/ethereum/go-ethereum/accounts"
import "github.com/ethereum/go-ethereum/accounts/keystore" import "github.com/ethereum/go-ethereum/accounts/keystore"
import "github.com/ethereum/go-ethereum/common" import "github.com/ethereum/go-ethereum/common"
``` ```
Afterwards a new encrypted account manager can be created via:
Afterwards you can create a new encrypted account manager via:
```go ```go
ks := keystore.NewKeyStore("/path/to/keystore", keystore.StandardScryptN, keystore.StandardScryptP) 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 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 but non-readable for other system users, such as inside the user's home directory.
recommend placing it either inside your user's home directory or even more locked down for
backend applications. 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 ## Account lifecycle
Having created an encrypted keystore for your Ethereum accounts, you can use this account Once an encrypted keystore for Ethereum accounts exists it, it can be used to manage accounts for the
manager for the entire account lifecycle requirements of your native application. This entire account lifecycle requirements of a Go native application. This includes the basic functionality
includes the basic functionality of creating new accounts and deleting existing ones; as of creating new accounts and deleting existing ones as well as updating access credentials,
well as the more advanced functionality of updating access credentials, exporting existing exporting existing accounts, and importing them on other devices.
accounts, and importing them on another device.
Although the keystore defines the encryption strength it uses to store your accounts, Although the keystore defines the encryption strength it uses to store accounts, there is no global master
there is no global master password that can grant access to all of them. Rather each password that can grant access to all of them. Rather each account is maintained individually, and stored on
account is maintained individually, and stored on disk in its [encrypted disk in its [encrypted format][wss] individually, ensuring a much cleaner and stricter separation of
format](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) credentials.
individually, ensuring a much cleaner and stricter separation of credentials.
This individuality however means that any operation requiring access to an account will This individuality means that any operation requiring access to an account will need to provide the
need to provide the necessary authentication credentials for that particular account in necessary authentication credentials for that particular account in the form of a passphrase:
the form of a passphrase:
* When creating a new account, the caller must supply a passphrase to encrypt the account * 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 with. This passphrase will be required for any subsequent access, the lack of which
will forever forfeit using the newly created account. will forever forfeit using the newly created account.
* When deleting an existing account, the caller must supply a passphrase to verify * 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 ownership of the account. This isn't cryptographically necessary, rather a protective
measure against accidental loss of accounts. measure against accidental loss of accounts.
* When updating an existing account, the caller must supply both current and new * 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 passphrases. After completing the operation, the account will not be accessible via the
old passphrase any more. old passphrase any more.
* When exporting an existing account, the caller must supply both the current passphrase * 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 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 returning the key-file to the user. This is required to allow moving accounts between
machines and applications without sharing original credentials. machines and applications without sharing original credentials.
* When importing a new account, the caller must supply both the encryption passphrase of * 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 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 account. This is required to allow storing account with different credentials than used
for moving them around. for moving them around.
*Please note, there is no recovery mechanisms for losing the passphrases. The ***Please note, there are no recovery mechanisms for lost passphrases. The
cryptographic properties of the encrypted keystore (if using the provided parameters) cryptographic properties of the encrypted keystore (using the provided parameters)
guarantee that account credentials cannot be brute forced in any meaningful time.* guarantee that account credentials cannot be brute forced in any meaningful time.***
### Accounts from Go An Ethereum account is implemented by the [`accounts.Account`][accounts-account] struct from
the Geth [accounts][accounts-pkg] package. Assuming an instance of an
An Ethereum account is implemented by the [`accounts.Manager`][accounts-manager] called `am` exists, all of the described lifecycle
[`accounts.Account`](https://godoc.org/github.com/ethereum/go-ethereum/accounts#Account) operations can be executed with a handful of function calls (error handling omitted).
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).
```go ```go
// Create a new account with the specified encryption passphrase. // 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") impAcc, _ := ks.Import(jsonAcc, "Export password", "Import password")
``` ```
*Although instances of *Although instances of [`accounts.Account`][accounts-account] can be used to access various information about
[`accounts.Account`](https://godoc.org/github.com/ethereum/go-ethereum/accounts#Account) specific Ethereum accounts, they do not contain any sensitive data (such as passphrases or private keys),
can be used to access various information about specific Ethereum accounts, they do not rather they act solely as identifiers for client code and the keystore.*
contain any sensitive data (such as passphrases or private keys), rather act solely as
identifiers for client code and the keystore.*
## Signing authorization ## Signing authorization
As mentioned above, account objects do not hold the sensitive private keys of the Account objects do not hold the sensitive private keys of the associated Ethereum accounts.
associated Ethereum accounts, but are merely placeholders to identify the cryptographic Account objects are placeholders that identify the cryptographic keys. All operations that
keys with. All operations that require authorization (e.g. transaction signing) are require authorization (e.g. transaction signing) are performed by the account manager after
performed by the account manager after granting it access to the private keys. 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 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: 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 * **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 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 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 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 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 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 security consequences if not done properly. Depending on the keystore's configured
strength, constantly decrypting keys can result in non-negligible resource strength, constantly decrypting keys can result in non-negligible resource
requirements. 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).
*Note, creating transactions is out of scope here, so the remainder of this section will * **Multiple authorizations**: A more complex way of signing transactions via the account
assume we already have a transaction hash to sign, and will focus only on creating a manager is to unlock the account via its passphrase once, and allow the account manager
cryptographic signature authorizing it. Creating an actual transaction and injecting the to cache the decrypted private key, enabling all subsequent signing requests to
authorization signature into it will be covered later.* 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 Assuming an instance of an [`accounts.Manager`][accounts-manager] called `am` exists, a new
[`accounts.Manager`](https://godoc.org/github.com/ethereum/go-ethereum/accounts#Manager) account can be created to sign transactions using [`NewAccount`][new-account]. Creating transactions
called `am` from the previous sections, we can create a new account to sign transactions is out of scope for this page so instead a random [`common.Hash`][common-hash] will be signed instead.
with via it's already demonstrated For information on creating transactions in Go native applications see the [Go API page](/docs/dapp/native).
[`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.
```go ```go
// Create a new account to sign transactions with // Create a new account to sign transactions with
@ -217,7 +175,7 @@ signer, _ := ks.NewAccount("Signer password")
txHash := common.HexToHash("0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") 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: mechanisms described above:
```go ```go
@ -234,19 +192,32 @@ _ = ks.TimedUnlock(signer, "Signer password", time.Second)
signature, _ = ks.SignHash(signer, txHash.Bytes()) signature, _ = ks.SignHash(signer, txHash.Bytes())
``` ```
You may wonder why Note that [`SignWithPassphrase`][sign-w-phrase] takes an [`accounts.Account`][accounts-account] as the
[`SignWithPassphrase`](https://godoc.org/github.com/ethereum/go-ethereum/accounts#Manager.SignWithPassphrase) signer, whereas [`Sign`][accounts-sign] takes only a [`common.Address`][common-address]. The reason
takes an for this is that an [`accounts.Account`][accounts-account] object may also contain a custom key-path, allowing
[`accounts.Account`](https://godoc.org/github.com/ethereum/go-ethereum/accounts#Account) [`SignWithPassphrase`][sign-w-phrase] to sign using accounts outside of the keystore; however
as the signer, whereas [`Sign`][accounts-sign] relies on accounts already unlocked within the keystore, so it cannot specify custom paths.
[`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.
## 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