<?php /** * This file is part of CodeIgniter 4 framework. * * (c) CodeIgniter Foundation <admin@codeigniter.com> * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace CodeIgniter\Commands\Encryption; use CodeIgniter\CLI\BaseCommand; use CodeIgniter\CLI\CLI; use CodeIgniter\Config\DotEnv; use CodeIgniter\Encryption\Encryption; /** * Generates a new encryption key. */ class GenerateKey extends BaseCommand { /** * The Command's group. * * @var string */ protected $group = 'Encryption'; /** * The Command's name. * * @var string */ protected $name = 'key:generate'; /** * The Command's usage. * * @var string */ protected $usage = 'key:generate [options]'; /** * The Command's short description. * * @var string */ protected $description = 'Generates a new encryption key and writes it in an `.env` file.'; /** * The command's options * * @var array */ protected $options = [ '--force' => 'Force overwrite existing key in `.env` file.', '--length' => 'The length of the random string that should be returned in bytes. Defaults to 32.', '--prefix' => 'Prefix to prepend to encoded key (either hex2bin or base64). Defaults to hex2bin.', '--show' => 'Shows the generated key in the terminal instead of storing in the `.env` file.', ]; /** * Actually execute the command. */ public function run(array $params) { $prefix = $params['prefix'] ?? CLI::getOption('prefix'); if (in_array($prefix, [null, true], true)) { $prefix = 'hex2bin'; } elseif (! in_array($prefix, ['hex2bin', 'base64'], true)) { $prefix = CLI::prompt('Please provide a valid prefix to use.', ['hex2bin', 'base64'], 'required'); // @codeCoverageIgnore } $length = $params['length'] ?? CLI::getOption('length'); if (in_array($length, [null, true], true)) { $length = 32; } $encodedKey = $this->generateRandomKey($prefix, $length); if (array_key_exists('show', $params) || (bool) CLI::getOption('show')) { CLI::write($encodedKey, 'yellow'); CLI::newLine(); return; } if (! $this->setNewEncryptionKey($encodedKey, $params)) { CLI::write('Error in setting new encryption key to .env file.', 'light_gray', 'red'); CLI::newLine(); return; } // force DotEnv to reload the new env vars putenv('encryption.key'); unset($_ENV['encryption.key'], $_SERVER['encryption.key']); $dotenv = new DotEnv(ROOTPATH); $dotenv->load(); CLI::write('Application\'s new encryption key was successfully set.', 'green'); CLI::newLine(); } /** * Generates a key and encodes it. */ protected function generateRandomKey(string $prefix, int $length): string { $key = Encryption::createKey($length); if ($prefix === 'hex2bin') { return 'hex2bin:' . bin2hex($key); } return 'base64:' . base64_encode($key); } /** * Sets the new encryption key in your .env file. */ protected function setNewEncryptionKey(string $key, array $params): bool { $currentKey = env('encryption.key', ''); if (strlen($currentKey) !== 0 && ! $this->confirmOverwrite($params)) { // Not yet testable since it requires keyboard input // @codeCoverageIgnoreStart return false; // @codeCoverageIgnoreEnd } return $this->writeNewEncryptionKeyToFile($currentKey, $key); } /** * Checks whether to overwrite existing encryption key. */ protected function confirmOverwrite(array $params): bool { return (array_key_exists('force', $params) || CLI::getOption('force')) || CLI::prompt('Overwrite existing key?', ['n', 'y']) === 'y'; } /** * Writes the new encryption key to .env file. */ protected function writeNewEncryptionKeyToFile(string $oldKey, string $newKey): bool { $baseEnv = ROOTPATH . 'env'; $envFile = ROOTPATH . '.env'; if (! file_exists($envFile)) { if (! file_exists($baseEnv)) { CLI::write('Both default shipped `env` file and custom `.env` are missing.', 'yellow'); CLI::write('Here\'s your new key instead: ' . CLI::color($newKey, 'yellow')); CLI::newLine(); return false; } copy($baseEnv, $envFile); } $ret = file_put_contents($envFile, preg_replace( $this->keyPattern($oldKey), "\nencryption.key = {$newKey}", file_get_contents($envFile) )); return $ret !== false; } /** * Get the regex of the current encryption key. */ protected function keyPattern(string $oldKey): string { $escaped = preg_quote($oldKey, '/'); if ($escaped !== '') { $escaped = "[{$escaped}]*"; } return "/^[#\\s]*encryption.key[=\\s]*{$escaped}$/m"; } }