Laravel cookie encryption - php

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"}

Related

Verifying JWT tokens generated with PHP in Node

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).

Firebase/JWT encryption with public Key

Hi i'm developing an architecture with an application authorization server(AS) and some client applications(CA) in PHP.
Users that are signing in a CA will login by SSO on AS and on success will be redirect to CA with some parameters stored in a JWT token.
I generate a pair of keys to implement asymmetric encryption, where private is secret and stored on the AS and public is shared around CAs.
I'm using Firebase/JWT library for PHP and i'm able to encode token generated by AS using JWT::encode($this->token, $privateKey, 'RS256') with AS private key.
At the same way the CA receives the token and decode it using
JWT::decode($jwt_token, $key, array('RS256'))
and all works fine...
But i want to introduce another secure step at the beginning.
I want generate a token in the CA(with the same library), containing referral CA infos (like app code, and other CA parameters to send to AS) before this were redirect to AS for login and i want to encrypt this token with public shared key of AS so that only AS can decrypt the token with its own private key.
In simple words Bob(CA) encrypt a message with Alice's(AS) public key so that only Alice can decrypt it with her own private key.
I'm trying to do that with same method
JWT::encode($this->token, $_ALICES_PUBLIC_KEY, 'RS256')
but it seems that the JWT::encode method only accept PRIVATE type keys and i'm getting the following error
PHP Warning: openssl_sign(): supplied key param cannot be coerced into a private key
Any help is appreciated. thanks

What is the significance of Application key in a Laravel Application?

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.

can we rely on laravel encryption for future?

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.

Verifying JWT from Azure Active Directory

I have followed the instructions here to obtain an access token for a web API.
https://msdn.microsoft.com/en-us/library/azure/dn645542.aspx
I have this working but the documentation is vague when it comes to figuring out how to validate the token in PHP.
You can use the access token that is returned in the response to authenticate to a protected resources, such as a web API. Typically, the token is presented to the web API in an HTTP request using the Bearer scheme, which is described in RFC 6750. This specification explains how to use bearer tokens in HTTP requests to access protected resources.
When the web API receives and validates the token, it gives the native client application access to the web API.
How do I validate the JWT in application? I have a PHP framework which is using PHP openssl_verify() function with the token, signiture, key and algorithm but I receive error of when I use the private key from Azure with the SHA256 algorithm:
openssl_verify(): supplied key param cannot be coerced into a public key
This leads me to believe that the key I am using in PHP to validate is not correct. At the moment, I am using the private key I generated for the Active Directory Application, which happens to also be the same value I am using for the "client_secret" parameter when hitting the oauth2/token url (any other value causes no token to be generated so this is probably correct).
The key is similar to (BUT IT NOT ACTUALLY):
cLDQWERTYUI12asdqwezxctlkjpoiAn7yhjeutl8jsP=
Where I beleive openssl needs to have a certificate... if so I can't seem to find where this certificate is in the Azure portal.
What am I missing here? What is the key I should be using with openssl_verify() to verify the JWT and where do I find it in Azure?
Thanks
--
UPDATE:
I have found the public keys here: https://login.windows.net/common/discovery/keys
However I still cannot use the X5C provided to verify the signature. How do you do this in PHP?
--
UPDATE 2:
I used a converted to create a .pem file for the public key using both the 'e' and 'n' parameters. This received a public key.
Now I get OPEN SSL errors when decrypting with it:
error:0906D06C:PEM routines:PEM_read_bio:no start line
Closing this question as I have moved on from the origional issue. Updated my question with comments showing how I have progressed.
Created a new question for the new specific issue: How do I verify a JSON Web Token using a Public RSA key?
--
Just in case it helps anyone else:
For further information on a solution to obtaining a public key from Microsoft in PHP I did the following:
$string_microsoftPublicKeyURL = 'https://login.windows.net/common/discovery/keys';
$array_publicKeysWithKIDasArrayKey = loadKeysFromAzure($string_microsoftPublicKeyURL);
function loadKeysFromAzure($string_microsoftPublicKeyURL) {
$array_keys = array();
$jsonString_microsoftPublicKeys = file_get_contents($string_microsoftPublicKeyURL);
$array_microsoftPublicKeys = json_decode($jsonString_microsoftPublicKeys, true);
foreach($array_microsoftPublicKeys['keys'] as $array_publicKey) {
$string_certText = "-----BEGIN CERTIFICATE-----\r\n".chunk_split($array_publicKey['x5c'][0],64)."-----END CERTIFICATE-----\r\n";
$array_keys[$array_publicKey['kid']] = getPublicKeyFromX5C($string_certText);
}
return $array_keys;
}
function getPublicKeyFromX5C($string_certText) {
$object_cert = openssl_x509_read($string_certText);
$object_pubkey = openssl_pkey_get_public($object_cert);
$array_publicKey = openssl_pkey_get_details($object_pubkey);
return $array_publicKey['key'];
}
Its better however to cache these to disk so your not loading these them every time, but this is just a simple example of how to do this.
Then, using the array of public keys, check the JWT header for the 'kid' value to find the correct public cert to verify against and use this in parameter 3 within openssl_verify(). I used the JWT class to deal with this for me.
Using this public key array created above and the JWT class should allow you to validate microsoft JWTs.
Link to JWT class from firebase: https://github.com/firebase/php-jwt
Call JWT::Decode with param 1 of your JWT, param 2 of this public key array and param three of an array of just 'RS256'.
JWT::decode($string_JSONWebToken, $array_publicKeysWithKIDasArrayKey, array('RS256'));
This will throw an exception if the JWT is invalid or return a decrypted JWT for you to use (and check the claims).
If you want to verify the jwt then go to jwt.io
This will allow you to paste the JWT and it will then verify the header, claims, and if you add the Public key or private key (depending how the server verifies the signature) it will also verify the signature of the JWT.

Categories