请帮我弄清楚如何将下面脚本中的 token 存储在 MySQL 字段中。我通过进行试验和错误测试来隔离问题,发现我能够存储 key 、编码密码,但由于某种原因我无法存储 token 。我应该使用什么字段类型来存储它?多长?
<?php
require_once('../MySQLi/mysqliConnect.php');
/**
* Password protection class.
*/
class PasswordCrypt {
/* Number of rounds to use for PBKDF2 for new setups.
* May be tweaked up or down because the number of rounds is encoded with the data
*/
const DEFAULT_PBKDF2_ROUNDS = 10000;
/* Definition:
* Output data = 1 || IV || HMAC_SHA256(K, PROTECT_AES_256_CBC(K, IV, data)) || AES_256_CBC(K, IV, data)
*/
const PROTECT_AES_256_CBC_HMACSHA1 = 1;
/* Definition:
* Uses the PBKDF2 derivation algorithm. The key is encrypted with a protection algorithm..
* Output data = 1 || SALT || Rounds (4-byte-integer) || 'Protected' key)
*/
const TOKEN_PBKDF2_SHA256_AES_256_CBC = 1;
/* Token which is effectively the key encrypted with the password */
private $token;
/* Key used to perform encryption/decryption */
private $key;
/* Private constructor that takes the token and key.
*
* The token is kept around for storage purposes whereas the key is the main
* important piece.
*/
protected function __construct($token, $key) {
$this->token = $token;
$this->key = $key;
}
/**
* Create a new instance using a given password.
*
* @param $password text string from user
*/
public static function createWithNewPassword($password) {
/* Generate the new random key to use */
$key = PasswordCrypt::generateRandomKey();
/* Generate the token to store */
$token = PasswordCrypt::encodeProtectedToken($password, $key);
/* Setup the instance */
return new PasswordCrypt($token, $key);
}
/**
* Create a new instance given input token and password.
*
* @param $password password associated with the given $token
* @param $token token for the given user
*
* @return null if decoding fails, else value instance that can decrypt/encrypt passwords/etc.
*/
public static function createFromToken($password, $token) {
$key = PasswordCrypt::decodeProtectedToken($password, $token);
if (!$key) {
return null;
}
return new PasswordCrypt($token, $key);
}
/**
* Create a new instance using session data.
*/
public static function createWithSession() {
$token = $_SESSION['__pc_token'];
$sessionKey = $_SESSION['__pc_key'];
$cookieData = $_COOKIE['__pc_cookie'];
if (!$token || !$sessionKey || !$cookieData) {
return null;
}
$cookieData = PasswordCrypt::base64url_decode($cookieData);
$key = PasswordCrypt::decryptData($sessionKey, $cookieData);
return new PasswordCrypt($token, $key);
}
/**
* Utility function to check if the PHP session state has what is needed for
* session-based construction.
*/
public static function hasSessionData() {
return $_SESSION['__pc_token']
&& $_SESSION['__pc_key']
&& $_COOKIE['__pc_cookie'];
}
/**
* Clear the session state from PHP to prevent re-use (ex: useful for logout)
*/
public static function clearSessionData() {
unset($_SESSION['__pc_token']);
unset($_SESSION['__pc_key']);
PasswordCrypt::setSessionCookie('__pc_cookie', '');
}
/* Utility function to store data in the session and set a cookie to decrypt */
public function storeSessionKey() {
$sessionKey = PasswordCrypt::generateRandomKey();
$cookieData = PasswordCrypt::encryptData($sessionKey, $this->key);
$_SESSION['__pc_token'] = $this->token;
$_SESSION['__pc_key'] = $sessionKey;
PasswordCrypt::setSessionCookie('__pc_cookie', PasswordCrypt::base64url_encode($cookieData));
}
/**
* Utility method to set a session cookie - tweak as appropriate for proper cookie settings for the site.
*/
private static function setSessionCookie($name, $value) {
/* Store for session only and assume total path... */
$secure = false;
if ($_SERVER["HTTPS"]) {
$secure = TRUE;
}
setcookie($name, $value, 0, "", "", $secure, TRUE);
}
/**
* Method to decode passwords previously encoded.
*
* @param $ciphertext raw binary string representing the encrypted password.
*
* @return decoded password string as passed in to encodePassword.
* null may be returned if the data is corrupt, uses a format not supported by
* this version, or does not match the key.
*/
/**
* Method to use MySQL functions
* Beginning of added code
*/
public function MySQL()
{
$query = "SELECT password_key FROM pwmkey";
$connection = $check_connection->db_connect();
$result = mysqli_query($check_connection,$query);
$array = mysqli_fetch_assoc($result);
}
/**
*End of added code
*/
public function decodePassword($ciphertext) {
/* Changed first parameter in decryptData from $this->key to $InsideOutsideKey */
return PasswordCrypt::decryptData($this->key,$ciphertext);
}
/**
* Method to encode passwords to later be decoded using decodePassword.
*
* @param $password text representing the data to protect - may be raw binary.
*
* @return encrypted password data in raw binary form. null will be returned if
* something is critically wrong with the setup - not likely.
*/
public function encodePassword($password) {
return PasswordCrypt::encryptData($this->key, $password);
}
/**
* Method to create a new token protected with a new password.
* Useful for when the user wants to change their password.
*
* @param $password new password to encode against.
*
* @return token to later be decoded using the provided password.
*/
public function encodeToken($password) {
return PasswordCrypt::encodeProtectedToken($password, $this->key);
}
/**
* Method to get the current token as protected by the initial password input.
*
* @return token to later be decoded using the initial password at creation.
*/
public function getToken() {
return $this->token;
}
/**
*Method to get a random key used by AES-256
*/
public function getKey() {
return $this->key;
}
/**
* Utility method to generate a random key as required by AES-256
*/
private static function generateRandomKey() {
/* Since using AES 256 CBC - we need 256 bits */
return openssl_random_pseudo_bytes(256 / 8);
}
/* Generate the protected token data */
private static function encodeProtectedToken($password, $key) {
$implementation = PasswordCrypt::TOKEN_PBKDF2_SHA256_AES_256_CBC;
switch ($implementation) {
case PasswordCrypt::TOKEN_PBKDF2_SHA256_AES_256_CBC:
/* Generate fresh salt to prevent rainbow table attacks */
$salt = openssl_random_pseudo_bytes(16);
/* Use the defaults configured */
$iterations = PasswordCrypt::DEFAULT_PBKDF2_ROUNDS;
/* Derive the key using PBKDF2 with SHA256 per the token key generation scheme */
$passwordDerivedKey = PasswordCrypt::pbkdf2("SHA256", $password, $salt, $iterations, 256 / 8, true);
/* Encrypt the 'real' key data using the password-based key */
$data = PasswordCrypt::encryptData($passwordDerivedKey, $key);
return pack("Ca16La*", $implementation, $salt, $iterations, $data);
default:
return null;
}
}
/* Decode the protected token data */
private static function decodeProtectedToken($password, $tokenData) {
list(,$implementation) = unpack("C", $tokenData);
switch ($implementation) {
case PasswordCrypt::TOKEN_PBKDF2_SHA256_AES_256_CBC:
/* Extract the salt encoded in the protection data */
$salt = substr($tokenData, 1, 16);
/* Extract the number of iterations used for this data */
list(,$iterations) = unpack("L", substr($tokenData, 1 + 16, 4));
/* Extract the encrypted key data */
$encrypted = substr($tokenData, 1 + 16 + 4);
/* Derive the key using PBKDF2 with SHA256 per the token key generation scheme */
$passwordDerivedKey = PasswordCrypt::pbkdf2("SHA256", $password, $salt, $iterations, 256 / 8, true);
/* Decrypt the key data using the derived key */
return PasswordCrypt::decryptData($passwordDerivedKey, $encrypted);
default:
return null;
}
}
/* Protect data using a given input key */
private static function encryptData($key, $plaintext) {
$implementation = PasswordCrypt::PROTECT_AES_256_CBC_HMACSHA1;
switch ($implementation) {
case PasswordCrypt::PROTECT_AES_256_CBC_HMACSHA1:
/* Some PHP versions to not have OPENSSL_RAW_DATA option and instead use a boolean for raw, so handle it */
$options = defined("OPENSSL_RAW_DATA") ? OPENSSL_RAW_DATA : true;
/* 16-byte IV */
$iv = openssl_random_pseudo_bytes(16);
/* Encrypt the data first */
$encrypted = openssl_encrypt($plaintext, "aes-256-cbc", bin2hex($key), $options, $iv);
/* Generate an HMAC over the data to make sure final output matches */
$hmac = hash_hmac("sha1", $encrypted, $key, TRUE);
/* HMAC should be 160 bits long - 20 bytes */
return pack("Ca16a20A*", $implementation, $iv, $hmac, $encrypted);
default:
return null;
}
}
/* Decoded protected data with a given key */
private static function decryptData($key, $ciphertext) {
list(,$implementation) = unpack("C", $ciphertext);
switch ($implementation) {
case PasswordCrypt::PROTECT_AES_256_CBC_HMACSHA1:
$iv = substr($ciphertext, 1, 16);
$included_hmac = substr($ciphertext, 1 + 16, 20);
$encrypted = substr($ciphertext, 1 + 16 + 20);
/* Verify the HMAC */
$hmac = hash_hmac("sha1", $encrypted, $key, TRUE);
if ($hmac != $included_hmac) {
/* HMAC did not match, bad key or corrupt data */
return null;
}
/* Some PHP versions to not have OPENSSL_RAW_DATA option and instead use a boolean for raw, so handle it */
$options = defined("OPENSSL_RAW_DATA") ? OPENSSL_RAW_DATA : true;
$decrypted = openssl_decrypt($encrypted, "aes-256-cbc", bin2hex($key), $options, $iv);
return $decrypted;
default:
return null;
}
}
/*
* PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt
* $algorithm - The hash algorithm to use. Recommended: SHA256
* $password - The password.
* $salt - A salt that is unique to the password.
* $count - Iteration count. Higher is better, but slower. Recommended: At least 1000.
* $key_length - The length of the derived key in bytes.
* $raw_output - If true, the key is returned in raw binary format. Hex encoded otherwise.
* Returns: A $key_length-byte key derived from the password and salt.
*
* Test vectors can be found here: https://www.ietf.org/rfc/rfc6070.txt
*
* This implementation of PBKDF2 was originally created by https://defuse.ca
* With improvements by http://www.variations-of-shadow.com
*/
private static function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false) {
$algorithm = strtolower($algorithm);
if(!in_array($algorithm, hash_algos(), true)) {
trigger_error('PBKDF2 ERROR: Invalid hash algorithm.', E_USER_ERROR);
}
if($count <= 0 || $key_length <= 0) {
trigger_error('PBKDF2 ERROR: Invalid parameters.', E_USER_ERROR);
}
/* If we have a version of PHP with the native hash_pbkdf2 - use that! */
if (function_exists("hash_pbkdf2")) {
// The output length is in NIBBLES (4-bits) if $raw_output is false!
if (!$raw_output) {
$key_length = $key_length * 2;
}
return hash_pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output);
}
$hash_length = strlen(hash($algorithm, "", true));
$block_count = ceil($key_length / $hash_length);
$output = "";
for($i = 1; $i <= $block_count; $i++) {
// $i encoded as 4 bytes, big endian.
$last = $salt . pack("N", $i);
// first iteration
$last = $xorsum = hash_hmac($algorithm, $last, $password, true);
// perform the other $count - 1 iterations
for ($j = 1; $j < $count; $j++) {
$xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true));
}
$output .= $xorsum;
}
if($raw_output) {
return substr($output, 0, $key_length);
} else {
return bin2hex(substr($output, 0, $key_length));
}
}
private static function base64url_encode($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
private static function base64url_decode($data) {
return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT));
}
}
?>
最佳答案
表中有一个二进制字段,例如一个varbinary ,您可以使用 prepared statement并将 token 参数绑定(bind)为 blob,然后通过 mysqli_stmt::send_long_data 发送实际数据
<?php
$mysqli = new mysqli('localhost', 'localonly', 'localonly', 'test');
if ($mysqli->connect_errno) {
trigger_error( sprintf('mysqli connect error (%d) %s', $mysqli->connect_errno, $mysqli->connect_error), E_USER_ERROR);
die;
}
mysqli_report(MYSQLI_REPORT_STRICT|MYSQLI_REPORT_ALL);
$mysqli->query('
CREATE TEMPORARY TABLE soFoo (
id int auto_increment,
x varbinary(128),
primary key(id)
)
');
$token = openssl_random_pseudo_bytes(96); // just some binary data
$stmt = $mysqli->prepare('INSERT INTO soFoo (x) VALUES (?)');
$stmt->bind_param('b', $foo);
$stmt->send_long_data(0, $token);
$stmt->execute();
$stmt = null;
foreach( $mysqli->query('SELECT x FROM soFoo WHERE id=1') as $row ) {
var_export($token===$row['x']);
}
这可能也适用于仅绑定(bind)字符串参数。
$stmt = $mysqli->prepare('INSERT INTO soFoo (x) VALUES (?)');
$stmt->bind_param('s', $token);
$stmt->execute();
编辑:使用具有多个 blob 参数的准备好的语句:
$stmt = $mysqli->prepare('INSERT INTO soFoo (x, y, anInteger, largeblob) VALUES (?,?,?,?)');
$stmt->bind_param('bbib', $foo, $foo, $n, $foo);
// the first parameter of send_long_data specificies the positional parameter of the prep.statement, starting with 0
$stmt->send_long_data(0, $token); // the data "for" x
$stmt->send_long_data(1, $token); // the data "for" y
// assume a case where the data for largeblob might potentially be larger than max_client_packet
// there's an integer parameter that already "has its data" between the last blob and this one, so this is parameter #3
while( !feof($fp) ) { // the loop is a bit oversimplified; you would probably have a more error handling in production code....
$stmt->send_long_data(3, fread($fp, 4096)); // the next data chunk "for" largeblob
}
$stmt->execute();
$stmt = null;
正如我在上一条评论中提到的,使用 send_long_data() 绝不是必须。如果你愿意,我建议你从
开始 http://dev.mysql.com/doc/refman/5.7/en/charset-connection.html
http://dev.mysql.com/doc/refman/5.7/en/binary-varbinary.html
http://dev.mysql.com/doc/refman/5.7/en/string-literals.html
和 https://dev.mysql.com/doc/refman/5.7/en/packet-too-large.html
作为决定这是否适合您的依据;-)
关于php - 我在 MySQL 中存储 token 时遇到困难,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34112560/