Stanford JavaScript Crypto Library (SJCL) использует единый JSON-формат для представления зашифрованных данных. Это ключевая особенность библиотеки: результат шифрования всегда сериализуется в структурированный объект, который легко переносится между JavaScript и другими языками.
Типичный зашифрованный объект SJCL выглядит следующим образом:
{
"iv": "base64...",
"v": 1,
"iter": 10000,
"ks": 128,
"ts": 64,
"mode": "ccm",
"adata": "",
"cipher": "aes",
"salt": "base64...",
"ct": "base64..."
}
iv (Initialization Vector) Инициализационный вектор, используемый в режиме блочного шифрования. В SJCL он всегда случайный и уникальный для каждого шифрования.
salt Соль, используемая при деривации ключа из пароля. Она предотвращает использование радужных таблиц и атак по словарю.
iter Количество итераций PBKDF2. Чем выше значение, тем медленнее перебор пароля.
ks (key size) Размер ключа в битах (например, 128, 192 или 256).
ts (tag size) Размер аутентификационного тега в битах, используемого для проверки целостности.
ct (ciphertext) Собственно зашифрованные данные в Base64.
adata Дополнительные аутентифицированные данные (AAD), которые не шифруются, но участвуют в проверке целостности.
mode Режим шифрования. SJCL чаще всего использует
ccm.
Шифрование в SJCL состоит из двух основных этапов:
Ключ не используется напрямую из пароля. Вместо этого применяется PBKDF2 с HMAC-SHA256.
= (, , , )
Где:
PBKDF2 делает перебор пароля вычислительно дорогим.
SJCL использует AES в режиме CCM (Counter with CBC-MAC). Этот режим объединяет шифрование и аутентификацию.
CCM обеспечивает:
В CCM данные разбиваются на блоки, и для каждого блока вычисляется MAC.
Для работы с SJCL в Python чаще всего используются:
PyCryptodomecryptographyОба варианта позволяют реализовать PBKDF2 и AES-CCM.
Перед расшифровкой необходимо декодировать Base64 поля:
Пример преобразования:
import base64
import json
data = json.loads(sjcl_json)
salt = base64.b64decode(data["salt"])
iv = base64.b64decode(data["iv"])
ciphertext = base64.b64decode(data["ct"])
from Crypto.Protocol.KDF import PBKDF2
from Crypto.Hash import SHA256
key = PBKDF2(
password,
salt,
dkLen=ks // 8,
count=iter,
hmac_hash_module=SHA256
)
Важно:
from Crypto.Cipher import AES
cipher = AES.new(
key,
AES.MODE_CCM,
nonce=iv,
mac_len=ts // 8
)
plaintext = cipher.decrypt_and_verify(ciphertext[:-8], ciphertext[-8:])
Однако в SJCL структура отличается: MAC обычно встроен в конец ciphertext.
from cryptography.hazmat.primitives.ciphers.aead import AESCCM
aesccm = AESCCM(key, tag_length=ts // 8)
plaintext = aesccm.decrypt(iv, ciphertext, adata.encode() if adata else None)
Этот вариант ближе к тому, как работает SJCL, поскольку
cryptography напрямую поддерживает AEAD.
SJCL позволяет добавлять дополнительные данные, которые не шифруются, но участвуют в аутентификации.
Пример:
"adata": "header-information"
В Python это обязательно учитывается:
additional_data = adata.encode() if adata else None
import json
import base64
salt = base64.b64decode(data["salt"])
iv = base64.b64decode(data["iv"])
ct = base64.b64decode(data["ct"])
iter = data["iter"]
ks = data["ks"]
ts = data["ts"]
adata = data.get("adata", "")
key = PBKDF2(password, salt, dkLen=ks // 8, count=iter, hmac_hash_module=SHA256)
aesccm = AESCCM(key, tag_length=ts // 8)
plaintext = aesccm.decrypt(iv, ct, adata.encode() if adata else None)
SJCL строго фиксирует размер ключа. Если использовать неправильный
ks, расшифровка всегда будет проваливаться.
IV должен быть точно таким же, как в JS-версии. Любое изменение приводит к невозможности расшифровки.
SJCL использует CCM. Попытка применить CBC или GCM приведёт к ошибкам MAC.
SJCL использует стандарт Base64, но при переносе данных иногда появляются:
=SJCL проектировалась как браузерная библиотека, но её криптографическая модель строго соответствует стандартным примитивам:
Это делает её полностью переносимой в Python без необходимости эмуляции JavaScript.
| SJCL поле | Python эквивалент |
|---|---|
| salt | PBKDF2 salt |
| iter | PBKDF2 rounds |
| ks | key length |
| iv | nonce |
| ct | ciphertext |
| adata | additional data |
В SJCL ciphertext часто содержит:
В зависимости от режима CCM:
Поэтому иногда требуется разделение:
ciphertext, tag = ct[:-8], ct[-8:]
При высоких значениях iter (например, 100k+):
Решения:
hashlib.pbkdf2_hmac (быстрее
PyCryptodome)import hashlib
key = hashlib.pbkdf2_hmac(
"sha256",
password.encode(),
salt,
iter,
dklen=ks // 8
)
SJCL часто используется в веб-приложениях, где данные экспортируются в JSON и затем расшифровываются на сервере.
Типовой сценарий:
Критично соблюдать:
SJCL проектировалась с акцентом на:
CCM режим обеспечивает защиту от:
Несмотря на кросс-языковую совместимость, существуют ограничения:
Поэтому критично всегда тестировать расшифровку на реальных SJCL-данных, а не только на синтетических примерах.