#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"
Комментариев нет:
Отправить комментарий