Русский
Русский
English
Статистика
Реклама

Subtlecrypto

Web Cryptography API пример использования

16.09.2020 14:04:44 | Автор: admin


Доброго времени суток, друзья!

В этом туториале мы рассмотрим Web Cryptography API: интерфейс шифрования данных на стороне клиента.

Данный туториал основан на этой статье.

Предполагается, что вы немного знакомы с шифрованием.

Что конкретно мы будем делать?

Мы напишем простой сервер, который будет принимать зашифрованные данные от клиента и возвращать их ему по запросу. Сами данные будут обрабатываться на стороне клиента.

Сервер будет реализован на Node.js с помощью Express, клиент на JavaScript. Для стилизации будет использоваться Bootstrap.

Код проекта находится здесь.

Поиграть с кодом можно здесь.

Если вам это интересно, прошу следовать за мной.

Подготовка


Создаем директорию crypto-tut:

mkdir crypto-tut

Заходим в нее и инициализируем проект:

cd crypto-tutnpm init -y

Устанавливаем express:

npm i express

Устанавливаем nodemon:

npm i -D nodemon

Редактируем package.json:

"main": "server.js","scripts": {    "start": "nodemon"},

Структура проекта:

crypto-tut    --node_modules    --src        --client.js        --index.html        --style.css    --package-lock.json    --package.json    --server.js

Содержание index.html:

<head>    <!-- Bootstrap CSS -->    <link rel="stylesheet" href="http://personeltest.ru/aways/stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">    <link rel="stylesheet" href="style.css">    <script src="client.js" defer></source></head><body>    <div class="container">        <h3>Web Cryptography API Tutorial</h3>        <input type="text" value="Hello, World!" class="form-control">        <div class="btn-box">            <button class="btn btn-primary btn-send">Send message</button>            <button class="btn btn-success btn-get" disabled>Get message</button>        </div>        <output></output>    </div></body>

Содержание style.css:

h3,.btn-box {    margin: .5em;    text-align: center;}input,output {    display: block;    margin: 1em auto;    text-align: center;}output span {    color: green;}

Сервер


Приступаем к созданию сервера.

Открываем server.js.

Подключаем express и создаем экземпляры приложения и маршрутизатора:

const express = require('express')const app = express()const router = express.Router()

Подключаем middleware (промежуточный слой между запросом и ответом):

// разбор запросаapp.use(express.json({    type: ['application/json', 'text/plain']}))// подключение роутераapp.use(router)// директория со статическими файламиapp.use(express.static('src'))

Создаем переменную для хранения данных:

let data

Обрабатываем получение данных от клиента:

router.post('/secure-api', (req, res) => {    // получаем данные из тела запроса    data = req.body    // выводим данные в терминал    console.log(data)    // закрываем соединение    res.end()})

Обрабатываем отправку данных клиенту:

router.get('/secure-api', (req, res) => {    // данные отправляются в формате JSON,    // после чего соединение автоматически закрывается    res.json(data)})

Запускаем сервер:

app.listen(3000, () => console.log('Server ready'))

Выполняем команду npm start. В терминале появляется сообщение Server ready. Открываем http://localhost:3000:



На этом с сервером мы закончили, переходим к клиентской части приложения.

Клиент


Здесь начинается самое интересное.

Открываем файл client.js.

Для шифрования данных будет использоваться симметричный алгоритм AES-GCM. Такие алгоритмы позволяют использовать один и тот же ключ для шифрования и расшифровки.

Создаем функцию генерации симметричного ключа:

// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/generateKeyconst generateKey = async () =>    window.crypto.subtle.generateKey({        name: 'AES-GCM',        length: 256,    }, true, ['encrypt', 'decrypt'])

Перед шифрованием данные необходимо закодировать в поток байтов. Это легко сделать с помощью класса TextEncoder:

// https://developer.mozilla.org/en-US/docs/Web/API/TextEncoderconst encode = data => {    const encoder = new TextEncoder()    return encoder.encode(data)}

Далее, нам нужен вектор исполнения (вектор инициализации, initialization vector, IV), представляющий собой случайную или псевдослучайную последовательность символов, которую добавляют к ключу шифрования для повышения его безопасности:

// https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValuesconst generateIv = () =>    // https://developer.mozilla.org/en-US/docs/Web/API/AesGcmParams    window.crypto.getRandomValues(new Uint8Array(12))

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

const encrypt = async (data, key) => {    const encoded = encode(data)    const iv = generateIv()    const cipher = await window.crypto.subtle.encrypt({        name: 'AES-GCM',        iv    }, key, encoded)    return {            cipher,            iv        }}

После шифрования данных с помощью SubtleCrypto, они представляют собой буферы необработанных двоичных данных. Это не лучший формат для передачи и хранения. Давайте это исправим.

Данные, обычно, передаются в формате JSON и хранятся в базе данных. Поэтому имеет смысл упаковать данные в портируемый формат. Одним из способов это сделать является конвертация данных в строки в формате base64:

// https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-Stringconst pack = buffer => window.btoa(    String.fromCharCode.apply(null, new Uint8Array(buffer)))

После получения данных необходимо выполнить обратный процесс, т.е. преобразовать строки в кодировке base64 в буферы необработанных двоичных данных:

// https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-Stringconst unpack = packed => {    const string = window.atob(packed)    const buffer = new ArrayBuffer(string.length)    const bufferView = new Uint8Array(buffer)    for (let i = 0; i < string.length; i++) {        bufferView[i] = string.charCodeAt(i)    }    return buffer}

Остается расшифровать полученные данные. Однако, после расшифровки нам необходимо декодировать поток байтов в исходный формат. Это можно сделать с помощью класса TextDecoder:

// https://developer.mozilla.org/en-US/docs/Web/API/TextDecoderconst decode = byteStream => {    const decoder = new TextDecoder()    return decoder.decode(byteStream)}

Функция расшифровки представляет собой инверсию функции шифрования:

// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/decryptconst decrypt = async (cipher, key, iv) => {    const encoded = await window.crypto.subtle.decrypt({        name: 'AES-GCM',        iv    }, key, cipher)    return decode(encoded)}

На данном этапе содержимое client.js выглядит так:

const generateKey = async () =>    window.crypto.subtle.generateKey({        name: 'AES-GCM',        length: 256,    }, true, ['encrypt', 'decrypt'])const encode = data => {    const encoder = new TextEncoder()    return encoder.encode(data)}const generateIv = () =>    window.crypto.getRandomValues(new Uint8Array(12))const encrypt = async (data, key) => {    const encoded = encode(data)    const iv = generateIv()    const cipher = await window.crypto.subtle.encrypt({        name: 'AES-GCM',        iv    }, key, encoded)    return {        cipher,        iv    }}const pack = buffer => window.btoa(    String.fromCharCode.apply(null, new Uint8Array(buffer)))const unpack = packed => {    const string = window.atob(packed)    const buffer = new ArrayBuffer(string.length)    const bufferView = new Uint8Array(buffer)    for (let i = 0; i < string.length; i++) {        bufferView[i] = string.charCodeAt(i)    }    return buffer}const decode = byteStream => {    const decoder = new TextDecoder()    return decoder.decode(byteStream)}const decrypt = async (cipher, key, iv) => {    const encoded = await window.crypto.subtle.decrypt({        name: 'AES-GCM',        iv    }, key, cipher)    return decode(encoded)}

Теперь реализуем отправку и получение данных.

Создаем переменные:

// поле для ввода сообщения, которое будет зашифрованоconst input = document.querySelector('input')// контейнер для вывода результатовconst output = document.querySelector('output')// ключlet key

Шифрование и отправка данных:

const encryptAndSendMsg = async () => {    const msg = input.value     // шифрование    key = await generateKey()    const {        cipher,        iv    } = await encrypt(msg, key)    // упаковка и отправка    await fetch('http://localhost:3000/secure-api', {        method: 'POST',        body: JSON.stringify({            cipher: pack(cipher),            iv: pack(iv)        })    })    output.innerHTML = `Сообщение <span>"${msg}"</span> зашифровано.<br>Данные отправлены на сервер.`}

Получение и расшифровка данных:

const getAndDecryptMsg = async () => {    const res = await fetch('http://localhost:3000/secure-api')    const data = await res.json()    // выводим данные в консоль    console.log(data)    // распаковка и расшифровка    const msg = await decrypt(unpack(data.cipher), key, unpack(data.iv))    output.innerHTML = `Данные от сервера получены.<br>Сообщение <span>"${msg}"</span> расшифровано.`}

Обработка нажатия кнопок:

document.querySelector('.btn-box').addEventListener('click', e => {    if (e.target.classList.contains('btn-send')) {        encryptAndSendMsg()        e.target.nextElementSibling.removeAttribute('disabled')    } else if (e.target.classList.contains('btn-get')) {        getAndDecryptMsg()    }})

На всякий случай перезапускаем сервер. Открываем http://localhost:3000. Нажимаем на кнопку Send message:



Видим данные, полученные сервером, в терминале:

{  cipher: 'j8XqWlLIrFxyfA2easXkJTLLIt9x8zLHei/tTKI=',  iv: 'F8doVULJzbEQs3M1'}

Нажимаем на кнопку Get message:



Видим те же самые данные, полученные клиентом, в консоли:

{  cipher: 'j8XqWlLIrFxyfA2easXkJTLLIt9x8zLHei/tTKI=',  iv: 'F8doVULJzbEQs3M1'}

Web Cryptography API открывает перед нами интересные возможности по защите конфиденциальной информации на стороне клиента. Еще один шаг в сторону бессерверной веб-разработки.

Поддержка данной технологии на сегодняшний день составляет 96%:



Надеюсь, статья вам понравилась. Благодарю за внимание.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru