├── .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 |
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 |