Расшифровка данных SJCL на Python (cryptography, PyCryptodome)

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

Шифрование в SJCL состоит из двух основных этапов:

  1. Генерация ключа из пароля (PBKDF2)
  2. Симметричное шифрование AES (обычно CCM режим)

PBKDF2 в SJCL

Ключ не используется напрямую из пароля. Вместо этого применяется PBKDF2 с HMAC-SHA256.

= (, , , )

Где:

  • password — пароль пользователя
  • salt — случайная соль
  • iter — количество итераций
  • ks — размер ключа

PBKDF2 делает перебор пароля вычислительно дорогим.


Особенности AES-CCM в SJCL

SJCL использует AES в режиме CCM (Counter with CBC-MAC). Этот режим объединяет шифрование и аутентификацию.

CCM обеспечивает:

  • конфиденциальность данных
  • проверку целостности
  • защиту от подмены ciphertext

В CCM данные разбиваются на блоки, и для каждого блока вычисляется MAC.


Подготовка расшифровки в Python

Для работы с SJCL в Python чаще всего используются:

  • PyCryptodome
  • cryptography

Оба варианта позволяют реализовать PBKDF2 и AES-CCM.


Разбор SJCL-структуры

Перед расшифровкой необходимо декодировать Base64 поля:

  • salt
  • iv
  • ct

Пример преобразования:

import base64
import json

data = json.loads(sjcl_json)

salt = base64.b64decode(data["salt"])
iv = base64.b64decode(data["iv"])
ciphertext = base64.b64decode(data["ct"])

Воспроизведение PBKDF2 в Python

Вариант с PyCryptodome

from Crypto.Protocol.KDF import PBKDF2
from Crypto.Hash import SHA256

key = PBKDF2(
    password,
    salt,
    dkLen=ks // 8,
    count=iter,
    hmac_hash_module=SHA256
)

Важно:

  • ks в SJCL задаётся в битах, поэтому делится на 8
  • SHA256 обязателен, так как SJCL использует HMAC-SHA256

Расшифровка AES-CCM через PyCryptodome

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.


Альтернативный вариант через cryptography

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.


Учет дополнительных данных (adata)

SJCL позволяет добавлять дополнительные данные, которые не шифруются, но участвуют в аутентификации.

Пример:

"adata": "header-information"

В Python это обязательно учитывается:

additional_data = adata.encode() if adata else None

Полный процесс расшифровки SJCL

Шаг 1: декодирование JSON

import json
import base64

Шаг 2: извлечение параметров

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", "")

Шаг 3: получение ключа PBKDF2

key = PBKDF2(password, salt, dkLen=ks // 8, count=iter, hmac_hash_module=SHA256)

Шаг 4: расшифровка AES-CCM

aesccm = AESCCM(key, tag_length=ts // 8)
plaintext = aesccm.decrypt(iv, ct, adata.encode() if adata else None)

Типичные ошибки при расшифровке SJCL

Несовпадение длины ключа

SJCL строго фиксирует размер ключа. Если использовать неправильный ks, расшифровка всегда будет проваливаться.


Ошибки IV

IV должен быть точно таким же, как в JS-версии. Любое изменение приводит к невозможности расшифровки.


Неверный режим AES

SJCL использует CCM. Попытка применить CBC или GCM приведёт к ошибкам MAC.


Ошибки кодирования Base64

SJCL использует стандарт Base64, но при переносе данных иногда появляются:

  • URL-safe Base64
  • обрезанные строки
  • потерянные padding символы =

Совместимость SJCL и Python криптографии

SJCL проектировалась как браузерная библиотека, но её криптографическая модель строго соответствует стандартным примитивам:

  • PBKDF2 (RFC 2898)
  • AES-CCM (NIST SP 800-38C)
  • HMAC-SHA256

Это делает её полностью переносимой в Python без необходимости эмуляции JavaScript.


Практическая структура маппинга SJCL → Python

SJCL поле Python эквивалент
salt PBKDF2 salt
iter PBKDF2 rounds
ks key length
iv nonce
ct ciphertext
adata additional data

Внутренний формат ciphertext

В SJCL ciphertext часто содержит:

  • зашифрованный текст
  • встроенный authentication tag

В зависимости от режима CCM:

  • часть данных используется как payload
  • часть как MAC

Поэтому иногда требуется разделение:

ciphertext, tag = ct[:-8], ct[-8:]

Оптимизация производительности PBKDF2

При высоких значениях iter (например, 100k+):

  • PBKDF2 становится узким местом
  • Python может работать значительно медленнее JS-реализации

Решения:

  • использовать hashlib.pbkdf2_hmac (быстрее PyCryptodome)
  • кэшировать ключи при повторных операциях
import hashlib

key = hashlib.pbkdf2_hmac(
    "sha256",
    password.encode(),
    salt,
    iter,
    dklen=ks // 8
)

Совместимость с экспортом SJCL

SJCL часто используется в веб-приложениях, где данные экспортируются в JSON и затем расшифровываются на сервере.

Типовой сценарий:

  • браузер (SJCL) → JSON ciphertext
  • backend (Python) → decrypt

Критично соблюдать:

  • одинаковый PBKDF2 hash
  • идентичные параметры CCM
  • точное Base64 декодирование

Особенности безопасности модели SJCL

SJCL проектировалась с акцентом на:

  • отсутствие глобального состояния
  • детерминированные криптографические параметры (кроме IV)
  • обязательную аутентификацию данных

CCM режим обеспечивает защиту от:

  • bit-flipping атак
  • подмены ciphertext
  • replay атак (при корректном использовании IV)

Переносимость и ограничения

Несмотря на кросс-языковую совместимость, существуют ограничения:

  • различия в реализации PBKDF2 между библиотеками
  • различия в обработке padding
  • различия в формате Base64

Поэтому критично всегда тестировать расшифровку на реальных SJCL-данных, а не только на синтетических примерах.