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

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

Вызов и ожидание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 сработает.

Хлопайте, если вы сочли полезным работать над этим процессом с Джо. Будь в безопасности!