每天学习一点点,成功增加一大步

PHP笔记:SymmetricSecurityKey 的验证和解析

PHP zhanghui 264℃

今天要做的需求是,后端是 PHP 和 .Net 的 ABP 框架的组合,与前端的交互是通过前面写过的 JWT-TOKEN 的方式,今天写这个的目的是 .Net 的 ABP 框架自带有 JWT-TOKEN 的算法,而它使用的 KEY 不是平常用的 RSA 私钥和公钥,而是 SymmetricSecurityKey 中文名叫对称安全密钥,这种密钥要如何在 PHP 上使用,还是得到搜索的“老大哥 ”——谷歌,后来在 GITHUB 找到了一个现成的处理类。

要使用这个类首要的条件是:

  • PHP 的版本为:7.0 及以上

以下是处理类的代码:

class Symmetric
{
    /**
     * Minimum number of PBKDF2 iteration allowed for security reason
     * @see http://goo.gl/OzdRxi
     */
    const MIN_PBKDF2_ITERATIONS = 20000;
    /**
     * Minimum size of key in bytes
     * @see https://en.wikipedia.org/wiki/Password_strength
     */
    const MIN_SIZE_KEY = 12;
    /**
     * @var string
     */
    protected $algo = 'aes-256-cbc';
    /**
     * Set the default number of iteration 4x the min value
     * @see https://goo.gl/bzv4dK
     * @var int
     */
    protected $iterations = self::MIN_PBKDF2_ITERATIONS * 4;
    /**
     * @var string
     */
    protected $hash = 'sha256';
    /**
     * Symmetric encryption key
     * @var string
     */
    protected $key;
    /**
     * @var int
     */
    private $hmacSize = 32; // SHA-256
    /**
     * @var int
     */
    private $keySize = 32;

    /**
     * Constructor
     */
    public function __construct(array $config = [])
    {
        if (!empty($config)) {
            if (isset($config['algo'])) {
                $this->setAlgorithm($config['algo']);
            }
            if (isset($config['hash'])) {
                $this->setHash($config['hash']);
            }
            if (isset($config['iterations'])) {
                $this->setIterations($config['iterations']);
            }
            if (isset($config['key'])) {
                $this->setKey($config['key']);
            }
        }
    }

    /**
     * Calc the output size of hash_hmac
     * @return int
     */
    protected function getHmacSize($hash): int
    {
        return mb_strlen(hash_hmac($hash, 'test', openssl_random_pseudo_bytes(32), true), '8bit');
    }

    /**
     * Set the encryption key
     * @param string $key
     */
    public function setKey(string $key)
    {
        $this->checkLengthKey($key);
        $this->key = $key;
    }

    /**
     * Check the key length for security reason
     * @param string
     */
    protected function checkLengthKey($key)
    {
        if (mb_strlen($key, '8bit') < self::MIN_SIZE_KEY) {
            trigger_error(
                sprintf("The encryption key %s it's too short!", $key),
                E_USER_WARNING
            );
        }
    }

    /**
     * Get the encryption key
     * @return string
     */
    public function getKey(): string
    {
        return $this->key;
    }

    /**
     * Set the encryption key size (in bytes)
     * @param int $size
     */
    public function setKeySize(int $size)
    {
        if ($size < 16) { // key size < 128 bits
            trigger_error(
                sprintf("The size %d of the encryption key it's too short!", $size),
                E_USER_WARNING
            );
        }
        $this->keySize = $size;
    }

    /**
     * Get the key size of the cipher (in bytes)
     * @return int
     */
    public function getKeySize(): int
    {
        return $this->keySize;
    }

    /**
     * Set the symmetric encryption algorithm
     * @param string $algo
     * @throws \InvalidArgumentException
     */
    public function setAlgorithm(string $algo)
    {
        if (!in_array($algo, openssl_get_cipher_methods(true))) {
            throw new \InvalidArgumentException(sprintf(
                "The algorithm %s is not supported by OpenSSL", $algo
            ));
        }
        $this->algo = $algo;
    }

    /**
     * Get the symmetric encryption algorithm
     * @return string
     */
    public function getAlgorithm(): string
    {
        return $this->algo;
    }

    /**
     * Set the hash algorithm for PBKDF2 and HMAC
     * @param string $hash
     * @throws \InvalidArgumentException
     */
    public function setHash(string $hash)
    {
        if (!in_array($hash, hash_algos())) {
            throw new \InvalidArgumentException(sprintf(
                "The hash algorithm %s is not supported", $hash
            ));
        }
        $this->hash = $hash;
        $this->hmacSize = $this->getHmacSize($this->hash);
    }

    /**
     * Get the hash algorithm used by PBKDF2 and HMAC
     * @return string
     */
    public function getHash(): string
    {
        return $this->hash;
    }

    /**
     * Set the number of iteration for PBKDF2
     * @param int $iteration
     */
    public function setIterations(int $iterations)
    {
        // Security warning
        if ($iterations < self::MIN_PBKDF2_ITERATIONS) {
            trigger_error(
                sprintf("The number of iteration %s used for PBKDF2 it's too low!", $iterations),
                E_USER_WARNING
            );
        }
        $this->iterations = $iterations;
    }

    /**
     * Get the number of iterations for PBKDF2
     * @return int
     */
    public function getIterations(): int
    {
        return $this->iterations;
    }

    /**
     * Encrypt-then-authenticate with HMAC
     * @param string $plaintext
     * @param string $key
     * @return string
     * @throws \Exception
     */
    public function encrypt(string $plaintext, string $key=null): string
    {
        if (empty($key) && empty($this->key)) {
            throw new \RuntimeException('The encryption key cannot be empty');
        }
        if (empty($key)) {
            $key = $this->key;
        } else {
            $this->checkLengthKey($key);
        }
        $ivSize = openssl_cipher_iv_length($this->algo);
        $iv = random_bytes($ivSize);
        // Generate an encryption and authentication key
        $keys = hash_pbkdf2($this->hash, $key, $iv, $this->iterations, $this->keySize * 2, true);
        // encryption key
        $encKey = mb_substr($keys, 0, $this->keySize, '8bit');
        // authentication key
        $hmacKey = mb_substr($keys, $this->keySize, null, '8bit');
        // Encrypt
        $ciphertext = openssl_encrypt(
            $plaintext,
            $this->algo,
            $encKey,
            OPENSSL_RAW_DATA,
            $iv
        );
        // Authentication
        $hmac = hash_hmac($this->hash, $iv . $ciphertext, $hmacKey, true);
        return $hmac . $iv . $ciphertext;
    }

    /**
     * Authenticate-then-decrypt with HMAC
     * @param string $ciphertext
     * @param string $key
     * @return string
     * @throws \RuntimeException
     */
    public function decrypt(string $ciphertext, string $key = null): string
    {
        if (empty($key) && empty($this->key)) {
            throw new \RuntimeException('The decryption key cannot be empty');
        }
        if (empty($key)) {
            $key = $this->key;
        } else {
            $this->checkLengthKey($key);
        }
        $hmac = mb_substr($ciphertext, 0, $this->hmacSize, '8bit');
        $ivSize = openssl_cipher_iv_length($this->algo);
        $iv = mb_substr($ciphertext, $this->hmacSize, $ivSize, '8bit');
        $ciphertext = mb_substr($ciphertext, $ivSize + $this->hmacSize, null, '8bit');
        // Generate the encryption and hmac keys
        $keys = hash_pbkdf2($this->hash, $key, $iv, $this->iterations, $this->keySize * 2, true);
        // encryption key
        $encKey = mb_substr($keys, 0, $this->keySize, '8bit');
        // authentication key
        $hmacKey = mb_substr($keys, $this->keySize, null, '8bit');
        // Authentication
        $hmacNew = hash_hmac($this->hash, $iv . $ciphertext, $hmacKey, true);
        if (!hash_equals($hmac, $hmacNew)) {
            throw new \RuntimeException('Authentication failed');
        }
        // Decrypt
        return openssl_decrypt(
            $ciphertext,
            $this->algo,
            $encKey,
            OPENSSL_RAW_DATA,
            $iv
        );
    }
}

如何验证:

$plaintext = 'Text to encrypt';
$key = '123456789012';
$cipher = new Symmetric();
$cipher->setKey($key);
$ciphertext = $cipher->encrypt($plaintext);
// or passing the $key as optional paramter
// $ciphertext = $cipher->encrypt($plaintext, $key);
$result = $cipher->decrypt($ciphertext);
// or passing the $key as optional paramter
// $result = $cipher->decrypt($ciphertext, $key);
print ($result === $plaintext) ? "OK" : "FAILURE";

至于解析 TOKEN 里面所带的参数,可使用之前的文章【使用 JWT 创建 Token】上写的方法。

参考:https://github.com/ezimuel/phpcrypto

 

转载请注明:隨習筆記 » PHP笔记:SymmetricSecurityKey 的验证和解析