---
url: https://aelmanaa.github.io/hardhat-kms/user/explanation/kms-or-env-key.md
description: >-
  Should a Hardhat deployer key be a private key in .env or a cloud KMS key?
  What each protects against, what neither does, and what a KMS signature costs.
---

# A KMS key or a private key in .env

Audience: users who sign with a private key in `.env`, an environment variable or the Hardhat keystore, and want to know what a KMS key changes. Assumes you know what a private key and an RPC URL are; no cloud experience.

A private key in `.env` is a secret your process reads and signs with. A KMS key is a key your process never sees: the cloud signs on request, for whoever its access control allows, and the plugin checks each signature.

In Hardhat 3, a key reaches `accounts` through `configVariable("…")`, which reads an environment variable by default, or the Hardhat keystore when the config loads `@nomicfoundation/hardhat-keystore`. The keystore stores the value "encrypted in a file in your home directory" ([Configuration variables](https://hardhat.org/docs/learn-more/configuration-variables), checked 2026-10-07). Hardhat itself does not load a `.env` file; a project that uses one loads it with a package such as dotenv. The rows below on a `.env` key hold for the keystore once Hardhat has decrypted the value into the process.

## What each protects against

| Risk                                                                   | Private key in `.env` or the keystore                                                                                          | KMS key with hardhat-kms                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| A dependency or script copies the key and sends it away (exfiltration) | The copy signs from anywhere, for as long as the key holds anything. The only fix is to move every fund and role to a new key. | There is no key to copy. Code in the process can still sign while it runs, and can read the cloud credentials in its environment ([What the plugin does not protect against](https://aelmanaa.github.io/hardhat-kms/user/explanation/security-model.md#what-the-plugin-does-not-protect-against)). Revoking those credentials stops it, and the key and its address stay.                                                                                                                                                                                                                |
| The key or a credential ends up in a CI log                            | A printed private key is the key. Anyone who reads the log can sign.                                                           | A key id signs nothing on its own. A key id read from a configuration variable prints as the variable's name in the plugin's errors, debug output and task output ([No secrets in errors, logs or task output](https://aelmanaa.github.io/hardhat-kms/user/explanation/security-model.md#no-secrets-in-errors-logs-or-task-output)). A leaked cloud credential signs until it expires or is revoked. A CI job that signs in with OpenID Connect (OIDC) gets short-lived credentials and stores no long-lived secret ([Cloud credentials for KMS signing](https://aelmanaa.github.io/hardhat-kms/user/explanation/cloud-access.md#ci-with-oidc)). |
| A laptop is lost or stolen                                             | A `.env` file is plain text on the disk. A keystore file needs its password.                                                   | The laptop holds cloud credentials, not the key. Revoke them, or the user's access to the key, in the cloud console.                                                                                                                                                                                                                                                                                                                                                                                                             |
| You need to know what was signed, and when                             | Nothing records a signature unless your own code does.                                                                         | The cloud's audit log records each sign call; `kms history` lists them from CloudTrail, Cloud Audit Logs or the Key Vault audit log ([`kms history`](https://aelmanaa.github.io/hardhat-kms/user/reference/tasks.md#kms-history)).                                                                                                                                                                                                                                                                                                                                        |
| Who may sign                                                           | Whoever holds the file, the variable or the keystore password.                                                                 | The cloud's IAM or RBAC, which you can grant per identity and take back without changing the key ([AWS](https://aelmanaa.github.io/hardhat-kms/user/guides/aws-kms-setup.md#2-allow-signing-and-nothing-else), [Google Cloud](https://aelmanaa.github.io/hardhat-kms/user/guides/gcp-kms-setup.md#2-allow-signing-and-nothing-else), [Azure](https://aelmanaa.github.io/hardhat-kms/user/guides/azure-key-vault-setup.md#2-allow-get-and-sign-and-nothing-else)).                                                                                                                                                                                           |

## What neither protects against

A compromised build that signs a bad transaction. Any code in your Hardhat process, such as a script, a test, a plugin or a dependency, can send a request to the connection, and the account signs it, whether its key sits in `.env` or in a KMS. hardhat-kms refuses some requests for every caller, such as a bare 32-byte digest over RPC or typed data for another chain ([Guarantees](https://aelmanaa.github.io/hardhat-kms/user/explanation/security-model.md#guarantees)), but it cannot tell your transaction from one an attacker built. Run only code you trust in a process that can reach a key that holds funds.

Also out of reach of both: losing the key. A deleted KMS key cannot be recovered after its provider's waiting period ([Prevent and recover from losing a key](https://aelmanaa.github.io/hardhat-kms/user/guides/key-loss.md)), and a `.env` key with no backup is gone with its file.

## Cost and latency per signature

A `.env` key signs inside the Node.js process, at no charge. A KMS key costs a fee per call, and on AWS and Google Cloud a monthly fee per key. Each signature is a network request to the cloud.

| Cloud                                                     | Per key                                                                                          | Per sign call                                       | Source, checked 2026-10-07                                                                                                                  |
| --------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| AWS KMS, `ECC_SECG_P256K1`                                | $1 a month, prorated hourly                                                                      | $0.15 per 10,000 asymmetric requests in us-east-1   | [AWS KMS pricing](https://aws.amazon.com/kms/pricing/) and the AWS Price List API for `AWSKMS` in us-east-1                                 |
| Google Cloud KMS, `EC_SIGN_SECP256K1_SHA256` at level HSM | $0.003424658 an hour per key version, about $2.50 a month, for an account's first 2,000 versions | At most $0.15 per 10,000 operations                 | [Cloud KMS pricing](https://cloud.google.com/kms/pricing), prices effective 2025-03-17                                                      |
| Azure Key Vault, `EC` key on `P-256K` in a Standard vault | No monthly fee                                                                                   | $0.15 per 10,000 advanced key operations in East US | [Azure Key Vault pricing](https://azure.microsoft.com/pricing/details/key-vault/) and the Azure Retail Prices API for Key Vault in `eastus` |

The Azure row is for a Standard vault. An `EC-HSM` key in a Premium vault, or a key in a Managed HSM, carries its own fees on the same pricing page.

At $0.15 per 10,000, one signature costs $0.000015. The plugin also reads the key's public key when it first uses the key in a run, and again after it has closed the key's signer, 5 s after the last connection closes ([How hardhat-kms works](https://aelmanaa.github.io/hardhat-kms/user/explanation/how-it-works.md#keys-connections-and-the-kms-client)); that call is billed at the same rate. Prices vary by region and change over time. The first-deploy tutorials give each cloud's cost for a test key: [AWS](https://aelmanaa.github.io/hardhat-kms/user/tutorials/first-deploy-aws.md), [Google Cloud](https://aelmanaa.github.io/hardhat-kms/user/tutorials/first-deploy-gcp.md), [Azure](https://aelmanaa.github.io/hardhat-kms/user/tutorials/first-deploy-azure.md).

This project has not measured signing latency. It depends on the cloud, the region and your network path to it. To see it for your key, run with `DEBUG=hardhat:kms:signer`, which prints the duration of each call to the KMS ([Debug output](https://aelmanaa.github.io/hardhat-kms/user/guides/debug-output.md)). Each KMS call may take up to `timeoutMs`, 30 s by default ([When a KMS call times out](https://aelmanaa.github.io/hardhat-kms/user/explanation/security-model.md#when-a-kms-call-times-out)).

## When to use which

* A `.env` key, or the keystore, is fine for local networks and for throwaway testnet keys that hold nothing you would miss: Hardhat's simulated network, a local node, a Sepolia key funded from a faucet.
* A KMS key fits a key that holds funds or contract roles, a key that CI or a server signs with, or a key that several people's runs share. Access is granted and revoked per identity, and every sign call lands in the cloud's audit log.
* Both in one project: a network can list `accounts` and `kmsAccounts` together, for example a local test key next to a KMS deployer ([Add local or Ledger accounts](https://aelmanaa.github.io/hardhat-kms/user/guides/multiple-keys.md#add-local-or-ledger-accounts)).

A key that a person approves on a device is a third option: [A KMS key or a Ledger](https://aelmanaa.github.io/hardhat-kms/user/explanation/kms-or-ledger.md). For the plugin's guarantees in full, read the [security model](https://aelmanaa.github.io/hardhat-kms/user/explanation/security-model.md).
