CreatiumMax

Шифрование персональных данных

❗Сложность материала высокая. Инструкция рассчитана на продвинутых пользователей, знакомых с Javascript.

Цель этого материала

Предоставить способ шифровать данные таким образом, чтобы увидеть их мог только их владелец (автор). Для этого мы будем шифровать данные на фронтенде, а на бэкенде храниться будут только зашифрованные данные.

Используемые методы

Для начала разберемся с базовой терминологией, для понимания происходящего.

Симметричное шифрование

Шифрование, при котором и для шифрования и для дешифрования используется один и тот же ключ (пароль). Позволяет шифровать большие объемы данных, работает быстро.

Схематично:

typescript
шифр = шифроватьСимметрично(данные, ключ)
данные = дешифроватьСимметрично(шифр, ключ)

Асимметричное шифрование

Шифрование, при котором используется 2 ключа - публичный и приватный. Публичный используется для шифрования данных, приватный для дешифрования. Подходит для небольших объемов данных, работает медленно.

Схематично:

typescript
шифр = шифроватьАсимметрично(данные, публичный_ключ)
данные = дешифроватьАсимметрично(шифр, приватный_ключ)

Гибридное шифрование публичным ключом

На английском - hybrid public key encryption (HPKE).

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

Схематично:

typescript
// Шифрование:
ключ = случайнаяСтрока()
шифр_данных = шифроватьСимметрично(данные, ключ)
шифр_ключа = шифроватьАсиметрично(ключ, публичный_ключ)

// Дешифрование:
ключ = дешифроватьАсиметрично(шифр_ключа, приватный_ключ)
данные = дешифроватьСимметрично(шифр_данных, ключ)

Инструменты на Cremax

Ниже представлен набор инструментов, которые использовались для шифрования персональных данных на одном из проектов клиента, и которые можно повторно использовать в других проектах в силу универсальности подхода.

1 - Готовые сценарии

  • Гибридное шифрование данных:

https://cremax.ru/scenario-playground?saved=638ks8w493ci2d1cmqi2533u

  • Гибридное дешифрование данных

https://cremax.ru/scenario-playground?saved=638ksidnv3n81y7cm8hx570q

  • Пример использования:

https://cremax.ru/scenario-playground?saved=638ksoxz498h5zh5u5425o0q

  • Сохранение пароля в сессию (пример):

https://cremax.ru/scenario-playground?saved=6518kcamv83s583tgi3eec01

2 - Вставка кода

Код перед HEAD:

typescript
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsencrypt/3.3.2/jsencrypt.min.js" integrity="sha512-94ncgEEqkuZ4yNTFmu2dSn1TJ6Ij+ANQqpR7eLVU99kzvYzu6UjBxuVoNHtnd29R+T6nvK+ugCVI698pbyEkvQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/aes-js/3.1.2/index.min.js" integrity="sha512-LOqfKFwH2W3jeb0NzXcImFlSyoL7hjsWbZvIeKNOaZw1gFw+yKTE/QUDGLit2KWdd57qd6IgMDkppK2tkwIEhA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

Код в конце HEAD:

typescript
<script>
  const crcr = {};

  crcr.utf8 = aesjs.utils.utf8;

  crcr.random_bytes = function (length) {
      const array = new Uint8Array(length);
      window.crypto.getRandomValues(array);
      return array;
  };

  crcr.rsa_generate = function (key_size = 2048) {
    var crypt = new JSEncrypt({ default_key_size: key_size });
    crypt.getKey();
    return {
      private_key: crypt.getPrivateKey(),
      public_key: crypt.getPublicKey(),
    };
  };

  crcr.rsa_private_decrypt = function (private_key, data) {
    var decrypt = new JSEncrypt();
    decrypt.setPrivateKey(private_key);
    return decrypt.decrypt(data);
  }

  crcr.aes_cbc_encrypt = function (text, keyBytes, ivBytes) {
    const cbc = new aesjs.ModeOfOperation.cbc(keyBytes, ivBytes);
    const textBytes = aesjs.padding.pkcs7.pad(crcr.utf8.toBytes(text));
    return crcr.bytesToBase64(cbc.encrypt(textBytes));
  };

  function fixEncoding(str) {
    const bytes = [];
    for (let i = 0; i < str.length; i++) {
        bytes.push(str.charCodeAt(i));
    }

    const utf8Decoder = new TextDecoder('utf-8');
    const fixedStr = utf8Decoder.decode(new Uint8Array(bytes));
    
    return fixedStr;
  }

  crcr.aes_cbc_decrypt = function (text, keyBytes, ivBase64) {
    const textBytes = crcr.base64ToBytes(text);
    const ivBytes = [...crcr.base64ToBytes(ivBase64)];
    const cbc = new aesjs.ModeOfOperation.cbc(keyBytes, ivBytes);
    const decryptedBytes = aesjs.padding.pkcs7.strip(cbc.decrypt(textBytes));
    return fixEncoding(atob(crcr.bytesToBase64(decryptedBytes)));
  };

  crcr.base64ToBytes = function (base64) {
    return Uint8Array.from(atob(base64), (m) => m.codePointAt(0));
  }

  crcr.bytesToBase64 = function (bytes) {
    return btoa(Array.from(bytes, (byte) => String.fromCodePoint(byte)).join(""));
  }
</script>

3 - Готовые компоненты

  • Компонент для расшифровки пароля и данных: 45271

  • Поле для ввода пароля от персональных данных: 45272

  • Компонент для отображения шифрованных данных: 45273

Отключение логов

По запросу можем сделать отключение логов сценариев, чтобы в истории вообще ничего не сохранялось.

Было полезно?

Остались вопросы? — Напишите нам в чат поддержки будучи авторизованным в своем аккаунте cremax.ru или через чат поддержки в Телеграме: @CreatiumMaxSupportBot