我见过一些类似的问题似乎并不能解决我的确切用例,我想我已经找到了答案,但是当涉及到安全性时,我是一个完全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