如何保护API密钥和第三方站点凭据(LAMP)?

Sha*_*ock 12 php mysql api credentials api-key

我正在创建一个网站,该网站将使用ID,密码和API密钥到其他第三方网站 - 以便服务器相应地访问信息.出于此对话的目的,我们假设它是针对支付网关的 - 意味着存储在数据库中的此信息的暴露可能意味着恶意用户可以从其凭据泄露的帐户中提取现金.

不幸的是,这不像密码/散列情况,因为用户每次都不输入凭证 - 他们输入一次,然后将其保存在服务器上以供应用程序将来使用.

我能想出的唯一合理的方法(这将是一个MySQL/PHP应用程序),是通过PHP应用程序中的硬编码"密码"加密凭证.这里唯一的好处是,如果恶意用户/黑客获得对数据库的访问权限,而不是PHP代码,他们仍然没有任何东西.也就是说,这对我来说似乎毫无意义,因为我认为我们可以合理地假设一个黑客如果得到一个或另一个就会得到一切 - 对吧?

如果社区决定一些好的解决方案,那么将其他来源收集到示例/教程/更深入的信息中会很好,以便将来可以为所有人实施.

我很惊讶我没有在堆栈上看到任何好的答案这个问题.我确实找到了这个,但在我的情况下,这并不适用:我应该如何道德地接近用户密码存储以便以后的明文检索?

谢谢大家.

Jon*_*Jon 8

基于我在问题,答案和评论中可以看到的内容; 我建议利用OpenSSL.这假设您的站点需要定期访问此信息(这意味着可以安排).如你所说:

服务器需要此信息才能发送各种情况的付款.它不需要所述密钥的"所有者"登录,事实上,所有者可能永远不会在他们第一次提供它们时再次看到它们.

它来自此注释,并且假设访问要存储的数据可以放在cron作业中.进一步假设您的服务器上有SSL(https),因为您将处理机密的用户信息,并且可以使用OpenSSLmcrypt模块.此外,以下内容对于"如何"实现更为通用,但不是真的根据你的情况做这件事的细节.还应该指出,这个"操作方法"是通用的,你应该在实施之前做更多的研究.话虽这么说,让我们开始吧.

首先,我们来谈谈OpenSSL提供的内容.OpenSSL为我们提供了一个公钥加密:使用公钥加密数据的能力(如果受到损害,不会损害用它加密的数据的安全性.)其次,它提供了一种方法来访问该信息. '私钥.由于我们不关心创建证书(我们只需要加密密钥),因此可以使用一个简单的函数(只能使用一次)来获取它们.

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;
}
Run Code Online (Sandbox Code Playgroud)

现在,你有一个公共私有密钥.保护私钥,将其从服务器上移除,并将其保留在数据库之外.将它存储在另一台服务器,一台可以运行cron作业的计算机上等等.除非你每次需要付款处理并使用AES加密或其他东西加密私钥时都要求管理员出现在公众面前.类似.但是,公钥将硬编码到您的应用程序中,并且每次用户输入要存储的信息时都会使用该公钥.

接下来,您需要确定计划如何验证解密数据(因此您不会开始使用无效请求发布到支付API.)我将假设有多个字段需要存储,因为我们只想要加密一次,它将在一个可以序列化的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;

}
Run Code Online (Sandbox Code Playgroud)

这个特殊的功能将产生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, $pass, $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;
}
Run Code Online (Sandbox Code Playgroud)

(我最初将这些函数编写为类的一部分,并建议您将它们实现到自己的类中.)此外,使用此函数可以使用创建的一次性填充,但是,如果使用的话对于不同的应用程序,用户特定的密码,你肯定需要一些盐来添加密码.接下来,解密并验证解密数据是否正确:

/**
 * 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, 0, 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, $pass, $data, MCRYPT_MODE_CBC, $iv);
    //Now we trim off the padding at the end that php added
    $decData = rtrim($decData, "\0");
    //Get the md5 hash we stored at the end
    $dataHash = substr($decData, -32);
    //Remove the hash from the data
    $decData = substr($decData, 0, -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;
    }
}
Run Code Online (Sandbox Code Playgroud)

查看这两个函数,阅读注释等.找出它们的作用以及它们的工作方式,这样就不会错误地实现它们.现在,加密用户数据.我们将使用公钥对其进行加密,并且以下函数假定到目前为止(以及将来)的每个函数都在同一个类中.我将立即提供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;
}
Run Code Online (Sandbox Code Playgroud)

其中$data的内容始终是一次性填充,而不是用户信息.接下来,将用于加密和解密的公钥加密和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;
}
Run Code Online (Sandbox Code Playgroud)

我保留了完整的评论,以帮助理解这些功能.现在我们开始讨论有趣的部分,实际上是在处理用户数据.的$datasend方法是用户数据以序列化阵列.请记住,对于$publicKey硬编码的send方法,您可以将其作为变量存储在类中,并以这种方式访问​​它,以便将较少的变量传递给它,或者让它从其他地方输入以便每次都发送到方法.加密数据的示例用法:

$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.
Run Code Online (Sandbox Code Playgroud)

现在,这是最简单的部分,下一部分是能够使用该数据.为此,您需要在您的服务器上接受一个页面,$_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.
Run Code Online (Sandbox Code Playgroud)

接下来,使用特定的理论来访问具有私钥的安全页面.在单独的服务器上,您将拥有一个运行php脚本的cron作业,特别是不public_html包含私钥,然后用于curl将私钥发布到正在查找它的页面.(确保您正在呼叫以https开头的地址)

我希望这有助于回答如何在您的应用程序中安全地存储用户信息,这些信息不会因访问您的代码或数据库而受到损害.


小智 0

如果您使用基于 X 标准的随机盐,您可以预测该标准,但黑客无法预测,那么根据您编写代码的方式,即使黑客获得了对所有内容的访问权限,仍然可能不清楚什么是什么。

例如,您使用当前时间和日期加上用户 IP 地址作为盐。然后,您将这些值与哈希值一起存储在数据库中。您混淆了用于创建哈希的函数,并且盐是什么可能不太明显。当然,任何坚定的黑客最终都可以破解这一点,但这可以为您赢得时间和一些额外的保护。