Хотя моя постоянная работа в настоящее время не связана с конфиденциальностью данных, я собираюсь на ежегодную конференцию Ponemon Fellows и Ответственное управление информацией в следующем месяце и задумался о состоянии конфиденциальности данных и хранилищах документов JSON, таких как MongoDB. . Помимо стандарта JSON Web Encryption (JWE), доступно очень мало информации общего характера, и большая часть относится к конкретным хранилищам данных. Для JWE существует несколько сквозных руководств, в них ничего не говорится о том, как точно определить, что нужно защищать, и это может быть довольно сложно. Что касается хранилищ данных, например Redis, многие вообще не имеют особой безопасности (хотя и преднамеренно). Другие, реализованные как службы SAAS, просто используют подход к шифрованию данных по принципу все или ничего. В этой статье с помощью кода демонстрируется простой и гибкий подход к обеспечению безопасности, который можно использовать практически с любым хранилищем документов JSON в браузере или на сервере.
В следующих статьях будет рассказано о создании зашифрованного индекса с возможностью полнотекстового поиска, сегментировании данных для обеспечения соответствия и безопасности, подходах к авторизации и хранению / удалению записей.
Описание корпуса
Предположим, что Джо создает объекты JavaScript на клиентском компьютере и желает передать их на сервер или другой клиентский компьютер, одновременно защищая различные аспекты объектов разными способами, а также позволяя их индексировать и искать. Ниже представлена часть одного из объектов Джо:
const joe = { messages: [ { security:"confidential", summary:"confidential executive summary", message:"joe's confidential info" }, { security:"secret", summary:"secret executive summary", message:"joe's secret info" }, { security:"top secret executive summary", summary:"executive summary", message:"joe's top secret info" } ], privateKey: "joe's private key", name: "joe", ssn: "555-55-5555" };
Джо хотел бы обрабатывать данные следующим образом:
- SSN должен быть замаскирован, чтобы отображались только последние 4 номера, и его нельзя было восстановить на другой машине.
- Конфиденциальную информацию можно искать по ее резюме, но полный текст должен быть зашифрован.
- Секретная информация должна иметь зашифрованное резюме и полный текст.
- Совершенно секретная информация никогда не должна покидать исходную машину.
- Данные только для глаз, например закрытый ключ, может покинуть машину, если он зашифрован. И, поскольку Джо не хочет, чтобы кто-либо знал, что это за информация, имя ключа также необходимо зашифровать.
Итак, Джо нужно написать код, чтобы пройти через свой объект и замаскировать, зашифровать или удалить данные. Не слишком сложно, за исключением того, что Джо знает, что со временем природа объекта изменится, и если он напишет очень специфический код, он быстро станет неуправляемым. Существует ряд библиотек для просмотра графов документов. У MongoDB есть отличный вариант, но вы должны использовать Mongo. GraphQL отлично подходит для извлечения, но не идеален для преобразования. И я сам написал пару таких библиотек, одна называется assentials.
Assentials предоставляет маршрутизатор, который можно использовать для преобразования объектов. Роутер спросите вы? Да, роутер. Маршрутизаторы обычно принимают входные данные и изменяют их или создают побочные эффекты в зависимости от условий сопоставления и связанных действий. Некоторые маршрутизаторы являются узкоспециализированными и поддерживают только URL-адреса, другие, такие как маршрутизатор с ExpressJS, действительно могут обрабатывать практически любой объект, хотя по соглашению они используются для HTTP-запросов и ответов. Маршрутизатору assentials все равно, какие данные вы маршрутизируете. Думайте об этом как о физической маршрутизации пакета через множество обработчиков, которые могут просматривать или изменять содержимое пакета, но в конце дня должны доставить его следующему получателю в цепочке, прежде чем он в конечном итоге будет доставлен запрашивающей стороне, если только предоставляется допустимая подстановка (хватит секретности! Но Джо может использовать это в своих интересах, прежде чем выпускать свои объекты в мир).
Маршрутизатор assentials особенно силен тем, что поддерживает буквальное, функциональное, регулярное выражение и сопоставление объектов с помощью деструктивного присваивания или вложенных сопоставлений литералов. В результате можно передать объект маршрутизатору assentials и использовать маршруты для сопоставления частей объекта, а затем внести в объект изменения. Это означает, что каждое ограничение безопасности может быть отдельным случаем. Легко читать, легко поддерживать. При этом наверняка найдутся и другие варианты, к которым можно было бы применить общий подход, описанный ниже.
Защита объекта JavaScript
Итак, вернемся к Джо, вот что он мог написать. Обратите внимание на эффективное использование деструктурирующих и частичных литералов объектов для получения только желаемых свойств объекта:
const router = assentials.router, route = assentials.route; const secure = async (object,parentKey,parentObject) => { const transform = router( // mask SSN route(/^\d{3}-?\d{2}-?\d{4}$/, (value) => { parentObject[parentKey] = "***-**-" + value.substring(7); }), // insert more literal rules here // return anything that is not an object // assentials uses the Iterator paradigm to exit routing route((value) => !value || typeof(value)!=="object", (value) => { return {value,done:true}; }), // walk down objects route((value) => value && typeof(value)==="object", async (object) => { // not using forEach, async needed for(const key in object) { // pass parentKey and parentObject on recursion await secure(object[key],key,object); } }), // encrypt confidential message route({security:"confidential"}, async (object) => { object.message = await encrypt(object.message); }), // encrypt secret message and summary route({security:"secret"}, async (object) => { object.summary = await encrypt(object.summary); object.message = await encrypt(object.message); }), // delete top secret message route({security:"top secret"}, // Note, parentKey and parentObject were passed as // last arguments key and object to recursion above (object) => { delete parentObject[parentKey]; }), // encrypt key name and value for eyes only private key route(({privateKey}) => privateKey!==undefined, async (object) => { object[await encrypt("privateKey")] = await encrypt(object.privateKey); delete object.privateKey; }) // insert more object property rules here ) await transform(object); return object; }
Вот и все, за исключением того, что у нас нет функции encrypt
. Джо может пока просто притвориться, чтобы облегчить визуальный осмотр для отладки. В конце статьи мы расскажем о реальном шифровании.
const encrypt = async (value) { // reverse and mark the string returned so we know // it is something encrypted, or we won't // know what to decrypt! return `!e!${value.split("").reverse().join("")}!e!`; }
Если вас интересует весь приведенный выше async
код, он существует по двум причинам:
assentials
функции предназначены для работы в асинхронном режиме.- Многие библиотеки шифрования асинхронны, и наша фиктивная функция должна быть готова к преобразованию в реальную вещь.
Вызов и ожиданиеsecure
с указанным выше документом вернут:
{ "messages": [ { "security": "confidential", "summary": "confidential executive summary", "message": "!e!ofni laitnedifnoc s'eoj!e!" }, { "security": "secret", "summary": "!e!yrammus evitucexe terces!e!", "message": "!e!ofni terces s'eoj!e!" }, null ], "name": "joe", "ssn": "***-**-5555", "!e!yeKetavirp!e!": "!e!yek etavirp s'eoj!e!" }
Если вы умеете читать в обратном направлении, Джо вас не обманул, но он может отладить код. Конечно, теперь Джо должен написать код, чтобы в какой-то момент данные можно было расшифровать!
Расшифровка объекта JavaScript
Расшифровать несколько проще.
const decrypt = async (value) => { // if we are dealing with an encrypted string if(typeof(value)==="string" && value.startsWith("!e!") && value.endsWith("!e!")) { // reverse the encryption process return value .substring(3) .split("") .reverse() .join("") .substring(3); } // otherwise just return value return value; } const unsecure = async (object,parentKey,parentObject) => { const transform = router( // treat all values like they are encrypted // if they are not, no change will be made route((value) => !value || typeof(value)!=="object", async (value) => { parentObject[parentKey] = await decrypt(value); }), route((value) => value && typeof(value)==="object", async (object) => { // treat all keys like they are encrypted // if they are not, no change will be made for(const key in object) { const value = object[key], decrypted = await decrypt(key); // if the key changed it was encrypted // so, update object if(key!==decrypted) { object[decrypted] = value; delete object[key]; } } }), // walk down objects route((value) => value && typeof(value)==="object", async (object) => { for(const key in object) { object[key] = await unsecure(await decrypt(object[key]),key,object); } }) ); await transform(object); return object; }
Вызов и ожидание unsecure
с зашифрованным объектом выше вернет:
{ "messages": [ { "security": "confidential", "summary": "confidential executive summary", "message": "joe's confidential info" }, { "security": "secret", "summary": "secret executive summary", "message": "joe's secret info" }, null ], "name": "joe", "ssn": "***-**-5555", "privateKey": "joe's private key" }
Настоящее шифрование
Имея заведомо работающую базу кода, Джо готов добавить настоящее шифрование. Библиотека cryptozoa упрощает это.
const encrypt = async (value) { const {data} = await cryptozoa.symmetric.encrypt(value,"mypassword"); return `!e!${data}!e!`; } const decrypt = async (value) => { if(typeof(value)==="string" && value.startsWith("!e!") && value.endsWith("!e!")) { const {data} = cryptozoa.symmetric.decrypt( value.substring(3).substring(0,-3), "mypassword"); return data; }
Очевидно, что Джо не должен включать свои симметричные ключи шифрования в производственный исходный код, а должен только передавать и хранить их в зашифрованном виде с асимметричным шифрованием. Но это тема для другого раза.
Ниже приведены зашифрованные и незашифрованные версии данных Джо:
{ "messages": [ { "security": "confidential", "summary": "confidential executive summary", "message": "!e!/qbBRmBi/V69SZaQqvLzjzvFQfUVq1kkyhXbPelVOdk=!e!" }, { "security": "secret", "summary": "!e!leCB4/9PGQs7fiUIs+TwK/Qgddzop/846Ejiyc4nvdA=!e!", "message": "!e!SwmsCQoyKQ593clsR3XlMmxahX8glq1gzqnX9lzHbT4=!e!" }, null ], "name": "joe", "ssn": "***-**-5555", "!e!u8sO9HKsYlpEtq4W1PdTfw==!e!": "!e!1l5srmvJMF2629Iu0MUwVnhNcHZoM/12UJMQSaPJXDo=!e!" } { "messages": [ { "security": "confidential", "summary": "confidential executive summary" }, { "security": "secret" }, null ], "name": "joe", "ssn": "***-**-5555" }
На данный момент Джо готов, но ему следует подумать о том, как применить JWE для своих нужд, поскольку это часть отраслевого стандарта с дополнительными возможностями, касающимися подписи и совместного использования ключей. Самая простая для понимания документация доступна на BitBucket. Возможно, добавление обработки JWE на каждом узле в маршруты или ее использование для улучшения функций encrypt
и decrypt
сработает.
Хлопайте, если вы сочли полезным работать над этим процессом с Джо. Будь в безопасности!