php – 如何保护API密钥和第三方站点凭据(LAMP)?
我正在创建一个网站,将使用ID,密码和API密钥到其他第三方网站,以便服务器相应地访问信息.为了进行这个对话,我们假设它是一个支付网关 – 这意味着存储在数据库中的这些信息的暴露可能意味着恶意用户可以从其凭据泄漏的帐户中提取现金.
不幸的是,这不像密码/散列情况,因为用户每次都不输入凭据 – 它们输入一次,然后将其保存在服务器上以备将来由应用程序使用. 我可以想出的唯一合理的方法(这将是一个MySQL / PHP应用程序),是通过PHP应用程序中的硬编码“密码”来加密凭据.这里唯一的好处是,如果恶意用户/黑客获得对数据库的访问权限,而不是PHP代码,则它们仍然没有任何内容.也就是说,这对我来说似乎没有意义,因为我认为我们可以合理地假设一个黑客会得到一切,如果他们得到一个或另一个 – 对吗? 如果社区决定了一些好的解决方案,那么将其他来源收集到示例/教程/更多的深度信息中是非常好的,以便将来可以为每个人实现. 我很惊讶,我没有看到这个问题与堆栈上的任何好的答案已经.我确实找到了这个,但在我的情况下,这并不适用于:How should I ethically approach user password storage for later plaintext retrieval? 感谢所有
根据我在问题,答案和评论中可以看到的内容;我建议利用OpenSSL.这是假设您的站点需要定期访问此信息(意味着可以安排).正如你所说:
这是从这个评论,并且假设访问您要存储的数据可以放在cron作业中.进一步假设您的服务器上具有SSL(https),因为您将处理机密用户信息,并且可以使用OpenSSL和mcrypt模块.另外,下面的内容将是相当通用的,“如何”实现了,但不是真正的细节,做你的情况.还应该指出,这个“如何”是一般的,你应该在实施之前做更多的研究.话虽如此,让我们开始吧. 首先,我们来谈谈OpenSSL提供的内容. OpenSSL为我们提供了一个Public-Key Cryptography:使用公钥加密数据的能力(如果受到威胁,则不会危及使用其加密的数据的安全性).其次,它提供了一种使用“私钥”访问该信息的方法.由于我们不关心创建证书(我们只需要加密密钥),所以可以使用一个简单的函数获得这些证书(您只需要使用一次). function makeKeyPair() { //Define variables that will be used,set to '' $private = ''; $public = ''; //Generate the resource for the keys $resource = openssl_pkey_new(); //get the private key openssl_pkey_export($resource,$private); //get the public key $public = openssl_pkey_get_details($resource); $public = $public["key"]; $ret = array('privateKey' => $private,'publicKey' => $public); return $ret; } 现在你有一个公钥和私钥.保护私钥,将其关闭您的服务器,并将其保留在数据库之外.将其存储在另一台服务器上,可以运行cron作业的计算机等.除了公开的眼睛之外,除非您需要管理员存在,否则每次需要付款进行处理并使用AES加密或某些东西加密私钥类似.然而,公钥将被硬编码到您的应用程序中,并且将在每次用户输入要存储的信息时使用. 接下来,您需要确定如何计算验证解密的数据(因此您不会开始向无效请求的付款API发布).我将假定有多个字段需要存储,而且我们只想要要加密一次,它将在一个可以为serialize的PHP数组中.根据需要存储多少数据,我们可以直接对其进行加密,也可以生成密码以使用公钥进行加密,并使用该随机密码加密数据本身.我将在这个解释中走这条路.要走这条路线,我们将使用AES加密,并且需要一个加密和解密功能的方便 – 以及为数据随机生成一个体面的一次性的方法.我将提供我使用的密码生成器,尽管我将其从我写的一段代码中移除,但它将用于此目的,也可以编写更好的代码. ^^ public function generatePassword() { //create a random password here $chars = array( 'a','A','b','B','c','C','d','D','e','E','f','F','g','G','h','H','i','I','j','J','k','K','l','L','m','M','n','N','o','O','p','P','q','Q','r','R','s','S','t','T','u','U','v','V','w','W','x','X','y','Y','z','Z','1','2','3','4','5','6','7','8','9','0','?','<','>','.',',';','-','@','!','#','$','%','^','&','*','(',')'); $max_chars = count($chars) - 1; srand( (double) microtime()*1000000); $rand_str = ''; for($i = 0; $i < 30; $i++) { $rand_str .= $chars[rand(0,$max_chars)]; } return $rand_str; } 这个特定的功能将产生30位数,这提供了体面的熵 – 但您可以根据需要进行修改.接下来,执行AES加密的功能: /** * Encrypt AES * * Will Encrypt data with a password in AES compliant encryption. It * adds built in verification of the data so that the {@link this::decryptAES} * can verify that the decrypted data is correct. * * @param String $data This can either be string or binary input from a file * @param String $pass The Password to use while encrypting the data * @return String The encrypted data in concatenated base64 form. */ public function encryptAES($data,$pass) { //First,let's change the pass into a 256bit key value so we get 256bit encryption $pass = hash('SHA256',$pass,true); //Randomness is good since the Initialization Vector(IV) will need it srand(); //Create the IV (CBC mode is the most secure we get) $iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128,MCRYPT_MODE_CBC),MCRYPT_RAND); //Create a base64 version of the IV and remove the padding $base64IV = rtrim(base64_encode($iv),'='); //Create our integrity check hash $dataHash = md5($data); //Encrypt the data with AES 128 bit (include the hash at the end of the data for the integrity check later) $rawEnc = mcrypt_encrypt(MCRYPT_RIJNDAEL_128,$data . $dataHash,MCRYPT_MODE_CBC,$iv); //Transfer the encrypted data from binary form using base64 $baseEnc = base64_encode($rawEnc); //attach the IV to the front of the encrypted data (concatenated IV) $ret = $base64IV . $baseEnc; return $ret; } (我最初写这些函数是一个类的一部分,并建议你将它们实现到一个你自己的类中.)此外,使用这个函数是一个一次性创建的键,但是,如果与一个不同应用程序的用户专用密码,您绝对需要一些盐才能添加到密码.接下来,解密和验证解密的数据是正确的: /** * Decrypt AES * * Decrypts data previously encrypted WITH THIS CLASS,and checks the * integrity of that data before returning it to the programmer. * * @param String $data The encrypted data we will work with * @param String $pass The password used for decryption * @return String|Boolean False if the integrity check doesn't pass,or the raw decrypted data. */ public function decryptAES($data,$pass){ //We used a 256bit key to encrypt,recreate the key now $pass = hash('SHA256',$this->salt . $pass,true); //We should have a concatenated data,IV in the front - get it now //NOTE the IV base64 should ALWAYS be 22 characters in length. $base64IV = substr($data,22) .'=='; //add padding in case PHP changes at some point to require it //change the IV back to binary form $iv = base64_decode($base64IV); //Remove the IV from the data $data = substr($data,22); //now convert the data back to binary form $data = base64_decode($data); //Now we can decrypt the data $decData = mcrypt_decrypt(MCRYPT_RIJNDAEL_128,$data,$iv); //Now we trim off the padding at the end that php added $decData = rtrim($decData," "); //Get the md5 hash we stored at the end $dataHash = substr($decData,-32); //Remove the hash from the data $decData = substr($decData,-32); //Integrity check,return false if it doesn't pass if($dataHash != md5($decData)) { return false; } else { //Passed the integrity check,give use their data return $decData; } } 看看这两个功能,阅读评论等.找出它们的工作原理以及它们的工作原理,这样你就不会错误地实现它们.现在,加密用户数据.我们将使用公共密钥进行加密,以下功能假设到目前为止(即将到来)的每个功能都在同一个类中.我将同时提供OpenSSL加密/解密功能,因为我们稍后需要. /** * Public Encryption * * Will encrypt data based on the public key * * @param String $data The data to encrypt * @param String $publicKey The public key to use * @return String The Encrypted data in base64 coding */ public function publicEncrypt($data,$publicKey) { //Set up the variable to get the encrypted data $encData = ''; openssl_public_encrypt($data,$encData,$publicKey); //base64 code the encrypted data $encData = base64_encode($encData); //return it return $encData; } /** * Private Decryption * * Decrypt data that was encrypted with the assigned private * key's public key match. (You can't decrypt something with * a private key if it doesn't match the public key used.) * * @param String $data The data to decrypt (in base64 format) * @param String $privateKey The private key to decrypt with. * @return String The raw decoded data */ public function privateDecrypt($data,$privateKey) { //Set up the variable to catch the decoded date $decData = ''; //Remove the base64 encoding on the inputted data $data = base64_decode($data); //decrypt it openssl_private_decrypt($data,$decData,$privateKey); //return the decrypted data return $decData; } 这些中的$数据将始终是一次性填充,而不是用户信息.接下来,将加密和解密的一次性加密密钥加密和AES的功能相结合. /** * Secure Send * * OpenSSL and 'public-key' schemes are good for sending * encrypted messages to someone that can then use their * private key to decrypt it. However,for large amounts * of data,this method is incredibly slow (and limited). * This function will take the public key to encrypt the data * to,and using that key will encrypt a one-time-use randomly * generated password. That one-time password will be * used to encrypt the data that is provided. So the data * will be encrypted with a one-time password that only * the owner of the private key will be able to uncover. * This method will return a base64encoded serialized array * so that it can easily be stored,and all parts are there * without modification for the receive function * * @param String $data The data to encrypt * @param String $publicKey The public key to use * @return String serialized array of 'password' and 'data' */ public function secureSend($data,$publicKey) { //First,we'll create a 30digit random password $pass = $this->generatePassword(); //Now,we will encrypt in AES the data $encData = $this->encryptAES($data,$pass); //Now we will encrypt the password with the public key $pass = $this->publicEncrypt($pass,$publicKey); //set up the return array $ret = array('password' => $pass,'data' => $encData); //serialize the array and then base64 encode it $ret = serialize($ret); $ret = base64_encode($ret); //send it on its way return $ret; } /** * Secure Receive * * This is the complement of {@link this::secureSend}. * Pass the data that was returned from secureSend,and it * will dismantle it,and then decrypt it based on the * private key provided. * * @param String $data the base64 serialized array * @param String $privateKey The private key to use * @return String the decoded data. */ public function secureReceive($data,$privateKey) { //Let's decode the base64 data $data = base64_decode($data); //Now let's put it into array format $data = unserialize($data); //assign variables for the different parts $pass = $data['password']; $data = $data['data']; //Now we'll get the AES password by decrypting via OpenSSL $pass = $this->privateDecrypt($pass,$privateKey); //and now decrypt the data with the password we found $data = $this->decryptAES($data,$pass); //return the data return $data; } 我保留了这些意见,以帮助了解这些功能.现在是我们得到有趣的部分,实际上是使用用户数据. send方法中的$数据是序列化数组中的用户数据.记住发送方法,$publicKey是硬编码的,你可以把它作为一个变量存储在你的类中,并以这种方式进行访问,以使较少的变量传递给它,或者从别处输入,以便每次发送到该方法.用于加密数据的示例用法: $myCrypt = new encryptClass(); $userData = array( 'id' => $_POST['id'],'password' => $_POST['pass'],'api' => $_POST['api_key'] ); $publicKey = "the public key from earlier"; $encData = $myCrypt->secureSend(serialize($userData),$publicKey)); //Now store the $encData in the DB with a way to associate with the user //it is base64 encoded,so it is safe for DB input. 现在,这是很容易的部分,下一部分是能够使用该数据.为此,您需要在服务器上接受$_POST [‘privKey’]的页面,然后以您的网站所需的方式循环访问用户等等,以获取$encData.从此解密的示例用法: $myCrypt = new encryptClass(); $encData = "pulled from DB"; $privKey = $_POST['privKey']; $data = unserialize($myCrypt->secureReceive($encData,$privKey)); //$data will now contain the original array of data,or false if //it failed to decrypt it. Now do what you need with it. 接下来,使用私钥访问该安全页面的具体理论.在一个单独的服务器上,您将有一个cron作业,其中运行php脚本,特别是在包含私钥的public_html中,然后使用 希望能帮助您回答如何将用户信息安全地存储在应用程序中,而不必通过访问您的代码或数据库来损害用户信息. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |