powered by simpleCommunicator - 2.0.61     © 2026 Programmizd 02
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Форумы / Delphi [игнор отключен] [закрыт для гостей] / CryptAPI (RSA) - работа с парами ключей
8 сообщений из 8, страница 1 из 1
CryptAPI (RSA) - работа с парами ключей
    #37632681
пивафви
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Всем доброго времени суток!
Вот, наваял небольшой модуль для шифрования парами ключей 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
CryptAPI (RSA) - работа с парами ключей
    #37633517
GunSmoker
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
А что вы ожидали? Вы же храните зашифрованный текст в обычной строке - понятно, что при этом ни черта не соблюдается размер буфера (у строк он кратен 2, а вообще-то он должен быть произвольным).

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

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

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

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

Получается, что нельзя использовать один и тот же буфер как входной и выходной при шифровании, т.к. при попытке шифрования первого блока он затрет исходные данные еще незашифрованного.
...
Рейтинг: 0 / 0
CryptAPI (RSA) - работа с парами ключей
    #37634898
shmelina
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Правильнее будет шифровать случайным сессионным ключом (созданным с помощью CryptGenKey ), а сессионный ключ уже шифровать ключевой парой.
...
Рейтинг: 0 / 0
CryptAPI (RSA) - работа с парами ключей
    #37635313
shmelina
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Шифровать приватным ключом нельзя. Только публичным.
...
Рейтинг: 0 / 0
Период между сообщениями больше года.
CryptAPI (RSA) - работа с парами ключей
    #39734744
pivbul
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Генерирую и экспортирую в файлы приватный и публичный ключи
Код: 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
CryptAPI (RSA) - работа с парами ключей
    #39735136
GunSmoker
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Навскидку:

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


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