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

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

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

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

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

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

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

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

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

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

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

На английском - hybrid public key encryption (HPKE).
Комбинирование двух указанных методов шифрования для того, чтобы получить плюсы обоих - возможность шифровать большие объемы данных с использованием публичного и приватного ключа.
Схематично:
javascript
// Шифрование: ключ = случайнаяСтрока() шифр_данных = шифроватьСимметрично(данные, ключ) шифр_ключа = шифроватьАсиметрично(ключ, публичный_ключ) // Дешифрование: ключ = дешифроватьАсиметрично(шифр_ключа, приватный_ключ) данные = дешифроватьСимметрично(шифр_данных, ключ)

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

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

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

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

Код перед HEAD:
javascript
<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:
javascript
<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

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

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

Комикс

https://xkcd.ru/538/
https://xkcd.ru/538/