#delphi #rsa #steam #cryptoapi
Добрый день, есть желание из своей программы, написанной на 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.
Ответы
Ответ 1
Проблема была в некорректной работе //кодируем зашифрованное сообщение в 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"
Комментариев нет:
Отправить комментарий