Безопасное хранение приватных ключей в backend-сервисах

Организуешь хранение ключей бэкенда: env/HSM, минимальные права, ротация — без ключей в git и логах.

Ключи на бэкенде

Сервис, который подписывает транзакции (деплой, релей, выплата), держит секрет с полной властью над кошельком. Утечка .env или лога = потеря средств. Цель — минимизировать поверхность: где лежит ключ, кто читает, что он может подписать.


Уязвимо: ключ в репозитории и в логах

typescript
// УЯЗВИМО — не копируй в прод
// @ton/ton ^15.1.0 · @ton/crypto ^3.3.0

import { mnemonicToPrivateKey } from "@ton/crypto";
import { WalletContractV5R1 } from "@ton/ton";

// ключ в исходнике / закоммиченном .env.example с реальными словами
const MNEMONIC = "word1 word2 ... word24";

export async function getWallet() {
  const key = await mnemonicToPrivateKey(MNEMONIC.split(" "));
  console.log("secret", key.secretKey); // утечка в логах/APM
  return WalletContractV5R1.create({ workchain: 0, publicKey: key.publicKey });
}

Исправлено: secret manager + узкий signer

typescript
// @ton/ton ^15.1.0 · @ton/crypto ^3.3.0
// ИСПРАВЛЕНО: секрет из env/Secret Manager, без логирования raw key

import { mnemonicToPrivateKey } from "@ton/crypto";
import {
  TonClient,
  WalletContractV5R1,
  internal,
  toNano,
} from "@ton/ton";

async function loadMnemonic(): Promise<string[]> {
  const raw = process.env.TON_WALLET_MNEMONIC;
  if (!raw) {
    throw new Error("TON_WALLET_MNEMONIC is not set");
  }
  // В проде: AWS Secrets Manager / GCP Secret Manager / Vault —
  // процесс получает секрет при старте, не из git.
  return raw.trim().split(/\s+/);
}

export async function sendPayment(to: string, amountTon: string) {
  const mnemonic = await loadMnemonic();
  const key = await mnemonicToPrivateKey(mnemonic);
  const client = new TonClient({
    endpoint: process.env.TON_RPC_URL!,
  });
  const wallet = client.open(
    WalletContractV5R1.create({ workchain: 0, publicKey: key.publicKey }),
  );

  const seqno = await wallet.getSeqno();
  await wallet.sendTransfer({
    seqno,
    secretKey: key.secretKey,
    messages: [
      internal({
        to,
        value: toNano(amountTon),
        bounce: false,
      }),
    ],
  });
  // логируй только адрес кошелька, seqno, hash — не secretKey и не mnemonic
}

Как это работает

  • Хранение: Secret Manager / HSM / KMS; для горячего кошелька — отдельный ключ с лимитом баланса, не treasury.
  • Доступ: один сервис-роль, без шаринга мнемоники в Slack/CI logs; CI для деплоя — OIDC к secret store, не plaintext в variables UI.
  • Права on-chain: лучше мультисиг или контракт с лимитами, чем один hot key на всё.
  • Ротация: процедура смены ключа и инвалидации старого; мониторинг исходящих с адреса.
  • Hot wallet ≠ cold storage: операционные выплаты с малого баланса, резерв — оффлайн/мультисиг.

Частые ошибки

Коммитишь .env «на минуту» → история git хранит секрет навсегда; нужен rotate + purge осторожно.

Печатаешь mnemonic в exception tracker → Sentry/APM забирает Error с секретом в message.

Один ключ на dev/stage/prod → компрометация песочницы бьёт по mainnet.


Что дальше

Материалы gramdocs.tech носят образовательный характер и не являются финансовой, юридической или инвестиционной рекомендацией. Работа с блокчейном TON и токеном Gram связана с рисками потери средств. Правовая информация