PKCS7: стандарт и реализация

Блочные алгоритмы шифрования работают с фиксированным размером блока данных. Например, AES оперирует блоками по 16 байт. При обработке произвольного текста возникает проблема: длина входных данных редко кратна размеру блока. Для корректного шифрования используется дополнение (padding), приводящее данные к необходимой длине.

PKCS#7 (Public-Key Cryptography Standards #7 padding) определяет универсальный способ дополнения данных для блочных алгоритмов. Его особенность заключается в простоте и однозначности восстановления исходного сообщения.


Механизм PKCS#7

Алгоритм дополняет данные байтами, каждый из которых равен количеству добавленных байтов.

Пусть размер блока равен N, а длина сообщения — L. Тогда вычисляется:

  • paddingLength = N - (L % N)

Если сообщение уже кратно блоку, добавляется полный блок заполнения длиной N.

Каждый байт padding имеет значение paddingLength.

Пример

Исходные данные (ASCII):

HELLO

Размер блока: 8 байт Длина сообщения: 5 байт Padding: 3 байта

Результат:

HELLO\x03\x03\x03

Каждый добавленный байт равен 0x03.


Особенности стандарта PKCS#7

  • Работает для блоков от 1 до 255 байт
  • Всегда добавляется минимум один байт padding
  • Удаление padding однозначно: последний байт указывает количество добавленных байтов
  • Поддерживает симметричные блочные алгоритмы (AES, DES, 3DES и др.)

PKCS#7 в CryptoJS

В библиотеке CryptoJS PKCS#7 используется как padding по умолчанию для блочных шифров.

Внутренняя логика реализуется через объект:

CryptoJS.pad.Pkcs7

Он применяется автоматически при использовании режимов CBC, ECB и других блочных режимов.


Пример шифрования с PKCS#7

const CryptoJS = require("crypto-js");

const message = "HELLO";
const key = CryptoJS.enc.Utf8.parse("1234567890123456");

const encrypted = CryptoJS.AES.encrypt(message, key, {
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7,
    iv: CryptoJS.enc.Utf8.parse("1234567890123456")
});

console.log(encrypted.toString());

PKCS#7 применяется автоматически для выравнивания строки до размера блока AES (16 байт).


Пример расшифровки

const decrypted = CryptoJS.AES.decrypt(encrypted, key, {
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7,
    iv: CryptoJS.enc.Utf8.parse("1234567890123456")
});

console.log(decrypted.toString(CryptoJS.enc.Utf8));

После расшифровки библиотека удаляет PKCS#7 padding, используя значение последнего байта.


Внутренняя реализация в CryptoJS

PKCS#7 в CryptoJS реализован через два основных метода:

  • pad: добавление байтов
  • unpad: удаление байтов

Логика добавления:

pad: function (data, blockSize) {
    const blockSizeBytes = blockSize * 4;
    const padding = blockSizeBytes - (data.sigBytes % blockSizeBytes);

    const paddingWord = (padding << 24) | (padding << 16) | (padding << 8) | padding;

    const paddingWords = [];
    for (let i = 0; i < padding; i += 4) {
        paddingWords.push(paddingWord);
    }

    data.concat(CryptoJS.lib.WordArray.create(paddingWords, padding));
}

Логика удаления:

unpad: function (data) {
    const padding = data.words[(data.sigBytes - 1) >>> 2] & 0xff;
    data.sigBytes -= padding;
}

Особенности работы с WordArray

CryptoJS использует внутренний формат WordArray, где данные хранятся 32-битными словами. PKCS#7 работает на уровне байтов, поэтому происходит преобразование между байтовым и словесным представлением.

Ключевой момент:

  • sigBytes определяет фактическую длину данных
  • padding влияет только на хвостовую часть массива

Совместимость с алгоритмами

PKCS#7 используется в CryptoJS для всех блочных режимов:

  • AES
  • DES
  • Triple DES

В потоковых режимах (CTR, CFB, OFB) padding не применяется, так как они не требуют выравнивания блока.


Отличия PKCS#7 от других схем padding

Zero padding

Заполнение нулями приводит к неоднозначности при расшифровке, если исходные данные заканчиваются нулевыми байтами.

ANSI X.923

Последний байт содержит длину padding, но остальные байты заполнены нулями.

PKCS#7

Каждый байт содержит значение длины padding, что делает схему полностью однозначной.


Типичные ошибки при использовании

Неправильное отключение padding

padding: CryptoJS.pad.NoPadding

Использование без выравнивания данных приводит к ошибкам:

  • некорректная длина блока
  • исключения при шифровании

Двойное добавление padding

При ручной обработке данных перед CryptoJS возможно случайное добавление padding дважды, что приводит к искажению результата при расшифровке.


Безопасность PKCS#7

Сам по себе PKCS#7 не является механизмом безопасности, он лишь обеспечивает корректное выравнивание. Однако его неправильная реализация может привести к уязвимостям:

  • padding oracle атаки при некорректной обработке ошибок
  • утечка информации о структуре данных через ошибки расшифровки

Практическое использование в CryptoJS API

PKCS#7 встроен в стандартные вызовы:

CryptoJS.AES.encrypt(data, key)
CryptoJS.AES.decrypt(cipher, key)

Если явно не указано иное, используется:

  • CBC режим
  • PKCS#7 padding

Поведение при декодировании

При расшифровке происходит:

  1. Восстановление блоков
  2. Чтение последнего байта
  3. Удаление padding байтов
  4. Возврат исходного текста

Если padding повреждён, возникает ошибка или некорректный результат.


Работа с бинарными данными

PKCS#7 корректно обрабатывает:

  • текстовые строки UTF-8
  • бинарные массивы
  • Base64-представления

Важно учитывать, что padding добавляется на уровне байтов, а не символов.


Значение PKCS#7 в экосистеме шифрования

PKCS#7 стал де-факто стандартом для симметричного блочного шифрования благодаря:

  • простоте реализации
  • универсальности
  • предсказуемости поведения
  • совместимости между библиотеками и языками

CryptoJS следует этому стандарту, обеспечивая совместимость с другими криптографическими реализациями, включая OpenSSL и Node.js crypto API в совместимых режимах.