Страницы

пятница, 4 февраля 2011 г.

Использование алгоритма AES при помощи библиотеки openssl

Столкнулся с тем, что нет описания интерфейс к алгоритму AES в библиотеке OpenSSL, а несколько русскоязычных статей оказались мало пригодно, по тому простому поводу, что в примерах давали не работающий код. Хотя нельзя отрицать, что там была и полезная информация, которая помогла лучше понять англоязычную документацию на openssl.org .
Собственно функция фишрования :
unsigned long int do_crypt(unsigned char *infile,int inlen ,unsigned char * key)
{
// unsigned char key[32]; /* 256- битный ключ */
unsigned char *last_buf;//for last bytes that is
int sum_len=0;
int outlen=0;
EVP_CIPHER_CTX ctx;
const EVP_CIPHER * cipher;
/* Обнуляем структуру контекста */
EVP_CIPHER_CTX_init(&ctx);
/* Выбираем алгоритм шифрования */
cipher = EVP_aes_256_cbc();
/* Инициализируем контекст алгоритма */
EVP_EncryptInit(&ctx, cipher, key, NULL);
/* Шифруем данные */
last_buf=infile;
if(!EVP_EncryptUpdate(&ctx, last_buf, &outlen, infile,inlen ) ) return 0;
sum_len+=outlen;
last_buf=infile+outlen;
if(!EVP_EncryptFinal(&ctx, last_buf, &outlen)) return 0;
sum_len+=outlen;
EVP_CIPHER_CTX_cleanup(&ctx);
return sum_len;//возвращаем длину зашифрованых данных



};
Собственно функция принимает указатель на строку, которую надо зашифровать, длинну строки и ключ ( 256 бит в данном случае). Данная функция справляется нормально с шифрованием данных до 65 мегабайт, больше не тестировал. Возвращает длину зашифрованных данных, которая может отличаться от исходной длинны.
Функция дешифрования :

int do_decrypt(unsigned char *infile,int inlen,unsigned char * key)
{
unsigned char *last_buf;//for last bytes that is
int outlen,sum_len,temp_len,last;


EVP_CIPHER_CTX ctx;

const EVP_CIPHER * cipher;
/* Обнуляем структуру контекста */
EVP_CIPHER_CTX_init(&ctx);
/* Выбираем алгоритм шифрования */

cipher = EVP_aes_256_cbc();
/* Инициализируем контекст алгоритма */
EVP_DecryptInit(&ctx, cipher, key, NULL);
outlen=temp_len=0;
last_buf=infile;
if(!EVP_DecryptUpdate(&ctx, last_buf, &outlen,infile, inlen)) return 0;
memcpy(infile,last_buf,outlen);
last_buf=infile+outlen;
if(!EVP_DecryptFinal(&ctx, last_buf, &temp_len)) return 0;
memcpy(infile+outlen,last_buf,temp_len);

EVP_CIPHER_CTX_cleanup(&ctx);

return outlen+temp_len;
}


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

*(infile+outlen+temp_len)='\0';

вторник, 18 января 2011 г.

Тестировщику на заметку

Недавно наткнулся на статью про тестирование на http://habrahabr.ru, там кроме
всего прочего был в комментариях тезис :

"Тестирование относительно новая область, теоретическая база отсутствует"
С которым категорически не согласен, во всех аспектах, тестирование появилось вместе с программированием. Дальше вы мне возразите "Но им не занимались профессионально". Ага как же?! Им занимались профессионалы почище нас с вами. Так как в далекие 80-е - 90-е годы программирование было уделом институтов с перфокартами, то соответственно сложным программированием занимались люди владеющие на нормальном уровне "Теорией Вероятности" а так же "Теорией Планирование Экспериментов", которая и по сути является прототипом нынешней теоретической базы тестирования. Разрабатывалось, в частности, данное направление академиком МГУ В.В Налимов.

P.S
Я не ботан- асспирант в университете, не преподователь университета, не был заучкой( у меня порядочно троек есть), хоть и магистр.

P.S.S
Все придумано до нас

вторник, 28 декабря 2010 г.

SSL, openssl и генерация сертификатов

Что бы не забывать
В состав дистрибутива openssl входят скрипты CA.sh и CA.pl (/usr/local/openssl/misc)
создаем корневой сертификат
./CA.sh -newca
генерируем личный ключ и сертификационный запрос сервера
./CA.sh -newreq
и подписываем его своим корневым сертификатом.
./CA.sh -sign
переписываем ключ и сертификат сервера в служебный каталог Apache
cp newreq.pem /usr/local/etc/apache/sslkey/server.key
cp newcert.pem /usr/local/etc/apache/ssl.crt/server.crt
Файл корневого сертификата ./demoCA/cacert.pem необходимо
распространить по клиентским компьютерам.

среда, 22 декабря 2010 г.

Где брать идеи

1. Идеи не появляются от просмотра телевизора.

2. Идеи иногда появляются после прослушивания лекции.

3. Идеи часто появляются во время чтения книги.

4. Хорошие идеи появляются из плохих идей, но только если последние есть в достаточном количестве.

5. Идеи ненавидят конференц-залы, особенно такие конференц-залы, где сохранился опыт критики, личных выпадов или занудства.

6. Идеи возникают, когда сталкиваются непохожие вселенные.

7. Идеи часто стремятся соответствовать ожиданиям. Если люди ожидают идею, то она появится.

8. Идеи боятся экспертов, но восхищаются умом начинающих. Немного осведомленности не помешает.

9. Идеи идут косяками, пока вы не испугаетесь. Уилли Нельсон написал три из своих главных хитов за одну неделю.

10. Идеи приходят благодаря неприятностям.

11. Идеи приходят от нашего эго, и они выходят лучше всего, когда они щедры и самозабвенны.

12. Идеи приходят из природы.

13. Иногда идеи приходят от страха (обычно это в кино), но часто – от уверенности.

14. Полезные идеи приходят, когда вы не спите и находитесь начеку, чтобы не пропустить намек.

15. Однако иногда идеи прокрадываются к нам, когда мы спим и слишком обмякли, чтобы бояться.

16. Идеи замечаешь краем глаза, а иногда – находясь в душе, когда не прилагаешь никаких усилий.

17. Посредственные идеи склонны копировать то, что, похоже, работает как раз в эту минуту.

18. Более крутые идеи перескакивают через посредственные.

19. Идеям не нужен паспорт, и они часто пересекают границы (всякого рода) безнаказанно.

20. Идея должна прийти откуда-то, потому что если она останется там, где есть, и не присоединится к нам здесь, она будет скрытой. А скрытые идеи не превращаются во что-то конкретное, не имеют влияния, не пересекаются с рынком. Они умирают в одиночестве.

Сет Годин...

четверг, 9 декабря 2010 г.

Оптимизация кода

Архитектурно правильный код - это хорошо. Он красиво выглядит, легко читается, все структурировано, а потом оказывается, что код работает долго, а все вроде бы красиво.
Так вот не всегда "правильный" с точки зрения стиля, архитектуры код самый быстрый.
Ну так получилось, так бывает, теория и практика знаете ли. А теперь идеи ускорения.

Идея 1. Назовем ее развертывание цикла.

Код :

my $ref = $dbh->selectall_arrayref(q[SELECT id FROM categories
WHERE c_status='active']);
my (@res);
my $count=10;
foreach my $tmp (@$ref){
my $ref=$dbh->selecall_hashref(qq[SELECT id,title,text,ts
FROM articles
WHERE category=$tmp
ORDER BY id DESC LIMIT $count],'id');
my @r;
push @r,$ref->{$_} foreach( key %$ref);
push @res,\@r;
}



То есть это простой пример вывода статей по 10 штук в каждой категорие. Он универсален, но его можно значительно ускорить потеряв при этом применимость нового алгоритма для некоторых частных случаев. "Развернув" внутренний цикл
мы получим ужасный код, но и более быстрый, избавившись в данном случае аж от 10 условных переходов (циклы это ничто иное как условные переходы) . Получим примерно следующее :
my $ref = $dbh->selectall_arrayref(q[SELECT id FROM categories
WHERE c_status='active']);
my @res;
my $count=10;
foreach my $tmp (@$ref){
my $ref=$dbh->selecall_arrayref(qq[SELECT id,title,text,ts
FROM articles
WHERE category=$tmp
ORDER BY id DESC LIMIT $count],'id');
my @r;
push @r,$ref->[0];
push @r,$ref->[1];
push @r,$ref->[2];
push @r,$ref->[3];
push @r,$ref->[4];
push @r,$ref->[5];
push @r,$ref->[6];
push @r,$ref->[7];
push @r,$ref->[8];
push @r,$ref->[9];
push @res,\@r;
}


Идея 2. Банальное кеширование

Не будем уходить далеко в мир высокого, а продолжим рассматривать пример выше.
Логично предположить, что новые категории добавляются не каждый день, потому от запроса на выборки списка надо избавиться путем сохранения его в памяти. Самый простой способ это Memcached, другой способ рассмотрим ниже.

sub get_cache_connection
{

require Cache::Memcached::Fast;
require Storable;
my $ref=new Cache::Memcached::Fast(
{
servers =>[{ address => '127.0.0.1:11211',weight=>2.5}],
namespace => 'sessions:',
connect_timeout => 0.2,
io_timeout => 0.5,
close_on_error => 1,
compress_threshold =>-1,
ketama_points => 150,
nowait => 1,
hash_namespace => 1,
serialize_methods => [ \&Storable::freeze, \&Storable::thaw ]
}
);

return $ref;

}

my $tabs;
my $md=get_cache_connection();
my $ref=$md->get('categories')) ;
foreach(@$ref){
###some code there

}
Кеширование применимо для данных, которые не часто меняются, при другом подходе может возникнуть проблема, что накладные расходы на работу с кешированием превысят затраты на работу с базой данных. Не забывайте, что mysql тоже кеширует запросы, и в предыдущем примере запрос на выборку категорий по сути не имеет смысла, потому что с огромной вероятностью он будет закеширован mysql. Другое дело, если вы генерируете из категорий свой особый html код, вот его можно и за кешировать.


Идея 3. Развертывание функций.


Думаю вы уже догадались в чем фишка. Рассмотрим предыдущие примеры, только добавим внутрь цикла функцию форматирования даты - ts.
my $ref = $mem_cache->get('categories')) ;
my @res;
my $count=10; foreach my $tmp (@$ref){
my $ref=$dbh->selecall_arrayref(qq[SELECT id,title,text,ts
FROM articles
WHERE category=$tmp
ORDER BY id DESC LIMIT $count],'id');
my @r;
format_date(\$ref->[0]->[3]);
push @r,$ref->[0];
format_date(\$ref->[1]->[3]);
push @r,$ref->[1];
format_date(\$ref->[2]->[3]);
push @r,$ref->[2];
format_date(\$ref->[3]->[3]);
push @r,$ref->[3];
format_date(\$ref->[4]->[3]);
push @r,$ref->[4];
format_date(\$ref->[5]->[3]);
push @r,$ref->[5];
format_date(\$ref->[6]->[3]);
push @r,$ref->[6];
format_date(\$ref->[7]->[3]);
push @r,$ref->[7];
format_date(\$ref->[8]->[3]);
push @r,$ref->[8];
format_date(\$ref->[9]->[3]);
push @r,$ref->[9];

push @res,\@r;
}


Заметка кстати, профайлинг Perl показал, что быстрее передавать параметры в функцию по значению, а не по указателю( то есть не так как здесь ;) ). И заменяем все format_date на их код.

my $ref = $mem_cache->get('categories')) ;
my @res;
my $count=10;
foreach my $tmp (@$ref){
my $ref=$dbh->selecall_arrayref(qq[SELECT id,title,text,ts
FROM articles
WHERE category=$tmp
ORDER BY id DESC LIMIT $count],'id');
my @r;

$ref->[0]->[3]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[0]->[3]="$3.$2.$1";
push @r,$ref->[0];
$ref->[1]->[3]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[1]->[3]="$3.$2.$1";


push @r,$ref->[1];
$ref->[2]->[3]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[2]->[3]="$3.$2.$1";
push @r,$ref->[2];
$ref->[3]->[3]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[3]->[3]="$3.$2.$1";
push @r,$ref->[3];
$ref->[4]->[3]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[4]->[3]="$3.$2.$1";
push @r,$ref->[4];
$ref->[5]->[3]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[5]->[3]="$3.$2.$1";
push @r,$ref->[5];
$ref->[6]->[3]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[6]->[3]="$3.$2.$1";
push @r,$ref->[6];
$ref->[7]->[3]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[7]->[3]="$3.$2.$1";
push @r,$ref->[7];
$ref->[1]->[8]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[1]->[8]="$3.$2.$1";
push @r,$ref->[8];
$ref->[1]->[9]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[1]->[9]="$3.$2.$1";
push @r,$ref->[9];
push @res,\@r;
}

В итоге мы види ужасный неудобоворимый код, который вызывает рвотные рефлексы при одном своем виде. Дальше агрументов много
1) А что делать, если полей в таблице дофига, и все надо форматировать, это ж "каша"
2) А если надо увеличить/уменьшить количество статей, снова лезть в код!
И так далее.. И так далее...Все эти вопросы можно решить при помощи "админки". А админка есть у любого уважающего себя сайта статусом выше, персональная страничка.
На нужно при изменение нужных вам параметров сделать генерацию нужного вам кода.
Например добавить изменение количества выводимых статей с 10 на 5( вполне обычная функция для администраторского интерфейса). Помещаем функцию обработки вывода статей в отдельный модуль например "/lib/Article.pm". Даем права на работу с этим файлом пользователю от которого работает наш скрипт
( ну или обычные авось chmod 777 /lib/Artcle.pm).
ну и в функцию изменения количества статей пишем примерно следующее:
open(Fl,">document_root/lib/Article");

print FL, q{
package lib::Artcle;
use strict;
use base qw[Exporter];
use SiteDB;##connecting to the database $dbh
our @EXPORT = qw(
list
);





sub list{
my $ref = $mem_cache->get('categories')) ;
my @res;
my $count=}.$new_count_value.q{;

foreach my $tmp (@$ref){
my $ref=$dbh->selecall_arrayref(qq[SELECT id,title,text,ts
FROM articles
WHERE category=$tmp
ORDER BY id DESC LIMIT $count],'id');
my @r;

$ref->[0]->[3]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[0]->[3]="$3.$2.$1";
push @r,$ref->[0];
$ref->[1]->[3]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[1]->[3]="$3.$2.$1";


push @r,$ref->[1];
$ref->[2]->[3]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[2]->[3]="$3.$2.$1";
push @r,$ref->[2];
$ref->[3]->[3]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[3]->[3]="$3.$2.$1";
push @r,$ref->[3];
$ref->[4]->[3]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[4]->[3]="$3.$2.$1";
push @r,$ref->[4];
$ref->[5]->[3]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[5]->[3]="$3.$2.$1";
push @r,$ref->[5];
$ref->[6]->[3]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[6]->[3]="$3.$2.$1";
push @r,$ref->[6];
$ref->[7]->[3]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[7]->[3]="$3.$2.$1";
push @r,$ref->[7];
$ref->[1]->[8]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[1]->[8]="$3.$2.$1";
push @r,$ref->[8];
$ref->[1]->[9]=~/(\d{1,4})-(\d{1,2})-(\d{1,2})/;
$ref->[1]->[9]="$3.$2.$1";
push @r,$ref->[9];
push @res,\@r;
\}

return \@res;
\}

1;
};
close(FL);
Ну и далее можем использовать этот модуль у себя в скрипте обычным способом :



sub some_sub{
my $self=shift;
require lib::Article;
my $ref=lib::Article::list();
###some code there
####
}


Всем удачи.

p.s
При помощи генерации кода можно избавиться вообще от последнего цикла обхода категорий.

пятница, 3 декабря 2010 г.

perl, utf8,киррилица и mysql

Вы похвально решили перевести вашу базу данных в utf-8...
Везеде поставили в mysql :
set names utf8;
set charset utf8;

в таблицах проставили новую коддировку по умолчанию,
default charset utf8



Начинаете добавлять из скрипта данные, а вместо них пустые строки в базе данных....
Первое скрипту выставляем коддировку utf8 это вы и сами догадались, а второе
пишем в начале скрипта :
use utf8;
Теперь все работает...Ошибка неприятная, потому perl молчал как партизан, и просто
все менял на пустую строку

понедельник, 29 ноября 2010 г.

Построение множеств в реляционной базе данных

Человеческий мозг интересная штука, особенно интересно наблюдать, как ты работаешь, напряженно думаешь, строишь огромные схемы, чтоб решить какую-то задачу, а потом вдруг озарение и в результате 50 строк предыдущего кода заменяются на 10 нового. Данная заметка родилась примерно так. Поговорим об организации простых математических множест в реляционной базе данных.
Множество это набор элементов, если по простому. Первое, что нужно нам иметь возможность делать - это получать список элементов входящих в множество.

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

create table crosses( p_key1 int(11), p_key2 int(11) )
Где p_key1 и p_key2 соответственно номера деталей, которые эквивалентны. Если "деталь1" может заменить "деталь2", а "деталь2" может заменить "деталь3", тогда логично предпложить, что "деталь1" может заменить "деталь3", это называется транзитивностью, это не обязательная характеристика элементов множеств, но в нашем примере имеет место. Собственно для эти трех деталей потребуется как минимум две записи. Чтобы охарактеризовать связь между всеми тремя, но это теория. А на практике, чтобы не путаться в запросах, потому что таких деталей может быть больше десятка, вроде бы стало удобно для каждой записи хранить ее кросы. То есть для примера из трех деталей мы получили бы следующий вид :
p_key1 p_key2
"деталь1" , "деталь2"
"деталь1" , "деталь3"
"деталь2" , "деталь1"
"деталь2" , "деталь3"
"деталь3" , "деталь2"
"деталь3" , "деталь1"
Уже настараживает...А теперь введем еще пару факторов такие как
1) Оператор ошибается, и добавляет неправильные элементы в существующие множества
2) Множества эквивалентных элементов всегда пополняется.
И теперь сразу видна неповоротливость данной схемы, что порадило априори кучу ошибок в логике работы, которые подтвердились на практике. Да и количество записей растет со скоростью n*(n-1), где n - количество деталей.
Второе решение сильно напоминающее экзоскелет. Написать переделать таблицу экивалентных деталей на манер экселя то есть :

create table crosses( p_key1 int(11), p_key2 int(11),p_key3 int(11), p_key4 int(11) .... )
Где p_key1, p_key2,p_key3 , эквивалентные детали. Размерность такой таблицы вызывает сомнения, потому что эквивалентных деталей может быть просто дикое количество, даже цифра в 100 уже вызывает сильные опасения. Приведем минусы
1) Добавление одного элемента уже в существующее множество приведет к операции сравнения по ста столбцам в худшем случае.
2) не будем же мы делать в самом деле таблицу из ста столбцов, мы напишем класс или библиотеку, которая работает с несколькими таблицами подобного типа, это нам позволит вроде бы избежать ограничения с размерностью.
3) явно видно что писать придеться снова много, ошибки неизбежны...

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


create table crosses(p_set int(11), p_key int(11) )
Где p_set уникальный идентификатор множества. Алгоритм работы прост. Мы нашли "деталь1", которая эквавалента другой "детале2". Мы начинаем поиск в таблице

SELECT p_set as p_set1 FROM crosses WHERE p_key='деталь1'

SELECT p_set as p_set2 FROM crosses WHERE p_key='деталь2'

Если первый и второй запрос дал результат, то мы на, например, заменяем p_set1 на p_set2 соответственно . То есть произошло слияние множеств.

UPDATE crosses SET p_set= $p_set1 WHERE p_set=$p_set2


Если какой из запросов дал результат, например нам стало известно $p_set2 то добавление нового свойства сводится к

INSERT INTO crosses(p_key,p_set) VALUES('деталь1',$p_set2)

Если оба запроса не дали результата, то создаем новый идентификатор например

LOCK TABLE crosses WRITE;

SELECT max(p_set)+1 FROM crosses
INSERT INTO crosses(p_set,p_key) VALUES($new_set,'деталь1'), ($new_set,'деталь2')
UNLOCK TABLES


Выборка всех элементов множества, в которое входит данный элемент осуществляется
в итоге всегда в фиксированое количество опеаций - две. Например дла "деталь1"

SELECT p_set as s FROM crosses WHERE p_key='деталь1'

SELECT p_key FROM crosses WHERE p_set=$s


P.S
можно и одним запросом...Но так нагляднее