Дэвид Лебланк - 19 смертных грехов, угрожающих безопасности программ
if(CryptProtectData(
&blobIn,
L"Sin#12 Example", // необязательный комментарий
&blobEntropy,
NULL,
NULL,
0,
&blobOut)) {
printf("Защита сработала.n");
} else {
printf("Ошибка при вызове CryptProtectData() -> %x", GetLastError());
exit(-1);
}
// Дешифрировать данные
DATA_BLOB blobVerify;
if(CryptUnprotectData(
&blobOut,
NULL,
&blobEntropy,
NULL,
NULL,
0,
&blobVerify)) {
printf("Расшифрованные данные: %sn", blobVerify.pbData);
} else {
printf("Ошибка при вызове CryptUnprotectData() -> %x",
GetLastError());
exit(-1);
}
if (blobOut.pbData)
LocalFree(blobOut.pbData);
if (blobVerify.pbData) {
SecureZeroMemory(blobOut.pbData, blobOut.cbData);
LocalFree(blobVerify.pbData);
}
Вот как реализована функция SecureZeroMemory в Windows:
...FORCEINLINE PVOID SecureZeroMemory(
void *ptr, size_t cnt) {
volatile char *vptr = (volatile char *)ptr;
while (cnt) {
*vptr = 0;
vptr++;
cnt—;
}
return ptr;
}
А вот другая реализация, которую предложил Дэвид Уилер (см. раздел «Другие ресурсы»):
...void guaranteed memset(void *v, int c, size t n)
{ volatile char *p=v; while(n-) *p++=c; return v; }
Искупление греха bASP.NET версии 1.1 и старше
Показанное ниже решение применимо к Web–приложениям, написанным на ASP.NET версии 1.1 и старше. Поскольку многие Web–приложения обращаются к базе данных, команда, работавшая над ASP.NET, постаралась максимально облегчить безопасное хранение секретной информации (скажем, строк соединения с сервером) в файле web.config. Подробнее см. статью в базе знаний Q329290 (ссылка приведена в разделе «Другие ресурсы»). Эта методика основана на использовании DPAPI.
Для хранения пароля в конфигурационном файле можно также обратиться к методу HashPasswordForStoringlnConfigFile.
Искупление греха в С# на платформе . NET Framework 2.0
В первом примере показано, как получить пароль, а потом записать защищенный пароль в файл. Отметим, что DPAPI позволяет защитить данные так, что они будут доступны либо только текущему пользователю, либо всем приложениям на данной машине. Что именно больше подходит для вашего приложения, определяется моделью угроз.
...byte[] sensitiveData = Encoding.UTF8.GetBytes(GetPassword());
byte[] protectedData = ProtectedData.Protect(sensitiveData, null,
DataProtectionScope.CurrentUser);
FileStream fs = new FileStream(filename, FileMode.Truncate);
fs.Write(protectedData, 0, protectedData.Length);
fs.Close();
Ниже продемонстрирована обратная процедура: файл открывается, и из него читаются секретные данные:
...FileStream fs = new FileStream(filename, FileMode.Open);
byte[] protectedData = new byte[512];
fs.Read(protectedData, 0, protectedData.Length);
byte[] unprotectedBytes = ProtectedData.Unprotect(protectedData, null,
DataProtectionScope.CurrentUser);
fs.Close();
Примечание. Если на платформе .NET Framework вы храните пароли в строках типа String, то лучше бы воспользоваться классом SecureStr ing. См. ссылку на статью «Making Strings More Secure» в разделе «Другие ресурсы».
Искупление греха в C/C++ для Mac OS X версии v10.2 и старше
На странице http://darwinsource.opendarwin.Org/10.3/SecurityTool–7/keychain_ add.с есть пример, показывающий, как добавить пароль или ключ к «брелку» Key–chain на компьютерах фирмы Apple. Используются следующие основные функции:
...// Установить пароль
SecKeychainRef keychain = NULL; // пользовательская цепочка ключей
// по умолчанию
OSStatus status = SecKeychainAddGenericPassword(keychain,
strlen(serviceName), serviceName,
strlen(accountName), accountName,
strlen(passwordData), passwordData,
NULL);
if (status == noErr) {
// все хорошо!
}
// Получить пароль
char *password = NULL;
u_int_32_t passwordLen = 0;
status = SecKeychainFindGenericPassword(keychain,
strlen(serviceName), serviceName,
strlen(accountName), accountName,
&passwordLen, &password,
NULL);
if (status == noErr) {
// все хорошо! Используем пароль
// Прибрать за собой
guaranteed_memset(password,42,passwordLen);
SecKeychainItemFreeContent(NULL, (void*) password);
}
Искупление греха без помощи операционной системы (или «храните секреты от греха подальше»)
Это посложнее. Конечно, лучше поручить всю трудную работу операционной системе, но если целевая ОС не готова помочь вам спрятать секрет, придется создать собственный механизм. Проще всего убрать секретные данные с линии огня.
Мы уже отмечали выше, что всегда надо думать, от кого вы защищаетесь и какова ценность защищаемых данных. Если вы работаете над Web–приложением, которое должно защищать некоторую секретную информацию, то ее следует разместить вне «Web–пространства». Иными словами, если приложение находится в каталоге , то сохраните секретные данные в , а еще лучше в , поскольку эти каталоги оказываются за линией огня. С другой стороны, до каталога wwwroot (и его подкаталогов) можно добраться из браузера. Разумеется, ваш сервер не должен возвращать клиенту текстовые конфигурационные файлы (к примеру, web.config, app.config и global.asa в случае IIS или httpd.conf или .htaccess в случае Apache), но достаточно небольшой ошибки в Web–приложении или Web–сервере – и противник сможет прочитать секретные данные.
В Windows можно также использовать реестр, тогда противнику придется как–то исполнить на серверной машине код, который может читать значения из реестра.
Если вы работаете с сервером Apache в Linux, Mac OS X и UNIX, то не стоит хранить секретные конфигурационные данные в каталоге, на который указывает параметр DocumentRoot (он определен в файле httpd.conf). Например, в дистрибутивах Red Hat и Fedora Core это /var/www/html. То же относится и к каталогу cgi–bin.
В примерах ниже показано, как читать секретные данные из ресурса, недоступного через Web.
Чтение из файловой системы из PHP–сценария в Linux
...<?php
$filename = "/home/apache/config", "r";
$fh = fopen($filename);
$data = fread($fh, filesize($filename));
fclose($fh);
?>
Чтение из файловой системы с помощью ASP.NET (С#)
Следующий код читает из app.config имя файла, в котором содержится строка соединения с SQL–сервером. Файл app.config выглядит следующим образом:
...<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="connectFile"
value="c:\webapps\config\sqlconn.config" />
</appSettings>
</configuration>
Остается добавить код на С# для получения значения параметра и последующего чтения строки соединения из файла:
...static string GetSQLConnectionString() {
NameValueCollection settings = ConfigurationSettings.AppSettings;
string filename = settings.Get("connectFile");
if (filename == null || filename.Length == 0)
throw IOException();
FileStream f = new FileStream(filename, FileMode.Open);
StreamReader reader = new StreamReader(d, Encoding.ASCII);
string connection = reader.ReadLine();
reader.Close();
f.Close();
return connection;
}
К вашим услугам также утилита aspnet_setreg, позволяющая сохранить и защитить конфигурационные данные.
Отметим, что в .NET Framework 2.0 класс Conf igurationSettings заменен на ConfigurationManager.
Чтение из файловой системы с помощью ASP (VBScript)
Это несколько сложнее, поскольку в ASP нет конфигурационных файлов. Однако можно поместить имя файла в переменную, хранящуюся в файле global.asa (по умолчанию ASP и I IS не возвращают клиенту содержимое этого файла), например так:
...Sub Application OnStart
Application("connectFile") = "c:webappsconfigsqlconn.txt"
End Sub
А затем прочитать этот файл, когда приложению понадобится строка соединения:
...Dim fso, file, pwd
Set fso = CreateObject("Scripting.FileSystemObject")
Set file = fso.OpenTextFile(Application("connectFile"))
connection = file.ReadLine
file.Close
Чтение из реестра с помощью ASP.NET (VB.NET)
Этот код читает не из файла, а из реестра:
...With My.Computer.Registry
Dim connection As String =
.GetValue("KKEY_LOCAL_MACHINESoftware" + _
"MyCompanyWebApp", "connectString", 0)
End With
Замечание по поводу Java и Java KeyStore
В JDK версии 1.2 и старше имеется класс KeyStore для управления ключами (javasecurity.KeyStore), который позволяет хранить сертификаты Х.509, закрытые ключи и – с помощью производных классов – ключи симметричных шифров. Однако KeyStore не предоставляет средств для защиты хранилища ключей. Поэтому если вы хотите получить ключ из программы, то должны прочитать ключ, используемый для шифрования хранилища из какого–то недоступного извне источника, например из файла вне домена приложения или Web–пространства, с помощью этого ключа расшифровать хранилище, получить оттуда закрытый ключ и воспользоваться им.
Поместить ключи в хранилище KeyStore позволяет приложение keytool, поставляемое в составе JDK, а для извлечения оттуда ключа надо написать примерно такой код:
...// Получить пароль для открытия хранилища ключей
private static char [] getPasswordFromFile()
{
try
{
BufferedReader pwdFile = new BufferedReader
(new FileReader("c:\webapps\config\pwd.txt"));
String pwdString = pwdFile.readLine();
pwdFile.close();
char [] pwd = new char[pwdString.length()];
pwdString.getChars(0, pwdString.length(), pwd, 0);
return pwd;
}
catch (Exception e) { return null; }
}
private static String getKeyStoreName()
{
return "<местоположение имени файла ключей>";
}
public static void main(String args[])
{
try {
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
// получить пароль пользователя и входной файловый поток
FileInputStream fis = new FileInputStream(getKeyStoreName());
char[] password = getPasswordFromFile();
ks.load(fis, password);
fis.close();
Key key = ks.getKey("mykey", password);
// Использовать ключ для криптографических операций
ks.close();
} catch (Exception e) { String s = e.getMessage(); }
}
Это, конечно, не идеальное решение, но, по крайней мере, ключами можно управлять с помощью утилиты keytool и, что самое важное, ключ не хранится в самом тексте программы. В этом коде есть типичная ошибка, отмеченная в грехе 6, -перехват всех исключений.