├── .gitignore ├── README.md ├── composer.json ├── laravel-logo.png └── src ├── ReEncrypter.php └── ReEncrypterException.php /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /vendor 3 | .idea 4 | .vscode 5 | .vagrant -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Re-encrypt after APP_KEY rotation 2 | 3 | A helper library to re-encrypt the existing encrypted data when you rotate your Laravel APP_KEY 4 | 5 |

6 | Laravel 7 |

8 | 9 | The APP_KEY is used to keep your user sessions and other encrypted data secure! If the application key is not set, your user sessions and other encrypted data will not be secure. Believe it or not it is a big security risk. 10 | 11 | To give you more specific context, earlier laravel had a security issue : 12 | 13 | If your application's encryption key is in the hands of a malicious party, that party could craft cookie values using the encryption key and exploit vulnerabilities inherent to PHP object serialization / unserialization, such as calling arbitrary class methods within your application. 14 | 15 | Hence, it is important to rotatate your APP_KEY in frequent invertals. [`Know More`](https://techsemicolon.github.io/blog/2019/06/10/aws-update-ami-systems-manager-automation/) 16 | 17 | 18 | ## How can you use this package : 19 | 20 | When APP_KEY is changed in an existing app, any data in your app which you have encrypted using Crypt facade or encrypt() helper function will no longer be decrypted as the encryption uses the APP_KEY. 21 | 22 | So when you run `php artisan key:generate` to have a new key as part of key rotation, you need to first decrypt the old encrypted using old APP_KEY and then re-encrypt using newly generated APP_KEY. 23 | 24 | 25 | ## Installation : 26 | 27 | ~~~bash 28 | composer require techsemicolon/laravel-app-key-rotation 29 | ~~~ 30 | 31 | ## Usage : 32 | 33 | You can instantiate the `ReEncrypter` class by passing old APP_KEY. For that it is important for you to keep your old APP_KEY safe for reference before you rotate APP_KEY to a new one. 34 | 35 | ~~~php 36 | // This is your old APP_KEY 37 | $oldAppKey = "your_old_app_key"; 38 | 39 | // Instantiate ReEncrypter 40 | $eeEncrypter = new ReEncrypter($oldAppKey); 41 | 42 | // Re-cncrypt the oldEncryptedPayload value 43 | $newEncryptedPayload = $eeEncrypter->encrypt($oldEncryptedPayload); 44 | ~~~ 45 | 46 | ##Suggestion : 47 | 48 | When you update your database by new encrypted payload values, make sure you have another column in which you store the old encrypted payload value as a backup. This is to prevent any data loss during the key rotation. 49 | 50 | ##Example : 51 | 52 | Let's imagine we have a column called `bank_account_number` in `users` table which is stored as encrypted string. We have another column in the same table as `old_bank_account_number` to store old encrypted payload as backup. 53 | 54 | We can create a command `php artisan encryption:rotate` : 55 | 56 | ~~~php 57 | option('oldappkey'); 99 | 100 | // Instantiate ReEncrypter 101 | $eeEncrypter = new ReEncrypter($oldAppKey); 102 | 103 | 104 | User::all()->each(function($user) use($eeEncrypter){ 105 | 106 | // Stored value in a backup column 107 | $user->old_bank_account_number = $user->bank_account_number; 108 | 109 | // Re-cncrypt the old encrypted payload value 110 | $user->bank_account_number = $eeEncrypter->encrypt($user->bank_account_number); 111 | $user->save(); 112 | 113 | }); 114 | 115 | $this->info('Encryption completed with newly rotated key'); 116 | } 117 | } 118 | ~~~ 119 | 120 | For more detailes about why and how of laravel APP_KEY rotation, [`click here`](https://techsemicolon.github.io/blog/2019/06/10/aws-update-ami-systems-manager-automation/) 121 | 122 | ## License : 123 | 124 | This psckage is open-sourced software licensed under the MIT license -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"techsemicolon/laravel-app-key-rotation", 3 | "license":"MIT", 4 | "authors":[ 5 | { 6 | "name":"MihirBhende", 7 | "email":"mihirbhende1201@gmail.com" 8 | } 9 | ], 10 | "description":"A helper library to re-encrypt the existing encrypted data when you rotate your Laravel APP_KEY", 11 | "minimum-stability":"dev", 12 | "require":{ 13 | "php":">=5.4.0", 14 | "illuminate/support": "5.2.x|5.3.x|5.4.x|5.5.x|5.6.x|5.7.x|5.8.x" 15 | 16 | }, 17 | "autoload":{ 18 | "psr-4":{ 19 | "Techsemicolon\\KeyRotation\\":"src/" 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /laravel-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techsemicolon/laravel-app-key-rotation/fa69f4e2dc6cbbc0b09204c4bae59be949b79384/laravel-logo.png -------------------------------------------------------------------------------- /src/ReEncrypter.php: -------------------------------------------------------------------------------- 1 | setup($oldAppKey); 32 | } 33 | 34 | /** 35 | * Initiate the setup 36 | * 37 | * @param string $oldAppKey 38 | * 39 | * @return void 40 | */ 41 | private function setup($oldAppKey) 42 | { 43 | // Get cipher of encryption 44 | $cipher = config('app.cipher'); 45 | 46 | // Get newly generated app_key 47 | $newAppKey = config('app.key'); 48 | 49 | // Verify the keys 50 | $oldAppKey = (string)$this->verifyAppKey($oldAppKey); 51 | $newAppKey = (string)$this->verifyAppKey($newAppKey); 52 | 53 | // Initialize encrypter instance for old app key 54 | $this->oldEncrypter = $this->getEncrypterInstance($oldAppKey, $cipher); 55 | $this->newEncrypter = $this->getEncrypterInstance($newAppKey, $cipher); 56 | 57 | return $this; 58 | } 59 | 60 | /** 61 | * Verify the given app key 62 | * 63 | * @param string $key 64 | * 65 | * @return string 66 | */ 67 | private function verifyAppKey($key) 68 | { 69 | if (Str::startsWith($key, 'base64:')) { 70 | return base64_decode(substr($key, 7)); 71 | } 72 | 73 | return $key; 74 | } 75 | 76 | /** 77 | * Instantiate Encrypter instance based 78 | * on key and cipher 79 | * 80 | * @param string $key 81 | * @param string $cipher 82 | * 83 | * @return Encrypter 84 | */ 85 | private function getEncrypterInstance($key, $cipher) 86 | { 87 | return new Encrypter($key, $cipher); 88 | } 89 | 90 | /** 91 | * Encrypt the old encrypted hash 92 | * 93 | * @param string $payloadValue 94 | * @param bool $serialized 95 | * 96 | * @throws ReEncrypterException 97 | * @return string 98 | */ 99 | public function encrypt($payloadValue, $serialized = false) 100 | { 101 | try{ 102 | 103 | return $this->newEncrypter->encrypt($this->oldEncrypter->decrypt($payloadValue, $serialized), $serialized); 104 | } 105 | catch(DecryptException $e){ 106 | throw new ReEncrypterException('Either the passed old APP_KEY is incorrect or payload value is invalid!'); 107 | } 108 | } 109 | } -------------------------------------------------------------------------------- /src/ReEncrypterException.php: -------------------------------------------------------------------------------- 1 |