我見過一些類似的問題似乎並不能解決我的確切用例,我想我已經找到了答案,但是當涉及到安全性時,我是一個完全noob的人,RSA和幾乎所有與之相關的東西。我對這些概念有一個基本的熟悉,但是到目前爲止我所做的所有實際實現都是關於編輯別人的代碼而不是自己創建的。無論如何,這裏是我在哪裏:通過Javascript使用SSL
我知道Javascript是一個固有的不好的地方做加密。有人可能會對你的迴應進行中間人處理,並摧毀JS,所以你最終會通過電報發送未加密的數據。它應該通過HTTPS SSL/TLS連接來完成,但是這種託管需要花錢,正式簽署的證書也應該切實地與連接一起使用。這就是說,我認爲我要這樣做的方式會繞過JS加密的Man-in-the-middle弱點,這是因爲我只對一件事進行加密(密碼散列)用於一個RESTful服務調用,然後僅使用該密碼哈希來簽署來自客戶端的請求,以便將它們認證爲來自請求聲明的來自用戶的請求。這意味着JS只負責在創建用戶帳戶時加密密碼散列,並且如果服務器無法解密該密碼,那麼它知道它已經存在。
我也打算保存一些客戶端信息,特別是$_SERVER['REMOTE_ADDR']
以保證有人不用M-i-t-M註冊交換本身。
我使用PHP的openssl_pkey_
函數生成非對稱密鑰,並在客戶端上生成Cryptico庫。我的計劃是讓用戶向REST服務發送一個「預註冊」請求,這將使服務器生成密鑰,將私鑰和客戶端信息存儲在由電子郵件地址索引的數據庫中,然後響應與公共密鑰。
然後客戶端將使用公鑰對用戶的密碼哈希進行加密,並將其作爲另一種請求類型發送給REST服務以完成註冊。服務器將解密並保存密碼散列,使客戶信息和私鑰失效,因此不能使用該信息進行進一步的註冊,然後使用狀態代碼進行響應。
要登錄,用戶將輸入他們的電子郵件地址和密碼,密碼將在註冊時被散列,附加到請求主體,並再次散列以簽名請求到登錄端點,該請求會嘗試追加將存儲的散列發送給請求主體,並對其進行散列以驗證簽名與請求中的簽名的對比,從而對用戶進行身份驗證。對服務的更多數據請求將遵循相同的認證過程。
我是否缺少任何明亮的洞?有可能欺騙$_SERVER['REMOTE_ADDR']
價值的東西具體?我不需要IP地址與用戶登錄時一樣準確或者相同,我只需要知道,「預先註冊」並獲得公鑰的同一臺機器跟進並完成了註冊,而不是劫機者使用偵聽的公鑰完成註冊。當然,我猜如果他們能做到這一點,他們已經在創建時劫持了賬戶而無法恢復,合法用戶也無法使用自己的密碼完成註冊,這也沒關係。
底線,有人仍然可以破解我的服務,除非我掏出一個真正的SSL主機?作爲一種加密工具,我是否圍繞Javascript的弱點回避了一下?
當我編寫和調試我的代碼時,如果有人想使用它,我會在這裏發佈它。如果我將我的網站開放給任何類型的攻擊,請讓我知道。
這些函數用於驗證客戶端對頭文件中散列的請求,生成私鑰,將其保存到數據庫,使用公鑰進行響應,並解密並檢查密碼哈希。
public function validate($requestBody = '',$signature = '',$url = '',$timestamp = '') {
if (is_array($requestBody)) {
if (empty($requestBody['signature'])) { return false; }
if (empty($requestBody['timestamp'])) { return false; }
if ($requestBody['requestBody'] === null) { return false; }
$signature = $requestBody['signature'];
$timestamp = $requestBody['timestamp'];
$requestBody = $requestBody['requestBody'];
}
if (($requestBody === null) || empty($signature) || empty($timestamp)) { return false; }
$user = $this->get();
if (count($user) !== 1 || empty($user)) { return false; }
$user = $user[0];
if ($signature !== md5("{$user['pwHash']}:{$this->primaryKey}:$requestBody:$url:$timestamp")) { return false; }
User::$isAuthenticated = $this->primaryKey;
return $requestBody;
}
public function register($emailAddress = '',$cipher = '') {
if (is_array($emailAddress)) {
if (empty($emailAddress['cipher'])) { return false; }
if (empty($emailAddress['email'])) { return false; }
$cipher = $emailAddress['cipher'];
$emailAddress = $emailAddress['email'];
}
if (empty($emailAddress) || empty($cipher)) { return false; }
$this->primaryKey = $emailAddress;
$user = $this->get();
if (count($user) !== 1 || empty($user)) { return false; }
$user = $user[0];
if (!openssl_private_decrypt(base64_decode($cipher),$user['pwHash'],$user['privateKey'])) { return false; }
if (md5($user['pwHash'].":/api/preRegister") !== $user['session']) { return false; }
$user['session'] = 0;
if ($this->put($user) !== 1) { return false; }
$this->primaryKey = $emailAddress;
User::$isAuthenticated = $this->primaryKey;
return $this->getProfile();
}
public function preRegister($emailAddress = '',$signature = '') {
if (is_array($emailAddress)) {
if (empty($emailAddress['signature'])) { return false; }
if (empty($emailAddress['email'])) { return false; }
$signature = $emailAddress['signature'];
$emailAddress = $emailAddress['email'];
}
if (empty($emailAddress) || empty($signature)) { return false; }
$this->primaryKey = $emailAddress;
$response = $this->makeUserKey($signature);
if (empty($response)) { return false; }
$response['emailAddress'] = $emailAddress;
return $response;
}
private function makeUserKey($signature = '') {
if (empty($signature)) { return false; }
$config = array();
$config['digest_alg'] = 'sha256';
$config['private_key_bits'] = 1024;
$config['private_key_type'] = OPENSSL_KEYTYPE_RSA;
$key = openssl_pkey_new($config);
if (!openssl_pkey_export($key,$privateKey)) { return false; }
if (!$keyDetails = openssl_pkey_get_details($key)) { return false; }
$keyData = array();
$keyData['publicKey'] = $keyDetails['key'];
$keyData['privateKey'] = $privateKey;
$keyData['session'] = $signature;
if (!$this->post($keyData)) { return false; }
$publicKey = openssl_get_publickey($keyData['publicKey']);
$publicKeyHash = md5($keyData['publicKey']);
if (!openssl_sign($publicKeyHash,$signedKey,$privateKey)) { return false; }
if (openssl_verify($publicKeyHash,$signedKey,$publicKey) !== 1) { return false; }
$keyData['signedKey'] = base64_encode($signedKey);
$keyData['rsa'] = base64_encode($keyDetails['rsa']['n']).'|'.bin2hex($keyDetails['rsa']['e']);
unset($keyData['privateKey']);
unset($keyData['session']);
return $keyData;
}
我剛剛看到[this one](http://stackoverflow.com/questions/5724650/ssl-alternative-encrypt-password-with-javascript-submit-to-php-to-decrypt?rq=1)in我的相關訂閱源,這裏的區別在於,帶有加密密碼的請求只能發送給每個用戶一次,這樣服務器就知道使用什麼值來爲將來請求的哈希值加鹽。如果有人設法欺騙他們的IP來匹配真實用戶,他們就能夠聲明用戶名,但從不劫持註冊完成後正在使用的帳戶......我認爲... – citizenslave
我認爲這兩個方面我不舒服的是; 1.不,我們不能相信$ _SERVER ['REMOTE_ADDR'],在公司的反向防火牆中,所有用戶都可以擁有相同的IP(向外),具體取決於其配置。 2.在非SSL情況下,您不能相信電話沒有被監聽(特別是現在我們知道「攻擊」可能是他們的防火牆網絡中的某個人)。這並不是說我已經有了完整的攻擊場景。 –
感謝您的快速響應。私有網絡上的共享IP在使用'$ _SERVER ['REMOTE_ADDR']'時是一個合法的漏洞,但在我看來,唯一的攻擊是從預註冊響應中窺探公鑰,將其用於加密你自己的密碼而不是用戶的密碼,並欺騙最終的註冊請求。然後你就可以控制帳戶,但用戶永遠不會擁有。我並不興奮地離開那條大道,但我可以忍受它,特別是假冒遠程地址的唯一方法是在同一個專用網絡上。不完美,但可以接受。 – citizenslave