"autoload": { "classmap": [ "app/commands", "app/controllers", "app/models", "app/database/migrations", "app/database/seeds", "app/tests/TestCase.php" ], "psr-0": { "MySecureApp": "app/lib" } },
composer dump-autoload
/ app / lib / MySecureApp / Cryptography - classes that directly implement encryption / Dto - Data Transfer Objects / Responses - our API response classes / Facades - Facades for convenient access to some classes / Filters - filters / Helpers - helpers classes / Providers - Service providers registering our functionality in the Laravel application.
{ "data": "<AES encrypted JSON object>", "sign": "<RSA signature of data>" }
set OPENSSL_CONF = \ path \ to \ openssl.cfg
openssl genrsa -aes256 -out temp.key 1024 openssl rsa -in temp.key -out private.key
openssl req -new -x509 -nodes -sha1 -key private.key -out public.crt -days 365000
'privateKey' => 'private.key',
<?php namespace MySecureApp\Helpers; class Base64 { public static function UrlDecode($x) { return base64_decode(str_replace(array('_','-'), array('/','+'), $x)); } public static function UrlEncode($x) { return str_replace(array('/','+'), array('_','-'), base64_encode($x)); } }
<?php namespace MySecureApp\Cryptography; use MySecureApp\Helpers\Base64; class Cryptography { /** * RSA instance * @var \Crypt_RSA */ protected $rsa; /** * RSA private key * @var string */ protected $rsaPrivateKey; /** * Whether RSA instance is initialized * @var bool */ private $isRsaInitialized = false; /** * Initializes the RSA instance using either provided private key file or config value * @param String $privateKeyFile Path to private key file * @throws Exception */ public function initRsa($privateKeyFile = '') { // } /** * Decrypts RSA-encrypted data * @param String $data Data to decrypt * @return String */ public function rsaDecrypt($data) { // } /** * Encrypts data using RSA * @param String $data Data to encrypt * @return String */ public function rsaEncrypt($data) { // } /** * Signs provided data * @param String $data Data to sign * @throws \Exception * @return string Signed data */ public function rsaSign($data) { // } }
A small note : the proposed version of the Cryptography class does not quite correspond to the principles of SOLID design. I am doing this intentionally for the purpose of simplifying the material. I will tell you how to improve it at the end of the article.
public function initRsa($privateKeyFile = '') { // , if (!$privateKeyFile) { $privateKeyFile = app_path() . '/keys/' . \Config::get('app.privateKey'); } // , if (!\File::exists($privateKeyFile)) { Log::error("Error reading private key file."); throw new Exception("Error reading private key file."); } $this->rsaPrivateKey = \File::get($privateKeyFile); // RSA $rsa = new \Crypt_RSA(); $rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1); $rsa->loadKey($this->rsaPrivateKey); // // true // () $this->rsa = $rsa; $this->isRsaInitialized = true; }
public function rsaDecrypt($data) { // RSA if (!$this->isRsaInitialized) { $this->initRsa(); } // return $this->rsa->decrypt(Base64::UrlDecode($data)); } // ... public function rsaEncrypt($data) { // rsaDecrypt if (!$this->isRsaInitialized) { $this->initRsa(); } return Base64::UrlEncode($this->rsa->encrypt($data)); } // ... public function rsaSign($data) { if (!$this->isRsaInitialized) { $this->initRsa(); } // , PHP- openssl if (!function_exists('openssl_sign')) { throw new \Exception("OpenSSL is not enabled."); } // $signature = ''; $keyId = openssl_get_privatekey($this->rsaPrivateKey); openssl_sign($data, $signature, $keyId); openssl_free_key($keyId); return $signature; }
/** * AES instance * @var \Crypt_AES */ protected $aes; /** * Whether AES instance is initialized * @var bool */ private $isAesInitialized = false;
/** * Initializes AES instance using either provided $options or session values * @param array $options Array of options, containing 'key' and 'iv' values * @throws Exception */ public function initAes($options = array()) { // ... } /** * Encrypts data using AES * @param String $data Data to encrypt * @return String */ public function aesEncrypt($data) { // ... } /** * Decrypts AES encrypted data * @param String $data Data to decrypt * @return String */ public function aesDecrypt($data) { // ... }
public function initAes($options = array()) { // $options , if (empty($options) && Session::has('aes_key') && Session::has('aes_iv')) { $options = array( 'key' => Session::get('aes_key'), 'iv' => Session::get('aes_iv'), ); } // , if (!(isset($options['key']) && isset($options['iv']))) { Log::error("Either key or iv not set"); throw new Exception("Either key or iv not set"); } // Session::put('aes_key', $options['key']); Session::put('aes_iv', $options['iv']); // Crypt_AES, phpseclib $aes = new \Crypt_AES(CRYPT_AES_MODE_CBC); $aes->setKeyLength(256); $aes->setKey(Base64::UrlDecode($options['key'])); $aes->setIV(Base64::UrlDecode($options['iv'])); $aes->enablePadding(); // $this->aes = $aes; $this->isAesInitialized = true; }
public function aesEncrypt($data) { // RSA if (!$this->isAesInitialized) { $this->initAes(); } return $this->aes->encrypt($data); } public function aesDecrypt($data) { if (!$this->isAesInitialized) { $this->initAes(); } return $this->aes->decrypt($data); }
<?php namespace MySecureApp\Cryptography; use MySecureApp\Helpers\Base64; /** * Provides funcitonality for getting decrypted Input paramters * (encrypted with AES) * Class DecryptedInput * @package MySecureApp\Cryptography */ class DecryptedInput { /** * Array of raw (non-decrypted) input parameters * @var array */ protected $params; /** * Array of decrypted values * @var array */ protected $decryptedParams = array(); /** * @var Cryptography */ protected $crypt; /** * @param Cryptography $crypt Injected Cryptography object used for decrypting */ public function __construct(Cryptography $crypt) { // Cryptography // $this->crypt = $crypt; // $params $this->params = \Input::all(); } /** * Returns decrypted input parameter * @param $key * @return String */ public function get($key) { // , if (isset($this->decryptedParams[$key])) { return $this->decryptedParams[$key]; } // $value = $this->crypt->aesDecrypt(Base64::UrlDecode($this->params[$key])); // $this->decryptedParams[$key] = $value; // return $value; } /** * Returns all input params decrypted * @return array */ public function all() { // foreach ($this->params as $key => $value) { $this->decryptedParams[$key] = $this->get($key); } // return $this->decryptedParams; } /** * Returns only specified input parameters * @return array */ public function only() { $args = func_get_args(); $result = array(); foreach($args as $arg) { $result[$arg] = $this->get($arg); } return $result; } }
<?php namespace MySecureApp\Facades; use Illuminate\Support\Facades\Facade; class DecryptedInput extends Facade { protected static function getFacadeAccessor() { // " ", // DecryptedInput return 'decryptedinput'; } }
<?php namespace MySecureApp\Providers; use Illuminate\Foundation\AliasLoader; use Illuminate\Support\ServiceProvider; use MySecureApp\Cryptography\Cryptography; use MySecureApp\Cryptography\DecryptedInput; class CryptoServiceProvider extends ServiceProvider { /** * Register the service provider. * * @return void */ public function register() { // Cryptograpgy $this->app->singleton('cryptography', function() { return new Cryptography(); }); // Input' 'decryptedinput' $this->app['decryptedinput'] = $this->app->share(function($app) { return new DecryptedInput($app['cryptography']); }); // DirectInput' $this->app->booting(function() { $loader = AliasLoader::getInstance(); $loader->alias('DecryptedInput', 'MySecureApp\Facades\DecryptedInput'); }); } }
// ... $email = DecryptedInput::get('email'); $password = DecryptedInput::get('password'); // ... extract(DecryptedInput::only('email', 'password')); // 2 : // $email $password
// ... $this->app['decryptedinput'] = $this->app->share(function($app) { // DecryptedInput // Input', ) , // ) // use MySecureApp/Cryptography/DecryptedInput // , // Cryptography, return new DecryptedInput($app['cryptography']); }); // ...
<?php namespace MySecureApp\Filters; use MySecureApp\Cryptography\Cryptography; use MySecureApp\Helpers\Base64; /** * Class OutgoingCryptFilter * Encrypts and signs the response * * @package MySecureApp\Filter */ class OutgoingCryptFilter { private $crypt; public function __construct(Cryptography $crypt) { // , // // Cryptography - Laravel $this->crypt = $crypt; } // public function filter($route, $request, $response) { // , // $content = $response->getOriginalContent(); if (!is_string($content)) { $content = json_encode($content); } // $content = Base64::UrlEncode($this->crypt->aesEncrypt($content)); // $sign = Base64::UrlEncode($this->crypt->rsaSign($content)); // ( ) : // 'data' => $content, - // 'sign' => $sign, - // "" , $response->setContent(['data' => $content, 'sign' => $sign]); } }
// cryptOut Route::filter('cryptOut', 'MySecureApp\Filters\OutgoingCryptFilter');
<?php use MySecureApp\Cryptography\Cryptography; class ApiController extends BaseController { /** * @var Crypt */ private $crypt; public function __construct(Cryptography $crypt) { $this->crypt = $crypt; // after- ( ) // : except: // postInit, // $this->afterFilter('cryptOut', array('except' => 'postInit')); } // () // : RSA- // AES . : key iv public function postInit() { // , if (!(Input::has('key') && Input::has('iv'))) { return 'ERROR 1'; } // $key $iv extract(Input::only('key', 'iv')); // $key = $this->crypt->rsaDecrypt($key); $iv = $this->crypt->rsaDecrypt($iv); // == false ( ) // if (!($key && $iv)) { return 'ERROR 2'; } // AES $this->crypt->initAes(array( 'key' => $key, 'iv' => $iv, )); return 'OK'; } }
<?php namespace MySecureApp\Dto\Responses; abstract class ResponseBase implements \JsonSerializable { // public $type; }
<?php namespace MySecureApp\Dto\Responses; class LoginResponse extends ResponseBase { const LOGIN_SUCCESS = true; const LOGIN_FAIL = false; public $loginResult; // public $expire; // , public function __construct() { $this->type = 'login'; $this->expire = '0000-00-00 00:00:00'; } /** * (PHP 5 >= 5.4.0)<br/> * Specify data which should be serialized to JSON * @link http://php.net/manual/en/jsonserializable.jsonserialize.php * @return mixed data which can be serialized by <b>json_encode</b>, * which is a value of any type other than a resource. */ public function jsonSerialize() { return [ 'type' => $this->type, 'loginResult' => $this->loginResult, 'expire' => $this->expire, ]; } }
public function postLogin() { // $creds = [ 'email' => DecryptedInput::get('email'), 'password' => DecryptedInput::get('password'), ]; $response = new \MySecureApp\Dto\Responses\LoginResponse; if (!Auth::attempt($creds, false)) { // , loginResult $response->loginResult = \MySecureApp\Dto\Responses\LoginResponse::LOGIN_FAIL; // return json_encode($response); } $response->loginResult = \MySecureApp\Dto\Responses\LoginResponse::LOGIN_SUCCESS; $response->expire = Auth::user()->tariffExpire; return json_encode($response); }
Route::controller('api', 'ApiController');
'providers' => array( // ... 'MySecureApp\Providers\CryptoServiceProvider', ),
Source: https://habr.com/ru/post/209612/
All Articles