Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

kek — KMS Encryption Kit

kek is a single-binary CLI for encrypting and decrypting Solana keypairs and arbitrary messages with AWS KMS. It supports two payload schemes so downstream consumers can decrypt straight into the keypair format they expect (raw bytes or a base58 string).

Install

One-liner

curl -fsSL https://raw.githubusercontent.com/mirrorworld-universe/solana-kms-keypair-encryption-kit/main/install.sh | bash
# or pin a release:
curl -fsSL https://raw.githubusercontent.com/mirrorworld-universe/solana-kms-keypair-encryption-kit/main/install.sh | KEK_REF=v0.2.0 bash

The script delegates to cargo install --git, which lands the binary at ~/.cargo/bin/kek. If the repository is private, configure your git credentials (SSH key, gh, or a credential helper) as a mirrorworld-universe org member before running — GitHub enforces access at the repo level.

Prerequisites: rustup (for cargo).

Build from source

git clone git@github.com:mirrorworld-universe/solana-kms-keypair-encryption-kit.git
cd solana-kms-keypair-encryption-kit
cargo install --path .       # installs `kek` into ~/.cargo/bin
# or, without installing:
cargo build --release        # binary at target/release/kek

Make sure ~/.cargo/bin is on your PATH. Verify with:

kek --version

AWS credentials

kek uses the standard AWS SDK credential chain. Set whichever is most convenient:

export AWS_ACCESS_KEY_ID=…
export AWS_SECRET_ACCESS_KEY=…
export AWS_REGION=us-west-2   # optional; defaults to us-west-2
# or use ~/.aws/credentials, SSO, an EC2/ECS role, etc.

Required IAM permissions: kms:Encrypt, kms:Decrypt, and kms:CreateKey if you ever omit --kms-key-id.

Why two schemes?

Both schemes wrap the payload in base64 before handing it to KMS. The difference is what that base64 layer wraps — pick the one whose decoded plaintext matches what your downstream consumer expects:

Scheme Encrypt pipeline Decrypted plaintext (after base64.decode)
bytes keypair_bytes → base64 → KMS encrypt Raw 64-byte Uint8Array (Solana CLI form)
base58 base58_string → base64 → KMS encrypt Base58 private key string (Phantom form)

Consumers that decode the KMS plaintext as Uint8Array.from(...) need the bytes scheme. Consumers that decode it into a string and pass it to Keypair.fromBase58String() need the base58 scheme.

Flow

flowchart LR
    K["id.json<br/>(64-byte array)"]

    subgraph bytes_scheme["bytes scheme"]
        direction LR
        Bb["UInt8Array<br/>(64 bytes)"] -->|"STANDARD.encode"| Bc["base64 string"]
    end

    subgraph base58_scheme["base58 scheme"]
        direction LR
        Sa["UInt8Array<br/>(64 bytes)"] -->|"bs58.encode"| Sb["base58 string"]
        Sb -->|"STANDARD.encode"| Sc["base64 string"]
    end

    K --> Bb
    K --> Sa

    Bc --> KMS[("AWS KMS<br/>Encrypt")]
    Sc --> KMS

    KMS --> CT["ciphertext blob<br/>(base64-encoded for transport)"]

    CT -.->|"decrypt: KMS → base64 → bytes"| Bb
    CT -.->|"decrypt: KMS → base64 → base58 → bytes"| Sa
Loading

Solid arrows = encryption. Dashed arrows = the matching decryption path consumers must run.

Commands

Run kek --help for the full reference. Summary:

Command Purpose
encrypt-keypair Encrypt a Solana id.json as raw bytes (UInt8Array)
encrypt-keypair-base58 Encrypt a Solana id.json as a base58 private key
decrypt-keypair Decrypt a bytes-scheme ciphertext (alias: decrypt)
decrypt-keypair-base58 Decrypt a base58-scheme ciphertext
encrypt-message Encrypt an arbitrary UTF-8 string
decrypt-message Decrypt an encrypt-message ciphertext
serialize-base58 Print an id.json as a base58 private key (no KMS)

Every encrypt subcommand accepts --kms-key-id <ID>. If omitted, a fresh symmetric KMS key is created and its ID is printed to stderr.

Examples

bytes scheme (Solana CLI / Keypair.fromSecretKey(Uint8Array) consumers)

kek encrypt-keypair ./id.json <PUBKEY> --kms-key-id <KMS_KEY_ID>
kek decrypt-keypair <CIPHERTEXT> <KMS_KEY_ID>

Downstream decryption in TypeScript:

import { KMSClient, DecryptCommand } from "@aws-sdk/client-kms";
import { Keypair } from "@solana/web3.js";

const { Plaintext } = await new KMSClient({}).send(
  new DecryptCommand({
    CiphertextBlob: Buffer.from(ciphertext, "base64"),
    KeyId: kmsKeyId,
  }),
);
const base64 = Buffer.from(Plaintext!).toString("utf8");
const bytes = Buffer.from(base64, "base64");
const keypair = Keypair.fromSecretKey(new Uint8Array(bytes));

base58 scheme (Phantom / Keypair.fromBase58String consumers)

kek encrypt-keypair-base58 ./id.json <PUBKEY> --kms-key-id <KMS_KEY_ID>
kek decrypt-keypair-base58 <CIPHERTEXT> <KMS_KEY_ID>

Downstream decryption in TypeScript:

import { KMSClient, DecryptCommand } from "@aws-sdk/client-kms";
import { Keypair } from "@solana/web3.js";
import bs58 from "bs58";

const { Plaintext } = await new KMSClient({}).send(
  new DecryptCommand({
    CiphertextBlob: Buffer.from(ciphertext, "base64"),
    KeyId: kmsKeyId,
  }),
);
const base64 = Buffer.from(Plaintext!).toString("utf8");
const base58 = Buffer.from(base64, "base64").toString("utf8");
const keypair = Keypair.fromSecretKey(bs58.decode(base58));

Arbitrary message

kek encrypt-message "hello" --kms-key-id <KMS_KEY_ID>
kek decrypt-message <CIPHERTEXT> <KMS_KEY_ID>

Convert id.json to base58 (no KMS)

kek serialize-base58 ./id.json

Testing

cargo test                                              # offline tests, no AWS calls
KMS_TEST_KEY_ID=<your-key-id> cargo test -- --ignored   # live KMS round-trip

The --ignored test exercises the full encrypt + decrypt path against a real KMS key. The key ID must come from KMS_TEST_KEY_ID — never hard-code it.

Releases

Releases follow Semantic Versioning. See the Releases page for changelogs. Pin a specific release in install.sh with KEK_REF=v0.2.0.

Security notes

  • Decrypt output goes to stdout. The decrypted base58 secret is printed plainly so you can verify the result. Redirect to a file or pipe into another process when handling production keys; mind shell history and terminal scrollback.
  • Never commit keypair material. The bundled .gitignore blocks *.keypair.json, id.json, keypair*.json, *.secret, *.log, and .env*. Add patterns if your repo introduces new filenames.
  • Use a least-privilege IAM role. The binary only needs kms:Encrypt, kms:Decrypt, and (optionally, for the auto-create path) kms:CreateKey. Scope to specific key ARNs in production.
  • Prefer existing KMS keys. Running an encrypt command without --kms-key-id creates a new symmetric KMS key with no rotation policy or alias attached. Use it for experimentation only; supply --kms-key-id in production.
  • Memory is not zeroized. Secret bytes live in process memory until the CLI exits. The tool is single-shot, so the window is short — but do not embed this code in a long-lived process without adding a zeroize layer.

About

CLI for encrypting and decrypting Solana keypairs and arbitrary messages with AWS KMS

Resources

Stars

4 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages