Я создаю небольшой клиент Python для службы FastAPI, которая отправляет одноразовые зашифрованные сообщения. Ключи должны оставаться на клиенте (никогда не отправляться на сервер и не храниться им).
Env/Libraries
Python 3.11
[PyNaCl] для X25519/box (nacl.public)
Необязательно криптография для KDF + AEAD
Цель
Сохранять долгоживущую пару ключей PyNaCl между запусками. Я хочу, чтобы закрытый ключ хранился на диске и расшифровывался только после того, как пользователь введет парольную фразу (или, альтернативно, используйте связку ключей ОС, если это считается лучшей практикой).
Что я пробовал
Запись необработанного 32-байтового закрытого ключа в файл → легко, но явно небезопасно в состоянии покоя.
Использование криптографии для получения ключа из парольной фразы (Scrypt) и шифрования закрытый ключ с помощью AES-GCM перед записью на диск. Это работает, но я не уверен насчет рекомендуемых параметров/шаблонов.
Использование пакета ключей для хранения закрытого ключа в хранилище учетных данных ОС (Windows DPAPI/macOS Keychain/Secret Service). Это позволяет избежать использования парольных фраз, но я не уверен в переносимости и резервном копировании/восстановлении.
Минимальное воспроизведение (эскиз)
# Python 3.11
# pip install pynacl cryptography
from nacl.public import PrivateKey
from cryptography.hazmat.primitives.kdf.scrypt import Scrypt
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
import os, json, base64
# 1) Generate
sk = PrivateKey.generate()
pk = sk.public_key
sk_bytes = bytes(sk) # 32 bytes
pk_bytes = bytes(pk) # 32 bytes (saved in clear is fine)
# 2) Derive wrap key from passphrase
def derive_key(passphrase: str, salt: bytes) -> bytes:
kdf = Scrypt(salt=salt, length=32, n=2 ** 15, r=8, p=1,
backend=default_backend())
return kdf.derive(passphrase.encode("utf-8"))
# 3) Encrypt secret key with AES-GCM and store (salt, nonce, ct)
def store_key(sk_bytes: bytes, passphrase: str, path="keystore.json"):
salt = os.urandom(16)
nonce = os.urandom(12)
aead_key = derive_key(passphrase, salt)
ct = AESGCM(aead_key).encrypt(nonce, sk_bytes, associated_data=None)
data = {
"salt": base64.b64encode(salt).decode(),
"nonce": base64.b64encode(nonce).decode(),
"ct": base64.b64encode(ct).decode(),
"pub": base64.b64encode(pk_bytes).decode(),
}
with open(path, "w") as f:
json.dump(data, f)
# 4) Load + decrypt
def load_key(passphrase: str, path="keystore.json") -> bytes:
data = json.load(open(path))
salt = base64.b64decode(data["salt"])
nonce = base64.b64decode(data["nonce"])
ct = base64.b64decode(data["ct"])
aead_key = derive_key(passphrase, salt)
sk_bytes = AESGCM(aead_key).decrypt(nonce, ct, associated_data=None)
return sk_bytes
# Usage:
# store_key(sk_bytes, "my passphrase")
# recovered = load_key("my passphrase")
# assert recovered == sk_bytes
Вопросы
Является ли «ключ, полученный из Scrypt + упаковка AES-GCM, а затем сохранение JSON в файле» разумным шаблоном для сохранения ключа на стороне клиента с помощью PyNaCl? Какие-либо рекомендуемые параметры Scrypt для настольных компьютеров в 2025 году?
Рекомендуете ли вы вместо этого использовать связку ключей ОС через связку ключей (и если да, то как безопасно и портативно хранить 32 необработанных байта)?
Есть ли подводные камни с ключами X25519 в PyNaCl при такой сериализации/десериализации?
Ограничения
Я не могу хранить секреты на сервер. Можно запросить у пользователя парольную фразу при запуске; никаких инфраструктур пользовательского интерфейса не требуется.
Ожидается:
руководство или пример кода, демонстрирующий рекомендуемый подход (файл с парольной фразой или связка ключей ОС) и выбор параметров (стоимость шифрования, обработка nonce AES-GCM и т. д.).
Я создаю небольшой клиент Python для службы FastAPI, которая отправляет одноразовые зашифрованные сообщения. Ключи должны оставаться на клиенте (никогда не отправляться на сервер и не храниться им). Env/Libraries [list] [*]Python 3.11 [*][PyNaCl] для X25519/box (nacl.public) [*]Необязательно криптография для KDF + AEAD Цель [/list] Сохранять долгоживущую пару ключей PyNaCl между запусками. Я хочу, чтобы закрытый ключ хранился на диске и расшифровывался только после того, как пользователь введет парольную фразу (или, альтернативно, используйте связку ключей ОС, если это считается лучшей практикой). Что я пробовал [list] [*]Запись необработанного 32-байтового закрытого ключа в файл → легко, но явно небезопасно в состоянии покоя. [*]Использование криптографии для получения ключа из парольной фразы (Scrypt) и шифрования закрытый ключ с помощью AES-GCM перед записью на диск. Это работает, но я не уверен насчет рекомендуемых параметров/шаблонов. [*]Использование пакета ключей для хранения закрытого ключа в хранилище учетных данных ОС (Windows DPAPI/macOS Keychain/Secret Service). Это позволяет избежать использования парольных фраз, но я не уверен в переносимости и резервном копировании/восстановлении. Минимальное воспроизведение (эскиз) [/list] [code]# Python 3.11 # pip install pynacl cryptography
from nacl.public import PrivateKey from cryptography.hazmat.primitives.kdf.scrypt import Scrypt from cryptography.hazmat.primitives.ciphers.aead import AESGCM from cryptography.hazmat.primitives import serialization from cryptography.hazmat.backends import default_backend import os, json, base64
# 1) Generate sk = PrivateKey.generate() pk = sk.public_key sk_bytes = bytes(sk) # 32 bytes pk_bytes = bytes(pk) # 32 bytes (saved in clear is fine)
# Usage: # store_key(sk_bytes, "my passphrase") # recovered = load_key("my passphrase") # assert recovered == sk_bytes [/code] Вопросы [list] [*]Является ли «ключ, полученный из Scrypt + упаковка AES-GCM, а затем сохранение JSON в файле» разумным шаблоном для сохранения ключа на стороне клиента с помощью PyNaCl? Какие-либо рекомендуемые параметры Scrypt для настольных компьютеров в 2025 году? [*]Рекомендуете ли вы вместо этого использовать связку ключей ОС через связку ключей (и если да, то как безопасно и портативно хранить 32 необработанных байта)? [*]Есть ли подводные камни с ключами X25519 в PyNaCl при такой сериализации/десериализации? Ограничения Я не могу хранить секреты на сервер. Можно запросить у пользователя парольную фразу при запуске; никаких инфраструктур пользовательского интерфейса не требуется. Ожидается: руководство или пример кода, демонстрирующий рекомендуемый подход (файл с парольной фразой или связка ключей ОС) и выбор параметров (стоимость шифрования, обработка nonce AES-GCM и т. д.). [/list]