Добрый день, есть желание из своей программы, написанной на Delphi, авторизоваться в Steam.
Посмотрев исходники программы Steam Desktop Authenticator, пришел к выводу, что нам нужно сделать 2 POST-запроса к серверу Steam, первый вернет нам модуль и экспоненту открытого ключа RSA, которым мы должны зашифровать свой пароль и отправить его во втором запросе. Решил попробовать реализовать это с помощью функций Microsoft CryptoAPI: Next Generation.
Набросал функцию, которая на входе получает 3 строки: пароль от Стима, открытые модуль и экспоненту в виде строк, состоящих из шестнадцатеричных символов (их нам присылает сервер). На выходе должны получить пароль, зашифрованный открытым ключом по алгоритму RSA и закодированный в BASE64.
unit uRSA;
interface
uses WinAPI.Windows, System.SysUtils, VCL.Dialogs, System.NetEncoding;
//БЛОБ открытого ключа RSA, описание структуры есть на MSDN
type TPublicKeyBLOB = packed record
Magic: ULONG;
BitLength: ULONG;
cbPublicExp: ULONG;
cbModulus: ULONG;
cbPrime1: ULONG;
cbPrime2: ULONG;
exponent: array[0..2] of byte;
modulus: array[0..255] of byte;
end;
pPublicKeyBLOB = ^TPublicKeyBLOB;
//прототипы функций из библиотеки BCRYPT.DLL
TBCryptOpenAlgorithmProvider = function (phAlgorithm: pHandle; pszAlgId: LPCWSTR;
pszImplementation: LPCWSTR; dwFlags: DWORD): ULONG; stdcall;
//--
TBCryptImportKeyPair = function (hAlgorithm: THandle; hImportKey: THandle; pszBlobType: LPCWSTR;
phKey: pHandle; pbInput: pPublicKeyBLOB; cbInput: ULONG; dwFlags: ULONG): ULONG; stdcall;
//--
TBCryptEncrypt = function (hKey: THandle; pbInput: PUCHAR; cbInput: ULONG; pPaddingInfo: POINTER;
pbIV: PUCHAR; cbIV: ULONG; pbOutput: PUCHAR; cbOutput: ULONG; pcbResult: PULONG; dwFlags: ULONG): ULONG; stdcall;
//--
TBCryptCloseAlgorithmProvider = function (hAlgorithm: THandle; dwFlags: ULONG): ULONG; stdcall;
//сама функция шифрования
type TRSA = class
public
function RSA_Encrypt(DataStr: string; modulusStr: string; exponentStr: string): string;
end;
const
BCRYPT_RSAPUBLIC_MAGIC = $31415352; // RSA1
BCRYPT_PAD_PKCS1 = $00000002;
implementation
function HexToByte(Str: string): Byte;
begin
result := Byte(StrToInt('$' + (Str)));
end;
function TRsa.RSA_Encrypt(DataStr: string; modulusStr: string; exponentStr: string): string;
var
PublicKeyBLOB: TPublicKeyBlob;
i: word;
status: ULONG;
hLib: THandle;
hAlg: THandle;
hKey: tHandle;
Password: TBytes;
EncryptedPassword: TBytes;
EncryptedSize: ULONG;
BCryptOpenAlgorithmProvider: TBCryptOpenAlgorithmProvider;
BCryptImportKeyPair: TBCryptImportKeyPair;
BCryptEncrypt: TBCryptEncrypt;
BCryptCloseAlgorithmProvider: TBCryptCloseAlgorithmProvider;
begin
//преобразовываем входные данные в массив байтов;
Password := TEncoding.ASCII.GetBytes(DataStr);
hLib := LoadLibrary('BCRYPT.DLL');
//загрузка функций из библиотеки
//функция инициализации провайдера
@BCryptOpenAlgorithmProvider := GetProcAddress(hLib, 'BCryptOpenAlgorithmProvider');
//функция импорта ключа
@BCryptImportKeyPair := GetProcAddress(hLib, 'BCryptImportKeyPair');
//функция шифрования
@BCryptEncrypt := GetProcAddress(hLib, 'BCryptEncrypt');
//функция освобождения провайдера
@BCryptCloseAlgorithmProvider := GetProcAddress(hLib, 'BCryptCloseAlgorithmProvider');
//собираем ключ
with PublicKeyBLOB do
begin
Magic := BCRYPT_RSAPUBLIC_MAGIC; // RSA1;
BitLength := SizeOf(Modulus)*8;//размер модуля в битах
cbPublicExp := SizeOf(Exponent);//размер экспоненты в байтах
cbModulus := SizeOf(Modulus);//размер модуля в байтах
//(необязательно) проверим, что в переданных параметрах - четное количество символов
if (length(exponentstr) mod 2) 0 then
exponentstr := '0' + exponentstr;
if (length(modulusstr) mod 2) 0 then
modulusstr := '0' + modulusstr;
for i := Low(exponent) to High(exponent) do
exponent[i] := HexToByte(copy(exponentstr,i*2+1,2));
for i := Low(modulus) to High(modulus) do
modulus[i] := HexToByte(copy(modulusstr,i*2+1,2));
end;
//инициализация провайдера
status := BCryptOpenAlgorithmProvider(@halg,'RSA',nil,0);
//импорт ключа
status := BCryptImportKeyPair(hAlg,0,'RSAPUBLICBLOB', @hKey, @PublicKeyBLOB,
SizeOf(PublicKeyBLOB),0);
//шифрование сообщения. Первый проход - получаем размер выходного буфера, второй - шифруем текст
status := BCryptEncrypt(hKey, @Password[0], Length(Password), nil, nil, 0, nil, 0, @EncryptedSize, BCRYPT_PAD_PKCS1);
SetLength(EncryptedPassword,EncryptedSize);
status := BCryptEncrypt(hKey, @Password[0], Length(Password), nil, nil, 0, @EncryptedPassword[0], Length(EncryptedPassword), @EncryptedSize, BCRYPT_PAD_PKCS1);
//кодируем зашифрованное сообщение в Base64
Result := TNetEncoding.Base64.EncodeBytesToString(EncryptedPassword);
//освобождаем память
status := BCryptCloseAlgorithmProvider(hAlg,0);
FreeLibrary(hLib);
end;
end.
Вроде бы все успешно отрабатывает, но, когда делаю POST-запрос с зашифрованным паролем к серверу STEAM - получаю в ответ:
{"success":false,"requires_twofactor":false,
"message":"The account name or password that you have entered is incorrect.",
"clear_password_field":true,"captcha_needed":false,"captcha_gid":-1}
На вход функции подаются строки в виде
"publickey_mod":"c152954756c74aee68ca1e6ac77bc71af6a93660bd56f1517e8141436b8fda4e32cc123ced02c0421a6a8d505dd25d07c4df9a25cf682799ea87ceb0eeaef7c7e71a8a30ec6c60d4dcfa8006a193f0b99958655894147b13d4f15285f9d7dc75baaa61b1b12265a303324c6b5e648bb272d87e0003295ca124e6e7a8a5ec45b9d2f1399e3ffbd0d9aec012afe1fbe65bfc9bd4c2087e7b7a73d2f9286e311e77341113f4cbde9a2a94141252e395e36bc2bf135ef38be90458090d6cd2c419e293a5dcf93bff3df2ca641edfb38ff85f6c9f8864f2905015cf34bd35d74e324f4a6a156c2462cfdd1c1fafb15567047a59b188c9f2a4f78157f2ec120ce09b5d",
"publickey_exp":"010001"
Буду очень признателен, если кто-нибудь укажет на ошибку или даст совет: как в Delphi можно зашифровать сообщение, имея экспоненту и модуль открытого ключа RSA.
Ответ
Проблема была в некорректной работе
//кодируем зашифрованное сообщение в Base64
Result := TNetEncoding.Base64.EncodeBytesToString(EncryptedPassword);
Эта функция не убирает переносы строки, так что придется получившийся результат обработать дополнительно:
//кодируем зашифрованное сообщение в Base64
Result := TNetEncoding.Base64.EncodeBytesToString(EncryptedPassword);
//удалим переносы строки
Result := StringReplace(Result, #13#10, '',[rfReplaceAll]);
Либо же можно использовать аналогичную функцию из пакета Indy, которая работает верно:
uses IdCoderMIME, idcoder, idglobal;
...
function MyFunc(): string;
var
Temp: TIdBytes;
begin
...
Result := TIdEncoderMIME.EncodeBytes(Temp);
end;
Рабочий код :
unit uRSA;
interface
uses WinAPI.Windows, System.SysUtils, VCL.Dialogs, System.NetEncoding;
//БЛОБ открытого ключа RSA, описание структуры есть на MSDN
type TPublicKeyBLOB = packed record
Magic: ULONG;
BitLength: ULONG;
cbPublicExp: ULONG;
cbModulus: ULONG;
cbPrime1: ULONG;
cbPrime2: ULONG;
exponent: array[0..2] of byte;
modulus: array[0..255] of byte;
end;
pPublicKeyBLOB = ^TPublicKeyBLOB;
//прототипы функций из библиотеки BCRYPT.DLL
TBCryptOpenAlgorithmProvider = function (phAlgorithm: pHandle; pszAlgId: LPCWSTR;
pszImplementation: LPCWSTR; dwFlags: DWORD): ULONG; stdcall;
//--
TBCryptImportKeyPair = function (hAlgorithm: THandle; hImportKey: THandle; pszBlobType: LPCWSTR;
phKey: pHandle; pbInput: pPublicKeyBLOB; cbInput: ULONG; dwFlags: ULONG): ULONG; stdcall;
//--
TBCryptEncrypt = function (hKey: THandle; pbInput: PUCHAR; cbInput: ULONG; pPaddingInfo: POINTER;
pbIV: PUCHAR; cbIV: ULONG; pbOutput: PUCHAR; cbOutput: ULONG; pcbResult: PULONG; dwFlags: ULONG): ULONG; stdcall;
//--
TBCryptCloseAlgorithmProvider = function (hAlgorithm: THandle; dwFlags: ULONG): ULONG; stdcall;
//сама функция шифрования
type TRSA = class
public
function Encrypt(DataStr: string; modulusStr: string; exponentStr: string): string;
end;
const
BCRYPT_RSAPUBLIC_MAGIC = $31415352; // RSA1
BCRYPT_PAD_PKCS1 = $00000002;
BCRYPT_RSA_ALGORITHM = 'RSA';
BCRYPT_RSAPUBLIC_BLOB = 'RSAPUBLICBLOB';
implementation
function HexToByte(Str: string): Byte;
begin
result := Byte(StrToInt('$' + (Str)));
end;
function TRsa.Encrypt(DataStr: string; modulusStr: string; exponentStr: string): string;
var
PublicKeyBLOB: TPublicKeyBlob;
i: word;
status: ULONG;
hLib: THandle;
hAlg: THandle;
hKey: tHandle;
Password: TBytes;
EncryptedPassword: TBytes;
EncryptedSize: ULONG;
BCryptOpenAlgorithmProvider: TBCryptOpenAlgorithmProvider;
BCryptImportKeyPair: TBCryptImportKeyPair;
BCryptEncrypt: TBCryptEncrypt;
BCryptCloseAlgorithmProvider: TBCryptCloseAlgorithmProvider;
begin
//преобразовываем входные данные в массив байтов;
Password := TEncoding.ASCII.GetBytes(DataStr);
hLib := LoadLibrary('BCRYPT.DLL');
if hLib < 32 then
begin
ShowMessage('Error while loading BCRYPT.DLL!');
exit;
end;
//загрузка функций из библиотеки
//функция инициализации провайдера
@BCryptOpenAlgorithmProvider := GetProcAddress(hLib, 'BCryptOpenAlgorithmProvider');
//функция импорта ключа
@BCryptImportKeyPair := GetProcAddress(hLib, 'BCryptImportKeyPair');
//функция шифрования
@BCryptEncrypt := GetProcAddress(hLib, 'BCryptEncrypt');
//функция освобождения провайдера
@BCryptCloseAlgorithmProvider := GetProcAddress(hLib, 'BCryptCloseAlgorithmProvider');
//собираем ключ
with PublicKeyBLOB do
begin
Magic := BCRYPT_RSAPUBLIC_MAGIC; // RSA1;
BitLength := SizeOf(Modulus)*8;//размер модуля в битах
cbPublicExp := SizeOf(Exponent);//размер экспоненты в байтах
cbModulus := SizeOf(Modulus);//размер модуля в байтах
//(необязательно) проверим, что в переданных параметрах - четное количество символов
if (length(exponentstr) mod 2) <> 0 then
exponentstr := '0' + exponentstr;
if (length(modulusstr) mod 2) <> 0 then
modulusstr := '0' + modulusstr;
for i := Low(exponent) to High(exponent) do
exponent[i] := HexToByte(copy(exponentstr,i*2+1,2));
for i := Low(modulus) to High(modulus) do
modulus[i] := HexToByte(copy(modulusstr,i*2+1,2));
end;
//инициализация провайдера
status := BCryptOpenAlgorithmProvider(@halg,BCRYPT_RSA_ALGORITHM,nil,0);
if status <> 0 then
begin
ShowMessage('BCryptOpenAlgorithmProvider error, status: ' + IntToStr(status) + ', ' + SysErrorMessage(GetLastError()));
exit;
end;
//импорт ключа
status := BCryptImportKeyPair(hAlg,0,BCRYPT_RSAPUBLIC_BLOB, @hKey, @PublicKeyBLOB, SizeOf(PublicKeyBLOB),0);
if status <> 0 then
begin
ShowMessage('BCryptImportKeyPair error, status: ' + IntToStr(status) + ', ' + SysErrorMessage(GetLastError()));
exit;
end;
//шифрование сообщения. Первый проход - получаем размер выходного буфера, второй - шифруем текст
status := BCryptEncrypt(hKey, @Password[0], Length(Password), nil, nil, 0, nil, 0, @EncryptedSize, BCRYPT_PAD_PKCS1);
if status <> 0 then
begin
ShowMessage('BCryptEncrypt error 1, status: ' + IntToStr(status) + ', ' + SysErrorMessage(GetLastError()));
exit;
end;
SetLength(EncryptedPassword,EncryptedSize);
status := BCryptEncrypt(hKey, @Password[0], Length(Password), nil, nil, 0, @EncryptedPassword[0], Length(EncryptedPassword), @EncryptedSize, BCRYPT_PAD_PKCS1);
if status <> 0 then
begin
ShowMessage('BCryptEncrypt error 2, status: ' + IntToStr(status) + ', ' + SysErrorMessage(GetLastError()));
exit;
end;
//освобождаем занятые ресурсы
status := BCryptCloseAlgorithmProvider(hAlg,0);
if status <> 0 then
begin
ShowMessage('BCryptCloseAlgorithmProvider error, status: ' + IntToStr(status) + SysErrorMessage(GetLastError()));
exit;
end;
//кодируем зашифрованное сообщение в Base64
Result := TNetEncoding.Base64.EncodeBytesToString(EncryptedPassword);
//удалим переносы строки
Result := StringReplace(Result, #13#10, '',[rfReplaceAll]);
//выгружаем библиотеку
FreeLibrary(hLib);
end;
end.
На вход функции подаются строки в виде:
"publickey_mod":"c152954756c74aee68ca1e6ac77bc71af6a93660bd56f1517e8141436b8fda4e32cc123ced02c0421a6a8d505dd25d07c4df9a25cf682799ea87ceb0eeaef7c7e71a8a30ec6c60d4dcfa8006a193f0b99958655894147b13d4f15285f9d7dc75baaa61b1b12265a303324c6b5e648bb272d87e0003295ca124e6e7a8a5ec45b9d2f1399e3ffbd0d9aec012afe1fbe65bfc9bd4c2087e7b7a73d2f9286e311e77341113f4cbde9a2a94141252e395e36bc2bf135ef38be90458090d6cd2c419e293a5dcf93bff3df2ca641edfb38ff85f6c9f8864f2905015cf34bd35d74e324f4a6a156c2462cfdd1c1fafb15567047a59b188c9f2a4f78157f2ec120ce09b5d",
"publickey_exp":"010001"