Имя: Пароль:
1C
1C 7.7
v7: Крипто Про и 1С 7.7
0 Лохматый
 
12.05.13
04:45
Нужно файл зашифровать и наложить ЭЦП из 1с, куда копать - направьте на путь истинный
1 Zamestas
 
12.05.13
05:46
(0) Гугли CryptoAPI
2 Лохматый
 
12.05.13
06:45
Не хочу изобретать велосипед ..может кто подскажет где посмотреть готовое решение?
3 DMLangepas
 
12.05.13
08:40
зашифровать и наложить ЭЦП
да вы монстр батенька завязывайте накладывать все друг на друга..

Для чего??? для передачи этого файла? по каким каналам связи?
4 DMLangepas
 
12.05.13
08:42
КриптоПро платный.
VipNet без платный.
они тебе помогут в этом.
устанавливай их и с помощью клиента или что там у тебя есть подписываешь и шифруешь.
5 Лохматый
 
12.05.13
09:24
завязывайте накладывать все друг на друга??? ..ну давай по другому, чтоб без сарказма, -подписать и зашифровать что бы получить файл *.sig.enc

Крипто Про платный? ну спасибо! вообще то такого вопроса не задавал :) все на компьторе все работает
файл формируются в 1с
потом посредством криптоарма подписываются и шифруются так вот этот момент требуется исключить чтобы из 1с сразу получить файл зашифрованный
6 Лохматый
 
12.05.13
09:32
Нужно получить результат выполнения команды "подписать и зашифровать" криптоарма
Полагаю что нужно продолжить
oPKCS7Message = СоздатьОбъект("DigtCrypto.PKCS7Message")
7 Лохматый
 
12.05.13
13:01
Ну кто поможет??
8 Aleksey
 
12.05.13
13:03
(5) да платный. по умолчанию демо доступ на 30 дней
9 Лохматый
 
12.05.13
13:09
..да куплен уже ..к задаче никакого отношения это не имеет - шифровать хочу из 1с
10 Aleksey
 
12.05.13
13:10
Шифрование и защита информации в 1С:Предприятие 7.7/8.0/8.1/8.2
by 30. мая 2009 08:58

Статья описывает, как используя богатые возможности .Net Framework реализовать алгоритмы в 1С:Предприятие. Примеры показаны с применением компонента Elisy .Net Bridge v.2.1, который распространяется для разработчиков бесплатно.

(с) http://www.richmedia.us/post/2009/05/shifrovaniye-zashita-informacii-1c-predpriyatiye.aspx
11 Aleksey
 
12.05.13
13:13
12 Андрюха
 
12.05.13
13:15
(0) Позвонить в КриптоПро и спросить, не?
13 Jaap Vduul
 
12.05.13
15:37
14 Лохматый
 
12.05.13
15:52
(11)(13)спасибо пытаюсь разобраться и переводить на 1с
15 DMLangepas
 
12.05.13
16:46
(14) а гуглить не?
А просто сделать неформализованный обмен через 1с?
16 Лохматый
 
12.05.13
17:09
(15) гуглил возможно решение здесь
http://infostart.ru/public/156973/ но нет возможности получить эту обработку ..буду признателен если кто выложит ну хотя бы нужный фрагмент кода    больше не гулится
какая разница формализованный неформализованный если обмен между разными организациями посредством  пересылки файлов, подписанных и зашифрованных средствами Крипто Про ..или я не понял предложение
17 DMLangepas
 
12.05.13
17:48
(16) хорошо.
Мне интересно для чего его шифруешь?
Вот зашифровал файл, а как ты его передашь? по электронке?)))
18 Лохматый
 
12.05.13
18:00
(17) ну нравиться мне ход твоих мыслей..
именно так - по э/п  
отвечаю на следующий вопрос - факт передачи там и фиксируется
..не мной придумано
19 oleg_km
 
12.05.13
18:02
Оба варианта и с капикомом и 8.2, все тестировались только на 8.2

Если Истина Тогда
   обКрипто = Новый МенеджерКриптографии("", "", 75);
   //Сертификаты = обКрипто.ПолучитьХранилищеСертификатов().ПолучитьВсе();

   мсСерт = обКрипто.ПолучитьХранилищеСертификатов().НайтиПоСубъекту("Имя");
   Серт = Неопределено;
   Для каждого текСерт Из мсСерт Цикл
       Если текСерт.ДатаОкончания > ТекущаяДата() Тогда
           Серт = текСерт;
           Прервать;
       КонецЕсли;    
   КонецЦикла;
   
   Если Серт = Неопределено Тогда
       Сообщить("Нет сертификатов");
       Возврат;
   КонецЕсли;
   
//    обКрипто.ВключениеСертификатовВПодпись = РежимВключенияСертификатовКриптографии.НеВключать;
   обКрипто.ВключениеСертификатовВПодпись = РежимВключенияСертификатовКриптографии.ВключатьСертификатСубъекта;
   
   обКрипто.Подписать("C:\Temp\Crypto\oleg.txt", "C:\Temp\Crypto\oleg.sig", Серт);
   
   обКрипто1 = Новый МенеджерКриптографии("", "", 75);
   резСерт = Серт;
   обКрипто1.ПроверитьПодпись("C:\Temp\Crypto\oleg.txt", "C:\Temp\Crypto\oleg.sig", резСерт);
КонецЕсли;    
   
Если Ложь Тогда
   обХранСерт = Новый COMОбъект("CAPICOM.Store");
   обХранСерт.Open(2);
   
   мсСерт = обХранСерт.Certificates;
   Серт = Неопределено;
   Для каждого текСерт Из мсСерт Цикл
       Если текСерт.ValidToDate > ТекущаяДата() И текСерт.SerialNumber = "1BB3B39E000000002E6D" Тогда
           Серт = текСерт;
           Прервать;
       КонецЕсли;    
   КонецЦикла;
   
   Если Серт = Неопределено Тогда
       Сообщить("Нет сертификатов");
       Возврат;
   КонецЕсли;
   
   обКрипто = Новый COMОбъект("CAPICOM.Signer");
   обКрипто.Certificate = Серт;
   обКрипто.Options = 2;
   
   обКрипто1 = Новый COMОбъект("CAPICOM.SignedData");
   обКрипто1.Content  = "1234567890";
   
   Рез = обКрипто1.Sign(обКрипто, Истина, 1);
//    Base64Значение(Рез).Записать("C:\Temp\Crypto\oleg.sig1");
   
   обКрипто2 = Новый COMОбъект("CAPICOM.SignedData");
   обКрипто2.Content  = "1234567890";
   обКрипто2.Verify(Рез, Истина, 1);
КонецЕсли;
20 Лохматый
 
12.05.13
18:34
(19) благодарю
21 Dolly_EV
 
13.05.13
03:54
Вот кусок кода из http://infostart.ru/public/156973/
касаемо подписи и шифрования

//======================================================================
Функция ПолучитьПредставлениеСертификата(ТекСубъект)
   ПредставлениеСертификата="";
   СубъектИмя=""; СубъектДолжность=""; СубъектОрганизация=""; СубъектОрганизацияИНН=""; СубъектОрганизацияМестонахождение="";
   
   СписокСтр=СтрЗаменить(ТекСубъект,", ",РазделительСтрок);
   Для НомСтр=1 По СтрКоличествоСтрок(СписокСтр) Цикл
       ТекПараметр="";
       ТекЗначение="";
       ТекСтр=СокрЛП(СтрПолучитьСтроку(СписокСтр,НомСтр));
       ПозРавно=Найти(ТекСтр,"=");
       Если ПозРавно>0 Тогда
           ТекПараметр=СокрЛП(Лев(ТекСтр,ПозРавно-1));
           ТекЗначение=СокрЛП(Сред(ТекСтр,ПозРавно+1));
       КонецЕсли;
       
       Если ТекПараметр="CN" Тогда
           СубъектИмя=ТекЗначение;
       ИначеЕсли ТекПараметр="T" Тогда
           СубъектДолжность=ТекЗначение;
       ИначеЕсли ТекПараметр="O" Тогда
           Если (Лев(ТекЗначение,1)="""") И (Прав(ТекЗначение,1)="""") Тогда
               ТекЗначение=Сред(ТекЗначение,2,СтрДлина(ТекЗначение)-2);
           КонецЕсли;
           ТекЗначение=СтрЗаменить(ТекЗначение,"""""","""");
           СубъектОрганизация=ТекЗначение;
       ИначеЕсли ТекПараметр="L" Тогда
           СубъектОрганизацияМестонахождение=ТекЗначение;
       ИначеЕсли (ТекПараметр="ИНН") ИЛИ (ТекПараметр="INN") ИЛИ (ТекПараметр="OID.1.2.643.3.131.1.1") Тогда
           СубъектОрганизацияИНН="ИНН "+ТекЗначение;
       КонецЕсли;
       
   КонецЦикла;
   
   ПредставлениеСертификата=СубъектОрганизация+" "+СубъектОрганизацияМестонахождение+" "+СубъектОрганизацияИНН+", "+СубъектДолжность+" "+СубъектИмя;
   
   Возврат ПредставлениеСертификата;
КонецФункции

//======================================================================
Функция ПодписатьФайл(ИмяФайла,ВыбСертификат,ИмяВыхФайла)
   
   Попытка
       JS=СоздатьОбъект("MSScriptControl.ScriptControl");
       JS.Language="jscript";
       JS.Timeout=-1;
   Исключение
       ТекстОшибки=ОписаниеОшибки();
       Сообщить("Не удалось создать объект MSScriptControl.ScriptControl","!");
       Сообщить("Описание ошибки: "+ТекстОшибки,"!");
       Возврат 0;
   КонецПопытки;
   
   Попытка
       
       СтрКода="function SignFile(FileName,Cert,OutFileName)
       |{
       |   InStream=new ActiveXObject(""ADODB.Stream"");
       |   InStream.Type=1; // binary data
       |   InStream.Mode=3; // read/write
       |   InStream.Open();
       |   InStream.LoadFromFile(FileName);
       |   InData=InStream.Read(-1);
       |
       |   Signer=new ActiveXObject(""CAPICOM.Signer"");
       |   Signer.Certificate=Cert;
       |   Signer.Options=2; // CAPICOM_CERTIFICATE_INCLUDE_END_ENTITY_ONLY
       |   SignedData=new ActiveXObject(""CAPICOM.SignedData"");
       |   SignedData.Content=InData;
       |   OutSignedData=SignedData.Sign(Signer,0,1);
       |
       |   OutStream=new ActiveXObject(""ADODB.Stream"");
       |   OutStream.Type=2; // text data
       |   OutStream.Mode=3; // read/write
       |   OutStream.Open();
       |   OutStream.WriteText(OutSignedData);
       |   OutStream.SaveToFile(OutFileName,2);
       |   OutStream.Close();
       |
       | return(0);
       |}
       |";
   
   JS.AddCode(СтрКода);
   Рез=JS.Modules("Global").CodeObject.SignFile(ИмяФайла,ВыбСертификат,ИмяВыхФайла);
   Исключение
       ТекстОшибки=ОписаниеОшибки();
       Сообщить("Произошла ошибка при подписи файла!","!");
       Сообщить("Описание ошибки: "+ТекстОшибки,"!");
       Возврат 0;
   КонецПопытки;
   Возврат 1;
КонецФункции

//======================================================================
Функция УпаковатьФайл(ИмяФайла,ИмяВыхФайла)
   
   КоротИмяФайла=ФС.НайтиПервыйФайл(ИмяФайла);
   Если ПустоеЗначение(КоротИмяФайла)=1 Тогда
       Сообщить("Не найден файл "+ИмяФайла,"!");
       Возврат 0;
   КонецЕсли;
   ИмяКат=Лев(ИмяФайла,СтрДлина(ИмяФайла)-СтрДлина(КоротИмяФайла));
   
   Попытка
       BinData=СоздатьОбъект("BinaryData");
   Исключение
       ТекстОшибки=ОписаниеОшибки();
       Сообщить("Не удалось создать объект BinaryData","!");
       Сообщить("Описание ошибки: "+ТекстОшибки,"!");
       Возврат 0;
   КонецПопытки;
   
   Попытка
       
       ФС.УстТекКаталог(ИмяКат);
       BinData.pkZip(""""+ИмяВыхФайла+""" """+КоротИмяФайла+"""");
       BinData.Закрыть();
       
   Исключение
       ТекстОшибки=ОписаниеОшибки();
       Сообщить("Произошла ошибка при упаковке файла!","!");
       Сообщить("Описание ошибки: "+ТекстОшибки,"!");
       Возврат 0;
   КонецПопытки;
   Возврат 1;
КонецФункции

//======================================================================
Функция ЗашифроватьФайл(ИмяФайла,ВыбПолучатели,ИмяВыхФайла)
   
   Попытка
       JS=СоздатьОбъект("MSScriptControl.ScriptControl");
       JS.Language="jscript";
       JS.Timeout=-1;
   Исключение
       ТекстОшибки=ОписаниеОшибки();
       Сообщить("Не удалось создать объект MSScriptControl.ScriptControl","!");
       Сообщить("Описание ошибки: "+ТекстОшибки,"!");
       Возврат 0;
   КонецПопытки;
   
   Попытка
       
       СтрКода="function CryptFile(FileName,Recipients,OutFileName)
       |{
       |   InStream=new ActiveXObject(""ADODB.Stream"");
       |   InStream.Type=1; // binary data
       |   InStream.Mode=3; // read/write
       |   InStream.Open();
       |   InStream.LoadFromFile(FileName);
       |   InData=InStream.Read(-1);
       |
       |   EnvelopedData=new ActiveXObject(""CAPICOM.EnvelopedData"");
       |   EnvelopedData.Content=InData;
       |   for (var i=1; i<=Recipients.Count; i++) {EnvelopedData.Recipients.Add(Recipients.Item(i));}
       |   OutEnvelopedData=EnvelopedData.Encrypt(1);
       |
       |   OutStream=new ActiveXObject(""ADODB.Stream"");
       |   OutStream.Type=2; // text data
       |   OutStream.Mode=3; // read/write
       |   OutStream.Open();
       |   OutStream.WriteText(OutEnvelopedData);
       |   OutStream.SaveToFile(OutFileName,2);
       |   OutStream.Close();
       |
       | return(0);
       |}
       |";
   
   JS.AddCode(СтрКода);
   Рез=JS.Modules("Global").CodeObject.CryptFile(ИмяФайла,ВыбПолучатели,ИмяВыхФайла);
   Исключение
       ТекстОшибки=ОписаниеОшибки();
       Сообщить("Произошла ошибка при шифровании файла!","!");
       Сообщить("Описание ошибки: "+ТекстОшибки,"!");
       Возврат 0;
   КонецПопытки;
   Возврат 1;
КонецФункции

//======================================================================
Процедура БезопасноУдалитьФайл(ИмяФайла);
   
   Попытка
       ФС.УдалитьФайл(ИмяФайла);
   Исключение
   КонецПопытки;
   
КонецПроцедуры

//======================================================================
Процедура ПодписатьЗашифроватьФайл(ИмяФайла,ПоказыватьНастройки=1)
   
   ИмяФайлаПодписи=ИмяФайла+".sig";
   ИмяФайлаАрхива=ИмяФайлаПодписи+".zip";
   ИмяФайлаЗашифр=ИмяФайлаАрхива+".enc";
   
   ТекИНН=Сред(КороткоеИмяФайла,4,12);
   ПозПодч=Найти(ТекИНН,"_");
   Если (ПозПодч>0) Тогда
       ТекИНН=Лев(ТекИНН,ПозПодч-1);
   КонецЕсли;
   
   ПрефиксДекл=Лев(КороткоеИмяФайла,3);
   Если (ПрефиксДекл="R1_") ИЛИ (ПрефиксДекл="R2_") Тогда
       // розничные
       ПравильноеЧислоПолучателей=2;
       ИмяНастроек="ПодписьРознАлкоДеклараций_"+ТекИНН;
   Иначе
       ПравильноеЧислоПолучателей=1;
       ИмяНастроек="ПодписьАлкоДеклараций_"+ТекИНН;
   КонецЕсли;
   
   // CAPICOM constants
   CAPICOM_CURRENT_USER_STORE=2;
   CAPICOM_MY_STORE="My";
   CAPICOM_OTHER_STORE="AddressBook";
   CAPICOM_STORE_OPEN_READ_ONLY=0;
   CAPICOM_CERTIFICATE_FIND_TIME_VALID=9;
   
   ТекДата=ТекущаяДата();
   
   СохранённыйОтпечатокДекларанта="";
   СохранённыеОтпечаткиПолучателей=СоздатьОбъект("СписокЗначений");
   // восстанавливаем настройки
   Попытка
       НастройкиПодписи=ВосстановитьЗначение(ИмяНастроек);
       Если ТипЗначенияСтр(НастройкиПодписи)="СписокЗначений" Тогда
           СохранённыйОтпечатокДекларанта=НастройкиПодписи.Получить("ОтпечатокДекларанта");
           СохранённыеОтпечаткиПолучателей=НастройкиПодписи.Получить("ОтпечаткиПолучателей");
           Если ТипЗначенияСтр(СохранённыеОтпечаткиПолучателей)<>"СписокЗначений" Тогда
               СохранённыеОтпечаткиПолучателей=СоздатьОбъект("СписокЗначений");
           КонецЕсли;
       КонецЕсли;
   Исключение
       
   КонецПопытки;
   
   СписокНашихСертификатов=СоздатьОбъект("СписокЗначений");
   СписокСертификатовПолучателей=СоздатьОбъект("СписокЗначений");
   ВыбСертификатЭЦП="";
   
   Попытка
       // формируем список наших сертификатов
       Store=CreateObject("CAPICOM.Store");
       Store.Open(CAPICOM_CURRENT_USER_STORE,CAPICOM_MY_STORE,CAPICOM_STORE_OPEN_READ_ONLY);
       Certs=Store.Certificates;
       ValidCerts=Certs.Find(CAPICOM_CERTIFICATE_FIND_TIME_VALID,ТекущаяДата(),-1);
       Для CertNum=1 По ValidCerts.Count Цикл
           CurCert=ValidCerts.Item(CertNum);
           Thumbprint=CurCert.Thumbprint;
           SerialNumber=CurCert.SerialNumber;
           SubjectName=CurCert.SubjectName;
           ValidFromDate=CurCert.ValidFromDate;
           ValidToDate=CurCert.ValidToDate;
           
           ПредставлениеСертификата=ПолучитьПредставлениеСертификата(SubjectName);
           ПредставлениеСертификата=ПредставлениеСертификата+", с "+Формат(ValidFromDate,"Д ДДММГГГГ")+" по "+Формат(ValidToDate,"Д ДДММГГГГ");
           СписокНашихСертификатов.ДобавитьЗначение(CurCert,ПредставлениеСертификата);
           Если (Thumbprint=СохранённыйОтпечатокДекларанта) Тогда
               ВыбСертификатЭЦП=CurCert;
           КонецЕсли;
           
       КонецЦикла;
   Исключение
       ТекстОшибки=ОписаниеОшибки();
       Сообщить("Произошла ошибка при работе с хранилищем сертификатов! (Возможно, не установлен модуль CAPICOM)","!");
       Сообщить("Описание ошибки: "+ТекстОшибки,"!");
       Возврат;
   КонецПопытки;
   
   Если (ПоказыватьНастройки=1) Тогда
       РезВыбора=СписокНашихСертификатов.ВыбратьЗначение(ВыбСертификатЭЦП,"Укажите сертификат Декларанта");
       Если РезВыбора<>1 Тогда
           Возврат;
       КонецЕсли;
   Иначе
       Если (ПустоеЗначение(ВыбСертификатЭЦП)=1) Тогда
           Предупреждение("Сертификат Декларанта не выбран или отсутствует в списке действительных сертификатов");
           Возврат;
       КонецЕсли;
   КонецЕсли;
   
   СохранённыйОтпечатокДекларанта=ВыбСертификатЭЦП.Thumbprint;
   
   Попытка
       // формируем список получателей
       Store.Open(CAPICOM_CURRENT_USER_STORE,CAPICOM_OTHER_STORE,CAPICOM_STORE_OPEN_READ_ONLY);
       Certs=Store.Certificates;
       ValidCerts=Certs.Find(CAPICOM_CERTIFICATE_FIND_TIME_VALID,ТекущаяДата(),-1);
       Для CertNum=1 По ValidCerts.Count Цикл
           CurCert=ValidCerts.Item(CertNum);
           Thumbprint=CurCert.Thumbprint;
           SerialNumber=CurCert.SerialNumber;
           SubjectName=CurCert.SubjectName;
           ValidFromDate=CurCert.ValidFromDate;
           ValidToDate=CurCert.ValidToDate;
           
           ПредставлениеСертификата=ПолучитьПредставлениеСертификата(SubjectName);
           ПредставлениеСертификата=ПредставлениеСертификата+", с "+Формат(ValidFromDate,"Д ДДММГГГГ")+" по "+Формат(ValidToDate,"Д ДДММГГГГ");
           СписокСертификатовПолучателей.ДобавитьЗначение(CurCert,ПредставлениеСертификата);
           Если (СохранённыеОтпечаткиПолучателей.НайтиЗначение(Thumbprint)>0) Тогда
               СписокСертификатовПолучателей.Пометка(СписокСертификатовПолучателей.РазмерСписка(),1);
           КонецЕсли;
           
       КонецЦикла;
   Исключение
       ТекстОшибки=ОписаниеОшибки();
       Сообщить("Произошла ошибка при работе с хранилищем сертификатов!","!");
       Сообщить("Описание ошибки: "+ТекстОшибки,"!");
       Возврат;
   КонецПопытки;
   
   Если (ПоказыватьНастройки=1) Тогда
       РезВыбора=СписокСертификатовПолучателей.ОтметитьЗначения(,"Отметьте сертификаты получателей");
       Если РезВыбора<>1 Тогда
           Возврат;
       КонецЕсли;
   КонецЕсли;
   
   ВыбПолучатели=CreateObject("CAPICOM.Certificates");
   СохранённыеОтпечаткиПолучателей=СоздатьОбъект("СписокЗначений");
   Для НомСерт=1 По СписокСертификатовПолучателей.РазмерСписка() Цикл
       Если (СписокСертификатовПолучателей.Пометка(НомСерт)=1) Тогда
           CurCert=СписокСертификатовПолучателей.ПолучитьЗначение(НомСерт);
           ВыбПолучатели.Add(CurCert);
           СохранённыеОтпечаткиПолучателей.ДобавитьЗначение(CurCert.Thumbprint);
       КонецЕсли;
   КонецЦикла;
   
   Если (СохранённыеОтпечаткиПолучателей.РазмерСписка()=0) Тогда
       Предупреждение("Сертификаты получателей не выбраны или отсутствуют в списке действительных сертификатов");
       Возврат;
   ИначеЕсли (СохранённыеОтпечаткиПолучателей.РазмерСписка()<ПравильноеЧислоПолучателей) Тогда
       Предупреждение("Выбрано менее "+ПравильноеЧислоПолучателей+" сертификатов получателей");
   ИначеЕсли (СохранённыеОтпечаткиПолучателей.РазмерСписка()>ПравильноеЧислоПолучателей) Тогда
       Предупреждение("Выбрано более "+ПравильноеЧислоПолучателей+" сертификатов получателей");
   КонецЕсли;
   
   // сохраняем настройки
   НастройкиПодписи=СоздатьОбъект("СписокЗначений");
   НастройкиПодписи.Установить("ОтпечатокДекларанта",СохранённыйОтпечатокДекларанта);
   НастройкиПодписи.Установить("ОтпечаткиПолучателей",СохранённыеОтпечаткиПолучателей);
   СохранитьЗначение(ИмяНастроек,НастройкиПодписи);
   
   // подписываем и шифруем файл
   РезУпак=0; РезШифр=0;
   РезПодписи=ПодписатьФайл(ИмяФайла,ВыбСертификатЭЦП,ИмяФайлаПодписи);
   Если (РезПодписи=1) Тогда
       РезУпак=УпаковатьФайл(ИмяФайлаПодписи,ИмяФайлаАрхива);
       Если (РезУпак=1) Тогда
           РезШифр=ЗашифроватьФайл(ИмяФайлаАрхива,ВыбПолучатели,ИмяФайлаЗашифр);
       КонецЕсли;
       
       БезопасноУдалитьФайл(ИмяФайлаАрхива);
       
   КонецЕсли;
   
   БезопасноУдалитьФайл(ИмяФайлаПодписи);
   
   Если (РезШифр=1) Тогда
       Предупреждение("Файл успешно сформирован");
   КонецЕсли;
   
КонецПроцедуры
22 Лохматый
 
13.05.13
05:25
(21) ну знал же что нефик велик изобретать ..то на что хотел взглянуть ..СПАСИБО!
23 Ковычки
 
13.05.13
07:45
все значительно просче
24 Лохматый
 
13.05.13
11:10
(23) давай просче
Требовать и эффективности, и гибкости от одной и той же программы — все равно, что искать очаровательную и скромную жену... по-видимому, нам следует остановиться на чем-то одном из двух. Фредерик Брукс-младший