Как я зашифровал базу данных, нигде не сохраняя ключи.

Проблемы)

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

Однако, если вы храните поля базы данных в зашифрованном виде (по крайней мере, конфиденциальные данные), все, что у злоумышленников будет, это не поддающиеся расшифровке строки байтов — конфиденциальность ваших пользователей в безопасности.

Но подождите: чтобы ваш сервер мог хранить и извлекать данные, ему необходимо знать ключ шифрования! И вы не можете хранить этот ключ в самой базе данных — или где-нибудь на жестком диске сервера, если на то пошло — иначе злоумышленники также получат ключ, когда они взломают сервер.

И есть еще одна проблема: хранение полей в зашифрованном состоянии сделало бы невозможным поиск — мы не можем создать индексную базу данных для поиска полей, содержащих, скажем, слово «ипотека», если поля зашифрованы.

И нет, создание индекса на основе исходных незашифрованных данных не сработает — если злоумышленник увидит, что заданное зашифрованное поле имеет индексы, указывающие на него, указывающие на то, что оно содержит слова «ипотека», «платеж», ‘Январь’ и ‘2015’, то содержание поля стало бы достаточно очевидным — даже без его расшифровки.

К счастью, есть способы обойти эти две проблемы.

Решение первой проблемы — сгенерировать ключ из пароля (который нигде не хранится).

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

Такой метод называется «функция деривации ключей» — или, для краткости, KDF.

К счастью, такая функция, называемая ПБКДФ2 широко доступен — включен в OpenSSL и, следовательно, может использоваться на таких платформах, как Node.js, PHP и т. д., из коробки.

PBKDF2 работает, «растягивая» строки — он берет пароль (или любую строку), которую вы ему даете, и выполняет с ним тысячи сложных преобразований, пока не создаст последовательность байтов любой требуемой длины, без очевидной связи с паролем. исходная строка.

Вам нужно будет вводить пароль на свой веб-сервер каждый раз, когда вы его запускаете (поскольку он нигде не хранится); он будет использовать пароль для генерации ключа шифрования и с этого момента использовать этот ключ, не сохраняя его.

Решение второй проблемы заключается в создании слепых индексов.

Идея состоит в том, чтобы вычислить хэш из условий поиска, а затем использовать хэш для индексации.

Например: предположим, мы хотим найти, какие из (зашифрованных) полей содержат слово «ипотека».

Во-первых, мы вычисляем хэш от слова: скажем, «ипотека» -> 14231297424532579.

Затем мы создаем индекс, в котором перечислены все поля, содержащие слово, хэш которого равен числу 14231297424532579 — без сохранения фактического слова «ипотека».
т.е. «Слово, хэш которого равен 14231297424532579, содержится в строках 34, 156, 1240,…»

Это называется «слепой индекс» — поскольку в нем фактически не хранится слово «ипотека» — злоумышленник, получивший базу данных, не сможет выяснить из этого индекса, какие слова содержатся в каких полях.

Наконец, всякий раз, когда нам нужно найти строки, содержащие слово «ипотека», мы снова вычисляем его хэш (то есть число 14231297424532579), а затем ищем его в слепом индексе.

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

Мы можем добавить дополнительный уровень безопасности, используя секретную соль для генерации хэша — и сгенерировать этот хэш из того же пароля, который мы использовали для генерации ключа. Таким образом, злоумышленник не сможет выяснить, какое слово хешируется до 14231297424532579 при атаке по словарю — не зная пароля.

Вот пример (на Node.js, но можно портировать на любой язык)

Во-первых, мы импортируем криптобиблиотеку (которую Node предоставляет из коробки):

const crypto = require('crypto');

Затем мы пишем вспомогательную функцию для растяжения строки; Я использую алгоритм sha512 с сотней тысяч итераций — дает очень хорошие результаты без чрезмерной загрузки процессора.

function stretchString(s, salt, outputLength){
  return crypto.pbkdf2Sync(s, salt, 100000, outputLength, 'sha512');
}

Таким образом, все, что нам нужно для растяжения строки, это (помимо самой строки), используемая соль и количество байтов, которые мы хотим получить для вывода.

Затем с помощью нашего stretchString мы генерируем как наш криптографический ключ, так и очень хорошую соль для использования в наших слепых индексах, и все это только из пароля.

function keyFromPassword(password){
  
  const keyPlusHashingSalt = stretchString(password, 'salt', 24 + 48);
  return {
    cipherKey: keyPlusHashingSalt.slice(0,24), 
    hashingSalt: keyPlusHashingSalt.slice(24)
  };
}

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

function encrypt(key, sourceData){
  const iv = Buffer.alloc(16, 0); 
  const cipher = crypto.createCipheriv('aes-192-cbc', key.cipherKey, iv);
  let encrypted = cipher.update(sourceData, 'binary', 'binary');
  encrypted += cipher.final('binary');
  return encrypted;
}

А потом тем же (симметричным) ключом расшифровать его обратно:

function decrypt(key, encryptedData){
  const iv = Buffer.alloc(16, 0); 
  const decipher = crypto.createDecipheriv('aes-192-cbc', key.cipherKey, iv);
  let decrypted = decipher.update(encryptedData, 'binary', 'binary');
  decrypted += decipher.final('binary');
  return decrypted;
}

Теперь все, что нам нужно, это функция для вычисления целочисленных хэшей, которые можно использовать для слепого индексирования:

function hash(key, sourceData){
  const hashBuffer = stretchString(sourceData, key.hashingSalt, 6);
  return hashBuffer.readUIntLE(0,6);
}

Вот и все! вот полный код:

const crypto = require('crypto');




function stretchString(s, salt, outputLength){
  return crypto.pbkdf2Sync(s, salt, 100000, outputLength, 'sha512');
}



function keyFromPassword(password){
  
  const keyPlusHashingSalt = stretchString(password, 'salt', 24 + 48);
  return {
    cipherKey: keyPlusHashingSalt.slice(0,24), 
    hashingSalt: keyPlusHashingSalt.slice(24)
  };
}


function encrypt(key, sourceData){
  const iv = Buffer.alloc(16, 0); 
  const cipher = crypto.createCipheriv('aes-192-cbc', key.cipherKey, iv);
  let encrypted = cipher.update(sourceData, 'binary', 'binary');
  encrypted += cipher.final('binary');
  return encrypted;
}



function decrypt(key, encryptedData){
  const iv = Buffer.alloc(16, 0); 
  const decipher = crypto.createDecipheriv('aes-192-cbc', key.cipherKey, iv);
  let decrypted = decipher.update(encryptedData, 'binary', 'binary');
  decrypted += decipher.final('binary');
  return decrypted;
}



function hash(key, sourceData){
  const hashBuffer = stretchString(sourceData, key.hashingSalt, 6);
  return hashBuffer.readUIntLE(0,6);
}

const key = keyFromPassword('Our password');
const encryptedTest = encrypt(key, 'This is a test');

console.log( decrypt(key,  encryptedTest) ); 

console.log( hash(key, 'This is another test') ); 

Похожие записи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *