from laravel docs
Application Key The next thing you should do after installing Laravel
is set your application key to a random string. If you installed
Laravel via Composer or the Laravel installer, this key has already
been set for you by the php artisan key:generate command.
Typically, this string should be 32 characters long. The key can be
set in the .env environment file. If you have not renamed the
.env.example file to .env, you should do that now. If the application
key is not set, your user sessions and other encrypted data will not
be secure!
What I know about application key is: If the application key is not set, generally I do get an exception.
How do this random string help to secure the session?
What are the other uses of this application key?
If I use the same application key everywhere (like staging, production etc..) does it make the application less secure?
what are some best practices for this key
As we can see its used in EncryptionServiceProvider:
public function register()
{
$this->app->singleton('encrypter', function ($app) {
$config = $app->make('config')->get('app');
// If the key starts with "base64:", we will need to decode the key before handing
// it off to the encrypter. Keys may be base-64 encoded for presentation and we
// want to make sure to convert them back to the raw bytes before encrypting.
if (Str::startsWith($key = $this->key($config), 'base64:')) {
$key = base64_decode(substr($key, 7));
}
return new Encrypter($key, $config['cipher']);
});
}
So every component that uses encryption: session, encryption (user scope), csrf token benefit from the app_key.
Rest of the questions can be answered by "how encryption" (AES) works, just open up Encrypter.php, and confirm that Laravel uses AES under the hood and encodes the result to base64.
Further more we can see how its all done by using tinker:
➜ laravel git:(staging) ✗ art tinker
Psy Shell v0.8.17 (PHP 7.1.14 — cli) by Justin Hileman
>>> encrypt('Hello World!')
=> "eyJpdiI6ImgzK08zSDQyMUE1T1NMVThERjQzdEE9PSIsInZhbHVlIjoiYzlZTk1td0JJZGtrS2luMlo0QzdGcVpKdTEzTWsxeFB6ME5pT1NmaGlQaz0iLCJtYWMiOiI3YTAzY2IxZjBiM2IyNDZiYzljZGJjNTczYzA3MGRjN2U3ZmFkMTVmMWRhMjcwMTRlODk5YTg5ZmM2YjBjMGNlIn0="
Note: I used this key: base64:Qc25VgXJ8CEkp790nqF+eEocRk1o7Yp0lM1jWPUuocQ= to encrypt Hello World!
After decoding the result we get (you can try decode your own cookie with session):
{"iv":"h3+O3H421A5OSLU8DF43tA==","value":"c9YNMmwBIdkkKin2Z4C7FqZJu13Mk1xPz0NiOSfhiPk=","mac":"7a03cb1f0b3b246bc9cdbc573c070dc7e7fad15f1da27014e899a89fc6b0c0ce"}
to understand above json (iv, value, mac) you need to understand AES:
https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
Best practices for application key
do store it in .env file only
do not store it in app.php, in fact in any git tracked file
do not change it unless you really want to
invalidate sessions/cookies (user logout)
invalidate password reset tokens
invalidate signed urls
Obvious Note: Changing application key has no effect on hashed passwords since hashing algorithms do not require encryption keys.
Related
I'm working on implementing JWT verification in a client-side web-application that's using Webpack5. When a user is created on the backend running PHP, I create a public and private keypair for use in JWT like this and store them:
$keyPair = sodium_crypto_sign_keypair();
$privateKey = base64_encode(sodium_crypto_sign_secretkey($keyPair));
$publicKey = base64_encode(sodium_crypto_sign_publickey($keyPair));
Then with firebase/php-jwt create the request like this:
$jwt = JWT::encode($payload, $privateKey, 'EdDSA');
However, using jose I get the following error when attempting to verify the JWT:
import * as jose from 'jose';
const { payload, protectedHeader } = await jose.jwtVerify(response.token, window.atob(response.key));
console.log(payload);
console.log(protectedHeader);
TypeError: Key must be of type CryptoKey.
I'm not understanding how it wants the key as both the base64 string and base64 presentation of the publicKey is met with the same TypeError in node. What am I doing wrong here?
Edwards-Curve Digital Signature Algorithm (EdDSA in JOSE) is not supported by WebCryptography API, as such you won't succeed with any library that uses native browser crypto. Sadly.
There's a proposal for Ed25519, Ed448, X25519, and X448 to be added in WebCrypto but it'll take a while before adoption and then implementation. When that happens jose will support this action. That being said you'll need to pass a CryptoKey. That's either imported via crypto.subtle.importKey directly, or via jose's import key functions. SubtleCrypto supports raw public key imports, jose supports PEM key imports in its wrappers only.
See Browser Support for the browser algorithm support matrix, as well as Algorithm Key Requirements for algorithm key requirements, and Type alias: KeyLike for how to obtain the right key representation.
As for the other (now deleted) suggestions, they do not take into account the algorithm you use, suggesting to use Uint8Array instead of a CryptoKey is wrong, Uint8Array use is exclusive for symmetric algorithms. Likewise, using transpiled jsonwebtoken is only remotely reliable for HMAC based algorithms.
jose only uses the runtime's native crypto layer which is your best bet at conformance and secure implementation but when running cross-runtimes you need the consider the intersection of supported algorithms on the platform.
I'm not understanding how it wants the key as both the base64 string and base64 presentation of the publicKey is met with the same TypeError in node
It's supposed to be KeyLike (interface) - in Node that's KeyObject (see crypto documentation), in Web-like environments it's CryptoKey (see WebCryptoAPI).
So I have made an encryption middleware in laravel that encrypts data using the following code
public function handle($request, Closure $next)
{
return response()->json(encrypt($response->content()),$response->status());
}
I ran the command
php artisan:generate key
So now I'm trying to decrypt this data from flutter I tried using encrypt package in flutter but still no luck in making it work.
I feel the problem is the following:
1- The key used in encryption ni laravel is APP_KEY right it is in the format base64:random string this should be my key without base64?
2- Flutter encryption package needs IV I keep trying to set it from the key but still failing would really appreciate any help.
I have never done this between laravel and dart, but i have between two laravel apps.
// bits stolen from the laravel EncryptionServiceProvider.php
if (Str::startsWith($key = env('OTHER_APPS_API_APP_KEY'), 'base64:')) {
$key = base64_decode(substr($key, 7));
}
$encrypter = new Encrypter($key, config('app.cipher')); // probably AES-256-CBC
// decrypt the data
$encrypter->decrypt($theDataToDecrypt);
If you can find a library in dart that supports the same ciper you shoud be able to do the same
Is Laravel hashing differently request and response cookies?
I am using main domain and subdomains, and have set up CORS and CSRF and if I exclude cookies from EncryptCookies class I see the same cookies in the response headers and request headers.
If I leave them to encrypt however, I am getting different "encryption strings", and don't know if that is acceptable behaviour?
EncryptCookies class is only listed under web part in Kernel.php
For Laravel Encryption:
Laravel's encrypter uses OpenSSL to provide AES-256 and AES-128
encryption. You are strongly encouraged to use Laravel's built-in
encryption facilities and not attempt to roll your own "home grown"
encryption algorithms. All of Laravel's encrypted values are signed
using a message authentication code (MAC) so that their underlying
value can not be modified once encrypted.
For each encryption, the value are encrypted with AES-256 / AES-128 with different initialization vector and signed with different MAC, even if you encrypt the same value, the payload, returned value of encrypt always different. For easier understanding, you can check this example:
$value = Crypt::encrypt('foo');
// eyJpdiI6ImVoNEVlVWpnYUdwZ1JHRlJWSGlTZEE9PSIsInZhbHVlIjoiVThpWjJNWVBqZnVsWjhLVWNDXC85VHc9PSIsIm1hYyI6IjFjMDRhOTM5ZThhOWRmYjk3Mzk0OWFmNTM3YWE1NDAzNzMxNWY5YTJmODMwNmQxZDE4NDllZGJkMjc1Y2I3ZmYifQ==
base64_decode($value);
// {"iv":"eh4EeUjgaGpgRGFRVHiSdA==","value":"U8iZ2MYPjfulZ8KUcC\/9Tw==","mac":"1c04a939e8a9dfb973949af537aa54037315f9a2f8306d1d1849edbd275cb7ff"}
The second attempt:
$value = Crypt::encrypt('foo');
// eyJpdiI6Ill5MmZleG5ycTBaZmQ5NnRDT3N3dVE9PSIsInZhbHVlIjoiTmgrRnlqajJjUk9qTk1qeHJLU21LUT09IiwibWFjIjoiNWEzZDRjZWMwMjg0ZDhlMjhlZWRiODg3ZWQ5MTcxN2I5N2JjY2ZmMzc0NTYyOTI5MThmOTk4YjAyZjM1YTRjMyJ9
base64_decode($value);
// {"iv":"Yy2fexnrq0Zfd96tCOswuQ==","value":"Nh+Fyjj2cROjNMjxrKSmKQ==","mac":"5a3d4cec0284d8e28eedb887ed91717b97bccff37456292918f998b02f35a4c3"}
We are building application where we need to store a data encrypted in database and instead of using MySql AES_ENCRYPT and AES_DECRYPT we are plaining to use laravel's inbuilt encrypt & decrypt functions.
Is it will be future proof as we don't want to loose data for future updates.
First of all, nothing is truly "future proof." In fact, we're on the verge of current encryption being rendered obsolete by quantum computing, making all current encryption methods very much not future proof.
Does Taylor have any plans of changing it in the foreseeable future? Maybe, maybe not, but the only real way of knowing is to ask him directly. He's quite active on Twitter and in other venues, so as far as business owners go, he's pretty approachable. He's also a generally nice person, so don't be afraid to ping him.
But let's take a look at the code:
public function encrypt($value, $serialize = true)
{
$iv = random_bytes(16);
// First we will encrypt the value using OpenSSL. After this is encrypted we
// will proceed to calculating a MAC for the encrypted value so that this
// value can be verified later as not having been changed by the users.
$value = \openssl_encrypt(
$serialize ? serialize($value) : $value,
$this->cipher, $this->key, 0, $iv
);
if ($value === false) {
throw new EncryptException('Could not encrypt the data.');
}
// Once we get the encrypted value we'll go ahead and base64_encode the input
// vector and create the MAC for the encrypted value so we can then verify
// its authenticity. Then, we'll JSON the data into the "payload" array.
$mac = $this->hash($iv = base64_encode($iv), $value);
$json = json_encode(compact('iv', 'value', 'mac'));
if (! is_string($json)) {
throw new EncryptException('Could not encrypt the data.');
}
return base64_encode($json);
}
That's the main encrypt() function from master in the repository, and from the looks of it, it's not likely to be changed too much without completely rewriting it. And while Laravel doesn't really follow the SemVer versioning spec, it does generally follow an internally consistent versioning scheme, making the most likely times for it to change are at the whole number and first-decimal change (i.e. - 5.4 to 5.5 or 5.5 to 6.0).
However, it's worth noting that it's actually accessed via contracts and the service provider pattern (so the only time the class is actually directly referenced is in its associated ServiceProvider class). This means that you can use this one for now and if a breaking change is introduced in the future, you can copy this version into your own encryption class, replace the reference in config/app.php to Illuminate\Encryption\EncryptionServiceProvider to your new encryption service provider, and you've now preserved that method and can use it throughout your application, without making any other changes to your application.
On a bit of a side note, you can also consider writing an "encryption converter" if you find you do need to change algorithms (such as if your original algorithm is insecure) by using the old system's decrypt method to decrypt everything, then re-encrypt it all with the new system and storing it again. The application would then just use the new algorithm going forward.
for a customer we are migrating a Laravel application to a Ruby application. We have some data stored in the database that we would like to decrypt in the ruby world.
This is the laravel part that was used to encrypt the data: https://laravel.com/docs/5.0/encryption
Now when importing the data to ruby we need a counter part that can decrypt the data.
In the laravel console I was able to decrypt the data like this:
>>> Crypt::decrypt('eyJpdiI6ImZyek9ZTjJNSW5ZYlhSa2ZYUldVbEE9PSIsInZhbHVlIjoia20zMTRLWEpCdXM2K05DZDBHSlE5SDlcL2pYVXk5aE5RWWR3dHFQT1dGQzA9IiwibWFjIjoiZWZlNGE3NTRhMDDlNzk2MjhlYjI1Mzc1NGNiYmRjNDMwZjM1NzdiMzkyZTU4ZjA4ZDNkMGE0YjUyOTBjMDAzOCJA')
=> "123123123123"
I am no laravel expert, but in the app.php file a secret key was set. So I need to be able to pass it somehow to the decrypt function.
The goal is to have a ruby function that takes the laravel password, encryption key and returns the decrypted value.
def decrypt_laravel_crypt(value, encryption_key)
end
Thanks for the help!
You can reverse engineer Laravel's Encrypter::decrypt method using mostly standard libs. Laravel will serialize data before encrypting it unless told explicitly not to. This means attempting to unserialize the decrypted string prior to using it.
require 'base64'
require 'openssl'
require 'json'
require 'php_serialize'
def lara_decrypt(encryptedString, appKey)
data = JSON.parse(Base64::decode64(encryptedString))
decipher = OpenSSL::Cipher.new('aes-256-cbc')
decipher.decrypt
decipher.key = Base64::decode64(appKey)
decipher.iv = Base64::decode64(data['iv'])
decrypted = decipher.update(Base64::decode64(data['value'])) + decipher.final
begin
PHP.unserialize(decrypted)
rescue
decrypted
end
end
Also when you set the app key be sure to drop base64: from the beginning of it (if it's there).