Гость
Целевая тема:
Создать новую тему:
Автор:
Форумы / Delphi [игнор отключен] [закрыт для гостей] / CryptAPI (RSA) - работа с парами ключей / 8 сообщений из 8, страница 1 из 1
26.01.2012, 13:11
    #37632681
пивафви
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
CryptAPI (RSA) - работа с парами ключей
Всем доброго времени суток!
Вот, наваял небольшой модуль для шифрования парами ключей RSA. Пример работает, но, как оказалось, не всё в нём так гладко:
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
unit Crypt_RSA;

interface

uses
  Windows, Classes, SysUtils, WCrypt2;

function RSAGenerateKeys(var PrivateKey, PublicKey: String): Boolean;
function RSAEncrypt(Source, Key: String): String;
function RSADecrypt(Source, Key: String): String;

implementation

function RSAGenerateKeys(var PrivateKey, PublicKey: String): Boolean;
const
  RSA1024BIT_KEY = $04000000;

var
  RSA: HCRYPTPROV;
  HKeyPair: HCRYPTKEY;
  Pair: TStringStream;
  buflen: DWORD;

  function SetKey(BlobDef: Cardinal; var Key: String): Boolean;
  begin
    Result := Bool(CryptExportKey(HKeyPair, 0, BlobDef, 0, nil, @buflen));
    if Result then
    begin
      Pair.SetSize(buflen);
      Result := Bool(CryptExportKey(HKeyPair, 0, BlobDef, 0, PByte(Pair.Memory), @buflen));
    end;

    Key := Pair.ReadString(buflen);
    Pair.Seek(0, soBeginning);
  end;

begin
  Pair := TStringStream.Create;

  Result := Bool(CryptAcquireContext(@RSA, nil, nil, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT));
  if Result then
    Result := Bool(CryptGenKey(RSA, AT_KEYEXCHANGE, RSA1024BIT_KEY or CRYPT_EXPORTABLE, @HKeyPair));

  if Result then
    Result := SetKey(PRIVATEKEYBLOB, PrivateKey);
  if Result then
    Result := SetKey(PUBLICKEYBLOB, PublicKey);

  CryptDestroyKey(HKeyPair);
  CryptReleaseContext(RSA, 0);
  FreeAndNil(Pair);
end;

function RSAEncrypt(Source, Key: String): String;
var
  KeyPair: TStringStream;
  RSA: HCRYPTPROV;
  HPair: HCRYPTKEY;
  DDataSize, EDataSize: DWORD;

begin
  Result := '';
  KeyPair := TStringStream.Create(Key);
  if CryptAcquireContext(@RSA, nil, nil, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT) then
  try
    if CryptImportKey(RSA, PByte(KeyPair.Memory), KeyPair.Size, 0, 0, @HPair) then
    try
      EDataSize := SizeOf(Source);
      if CryptEncrypt(HPair, 0, true, 0, nil, @EDataSize, 0) then
      begin
        Result := Source;
        SetLength(Result, EDataSize);
        DDataSize := Length(Source) * SizeOf(Char);
        if not(CryptEncrypt(HPair, 0, True, 0, PByte(PChar(Result)), @DDataSize, EDataSize)) then
          Result := '';
      end;
    finally
      CryptDestroyKey(HPair);
    end;
  finally
  CryptReleaseContext(RSA, 0);
  end;
  FreeAndNil(KeyPair);
end;

function RSADecrypt(Source, Key: String): String;
var
  KeyPair: TStringStream;
  RSA: HCRYPTPROV;
  HPair: HCRYPTKEY;
  EDataSize: DWORD;

begin
  KeyPair := TStringStream.Create(Key);
  Result := '';
  if CryptAcquireContext(@RSA, nil, nil, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT) then
  try
    if CryptImportKey(RSA, PByte(KeyPair.Memory), KeyPair.Size, 0, 0, @HPair) then
    try
      Result := Source;
      EDataSize := Length(Result);
      if not Bool(CryptDecrypt(HPair, 0, True, 0, PByte(PChar(Result)), @EDataSize)) then
        EDataSize := 0;
      SetLength(Result, EDataSize div SizeOf(Char));
    finally
      CryptDestroyKey(HPair);
    end;
  finally
    CryptReleaseContext(RSA, 0);
  end;
  FreeAndNil(KeyPair);
end;

end.



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

Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
var
  HelloWorld,
  PrivateKey,
  PublicKey: String;

begin
  if RSAGenerateKeys(PrivateKey, PublicKey) then
  begin
     // OK :
    HelloWorld := RSADecrypt(RSAEncrypt('Hello, world!', PublicKey), PrivateKey);

     // CryptDecrypt fails on encrypt, error code 2148073476 "Bad Length" :
    HelloWorld := RSADecrypt(RSAEncrypt('12345678901234567890123456789012345678901234567890123456789', PublicKey), PrivateKey);

     // CryptDecrypt fails on decrypt, error code 2148073485 "Key does not exist" :
    HelloWorld := RSADecrypt(RSAEncrypt('Hello, world!', PrivateKey), PublicKey);
  end;
end;



Раньше я пользовался FGInt, но под юникодными версиями Delphi он не работает (во всяком случае мне не удалось заставить его работать правильно, чтобы быть совместимым с не-юникодными делфями).
Посему, собственно, вопросы:
- Откуда получить необходимую константу-размер буфера для шифрования?
- Как шифровать приватным ключом, чтобы расшифровывать публичным? (FGInt это поддерживал и я активно пользовался этой возможностью).
Буду рад любой помощи и подсказкам!
...
Рейтинг: 0 / 0
26.01.2012, 17:43
    #37633517
GunSmoker
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
CryptAPI (RSA) - работа с парами ключей
А что вы ожидали? Вы же храните зашифрованный текст в обычной строке - понятно, что при этом ни черта не соблюдается размер буфера (у строк он кратен 2, а вообще-то он должен быть произвольным).

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

Но если уж очень хочется со строками и сразу, то хотя бы так:

Код: pascal
1.
2.
function RSAEncrypt(Source: String; Key: RawByteString): RawByteString;
function RSADecrypt(Source, Key: RawByteString): String;
...
Рейтинг: 0 / 0
27.01.2012, 11:53
    #37634690
shmelina
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
CryptAPI (RSA) - работа с парами ключей
Тут дело не в строке. Ошибка возникает в вызове функции CryptEncrypt(HPair, 0, true, 0, nil, @EDataSize, 0) для определения длины выходного буфера. Входные параметры: дескриптор ключа и длина входного буфера. Тоже сейчас с этим мучаюсь.
...
Рейтинг: 0 / 0
27.01.2012, 12:46
    #37634815
shmelina
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
CryptAPI (RSA) - работа с парами ключей
Удалось выяснить следующее. Шифровать RSA может блоки длиной: ДлинаКлюча - 11 байт.
Если ключ 1024 бита (это 128 байт), то шифровать можно максимум 117 байт (128 -11).

пивафви, вы шифруете 58 символов (скорее всего в кодировке unicode), а это 118 байт.Отсюда и ошибка.

Получается, что нельзя использовать один и тот же буфер как входной и выходной при шифровании, т.к. при попытке шифрования первого блока он затрет исходные данные еще незашифрованного.
...
Рейтинг: 0 / 0
27.01.2012, 13:20
    #37634898
shmelina
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
CryptAPI (RSA) - работа с парами ключей
Правильнее будет шифровать случайным сессионным ключом (созданным с помощью CryptGenKey ), а сессионный ключ уже шифровать ключевой парой.
...
Рейтинг: 0 / 0
27.01.2012, 16:17
    #37635313
shmelina
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
CryptAPI (RSA) - работа с парами ключей
Шифровать приватным ключом нельзя. Только публичным.
...
Рейтинг: 0 / 0
Период между сообщениями больше года.
18.11.2018, 11:39
    #39734744
pivbul
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
CryptAPI (RSA) - работа с парами ключей
Генерирую и экспортирую в файлы приватный и публичный ключи
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
var
pbuf:tstringstream;
buflen:dword;
pass:ansistring;
begin
CryptAcquireContext( @RSA, PWideChar('my_container'), nil, PROV_RSA_FULL, 0 );
CryptGenKey(RSA, AT_KEYEXCHANGE, (1024 shl 16) or CRYPT_EXPORTABLE, @ExchangeKey);
//
pass := 'password';
CryptCreateHash(RSA, CALG_SHA, 0, 0, RSA_Hash);
  try
  CryptHashData(RSA_Hash, PByte(pass), Length(pass), 0);
  CryptSignHash(RSA_Hash, AT_KEYEXCHANGE, nil,0,nil,@buflen);
  finally
  CryptDestroyHash(RSA_Hash);
  end;
//
pbuf := tstringstream.Create;
CryptExportKey(ExchangeKey, 0, PUBLICKEYBLOB, 0, nil, @buflen);
pbuf.SetSize(buflen);
CryptExportKey(ExchangeKey, 0, PUBLICKEYBLOB, 0, PByte(pbuf.Memory), @buflen);
pbuf.Seek(0, soBeginning);
pbuf.SaveToFile('public.key');
pbuf.Free;

pbuf := tstringstream.Create;
CryptExportKey(ExchangeKey, 0, PRIVATEKEYBLOB, 0, nil, @buflen);
pbuf.SetSize(buflen);
CryptExportKey(ExchangeKey, 0, PRIVATEKEYBLOB, 0, PByte(pbuf.Memory), @buflen);
pbuf.Seek(0, soBeginning);
pbuf.SaveToFile('private.key');
pbuf.Free;
//
CryptDestroyKey(ExchangeKey);
CryptReleaseContext( RSA, 0 );
end;



Далее импортирую приватный ключ (публичный внутри него)

Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
var
pbuf:tstringstream;
i,buflen:dword;
begin
if not FileExists(APP_PATH + 'private.key') then Exit;
CryptAcquireContext( @RSA, PWideChar('my_container'), nil, PROV_RSA_FULL, 0 );
pbuf := tstringstream.Create;
pbuf.LoadFromFile('private.key');
buflen := pbuf.Size;
if CryptImportKey(RSA,PByte(pbuf.Memory),buflen,0,0,@ExchangeKey) then
  begin
  pbuf.Free;
  pbuf := tstringstream.Create;
  CryptExportKey(ExchangeKey, 0, PUBLICKEYBLOB, 0, nil, @buflen);
  pbuf.SetSize(buflen);
  CryptExportKey(ExchangeKey, 0, PUBLICKEYBLOB, 0, PByte(pbuf.Memory), @buflen);
  pbuf.Free;
  pbuf := tstringstream.Create;
  CryptExportKey(ExchangeKey, 0, PRIVATEKEYBLOB, 0, nil, @buflen);
  pbuf.SetSize(buflen);
  CryptExportKey(ExchangeKey, 0, PRIVATEKEYBLOB, 0, PByte(pbuf.Memory), @buflen);
  pbuf.Seek(0, soBeginning);
  pbuf.Free;
  CryptDestroyKey(ExchangeKey);
  CryptReleaseContext( RSA, 0 );
  end;
end;



При генерации я вижу:

публичный ключ:
0602000000A40000525341310004000001000100...

приватный ключ:
0702000000A40000525341320004000001000100...

При импорте только приватного ключа (публичный внутри него) я вижу:

публичный ключ:
0602000000A40000525341310004000001000100...

приватный ключ:
0702000000000000000000000000000000000000...

Подскажите, где и что не так делаю? Почему импорт приватного ключа работает (судя по ипортированному публичному) но сам приватный обнулён?
...
Рейтинг: 0 / 0
19.11.2018, 14:08
    #39735136
GunSmoker
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
CryptAPI (RSA) - работа с парами ключей
Навскидку:

1. CryptExportKey экспортирует же в бинарник. Зачем там TStringStream?
2. Флаг CRYPT_VERIFYCONTEXT попробуйте в CryptAcquireContext (последний аргумент).
3. Явно укажите MS_ENHANCED_PROV в CryptAcquireContext (третий аргумент).
4. Замените AT_KEYEXCHANGE на CALG_RSA_KEYX.
...
Рейтинг: 0 / 0
Форумы / Delphi [игнор отключен] [закрыт для гостей] / CryptAPI (RSA) - работа с парами ключей / 8 сообщений из 8, страница 1 из 1
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]