Сложность материала высокая. Инструкция рассчитана на продвинутых пользователей, знакомых с 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
Отключение логов
По запросу можем сделать отключение логов сценариев, чтобы в истории вообще ничего не сохранялось.