Введение
Доброго времени суток! Пришлось мне по воле судьбы работать с одним оператором, который очень любил шифровать свои данные :) Вроде бы задача казалась пустяковой и вполне обычной — алгоритм RSA популярен и всем хорошо знаком, реализация криптографии есть в .NET — написал пару строк и ваши запросы никто не видит :) Но так я думал только до тех пор, пока не начал писать эти самые пары строк…
Задача
Итак, что мне надо было: подписывать (sign) запросы с нашей стороны, и расшифровывать подписанные запросы, присланные партнером с использованием RSA алгоритма.
Пару слов о подписи: имеется 2 ключа public и private. Абонент А шифрует своим private ключом сообщение и передает абоненту Б. Абонент Б, приняв это сообщение и имея public ключ ( который прислал абонент А) может удостовериться что это сообщение отправил именно абонент А. Вообщем информации про Rsa достаточно, так что на этом останавлюсь.
Средства шифрования
В моем случае, «клиент» использовал популярную программу Open SSL , позволяющую делать много чего с шифрование, в том числе и подписывать данные. Работает программа так: принимает на вход файл, ключ и выдает подписанное сообщение. Верификация работает аналогично: принимает шифрованный файл, public ключ и на выходе в случае успеха, выдает файл с расшифрованным сообщением.
Для реализации RSA подписи в .NET существует очень удобный класс RSACryptoServiceProvider, позволяющий делать шифрование и подписывание. Все бы ничего, пока не оказалось что понятие подписи у этих реализаций весьма разное...:( Пришлось провести ни один час поиска в интернете, но информации особенно не было. Хотя жалобы людей видел часто на несовместимость реализаций. Основной смысл в том, что OpenSSL в подпись вставляет и сами данные (как шифрование), а .NET только подписывает их (что имхо логично). Нашел только 1 библиотеку на C#, которая содержит именно реализацию OpenSSL, но она оказалась платная. Правда там написано основное отличие реализаций:
OpenSSL rsautl is very different. Here's what it does:
1. The input data is not hashed. It must be a small enough amount of data such that it can be padded and signed. PKCS v1.5 padding is always used. The data is not ASN.1 encoded before padding.
2. The PKCS v1.5 padded data is RSA signed and the signature is returned.
Почти отчаявший и даже написав wrapper для OpenSSL, случайно наткнулся на одну хорошую ссылку: проект OPEN SSL NET — т.е. обертка для OpenSSL, а точнее бибилотек которые она использует — libeay32.dll и ssleay32.dll. Но, как оказалось, не все там было очевидно но копать среди C# классов было все же проще, чем реализовывать самому исходники с C :) Пообщавшись немного с автором библиотеки, написал класс-обертку, который содержит методы подписи и верификации, аналогичные OpenSSL.
На всякий случай приведу пример работы с утилитой OpenSSL, чтобы интересующиеся не тратили время на -help :)
Подпись:openssl rsautl -inkey priv.key -in temp.txt -out temp.bin -sign,
Верификация: openssl rsautl -pubin -inkey pub.key -in temp2.bin -out result2.txt -verify
Итак, сам код:
Класс-эмулятор RsaUtl OpenSSL
Думаю комментарии к самому коду излишни. Сам этот класс я включил в сборку OpenSSL.NET, но с тем же успехом его можно использовать и вне ее. Для успешной работы необходимы библиотеки libeay32.dll ,ssleay32.dll ( и если используете вне сборки, то ManagedOpenSsl.dll). В двух словах о классе: загружает ключи в pem формате и далее подписывает/проверяет данные. Сделал перегрузку, чтобы ключ можно было указывать как путь к файл, так и просто набор byte[].
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using OpenSSL;
namespace Rsautl
{
/// <summary>
/// Подпись как в утилите Openssl -rsautl
/// </summary>
public static class Rsautl
{
private static RSA ReadPrivateKey(string path)
{
Stream s = File.Open(path, FileMode.Open);
byte[] pk = new byte[s.Length];
s.Read(pk, 0, pk.Length);
s.Close();
BIO b = new BIO(pk);
return RSA.FromPrivateKey(b);
}
private static RSA ReadPrivateKey(byte[] path)
{
BIO b = new BIO(path);
return RSA.FromPrivateKey(b);
}
private static RSA ReadPubKey(string path)
{
Stream s = File.Open(path, FileMode.Open);
byte[] pk = new byte[s.Length];
s.Read(pk, 0, pk.Length);
s.Close();
BIO b = new BIO(pk);
return RSA.FromPublicKey(b);
}
private static RSA ReadPubKey(byte[] path)
{
BIO b = new BIO(path);
return RSA.FromPublicKey(b);
}
#region подпись
public static byte[] RsaUtlSignData(byte[] data,string privatekeypath)
{
RSA key = ReadPrivateKey(privatekeypath);
return key.PrivateEncrypt(data,RSA.Padding.PKCS1);
}
public static byte[] RsaUtlSignData(byte[] data, byte[] privatekey)
{
RSA key = ReadPrivateKey(privatekey);
return key.PrivateEncrypt(data, RSA.Padding.PKCS1);
}
#endregion
#region проверка подписи
public static byte[] RsaUtlVerifyData(byte[] sign,string pathtopublickey)
{
RSA key = ReadPubKey(pathtopublickey);
return key.PublicDecrypt(sign, RSA.Padding.PKCS1);
}
public static byte[] RsaUtlVerifyData(byte[] sign, byte[] publickey)
{
RSA key = ReadPubKey(publickey);
return key.PublicDecrypt(sign, RSA.Padding.PKCS1);
}
#endregion
}
}
* This source code was highlighted with Source Code Highlighter.
Заключение
Надеюсь, эта статья кому-то окажется полезной и сэкономит Ваше драгоценное время. Только не говорите, что это можно сделать все за 1 минуту — не поверю :) Но если все же есть способ в NET без сторонних библиотек подписывать данные, совместимо с OpenSSL большая просьба написать!!!
PS пожалуйста, аргументируйте оценку статьи ;)
PS2 если кому то будет интересно, могу написать статью как я боролся с OpenPGP =) Там похожая история… :)