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).
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
We have an implementation of openid connect which returns an encoded id token, this works well and has been working for a while. However we are attempting to connect to it using cognito in aws and after a bit of trial and error we have found that we are missing a .well-known/openid-configuration file.
This file is meant to contain information about the calls to the openid-connect server we have including the JWK keys.
I don't understand JWK keys, this means:
1. How to generate them
2. Once generated what to do with them?
3. Does the exising code we have for the openid-connect need to change and use the JWK keys?
4. Is there any way of validating an openid-connect configuration?
I have asked something similar a while back but with no avail but revisiting.
Thanks
Kevin
What Ive already tried:
Been here: https://mkjwk.org/ and clicked on 'New Key', which returns what I assume is a web key.. however don't know what i'm meant to do with it or what the other tabs on that mean.
Expected Results:
Expecting AWS Cognito to continue and allow it to connect to our open id connect implementation. Currently just get an error with regards to a missing well known configuration file.
The openid connect server you have generates ID token that are JWS.
These tokens are signed using a private key (EC, RSA or OKP).
The well-known/openid-configuration is should contain a JSON object that indicates what algorithms are used to sign the tokens and the url to get the public keys associated to the private keys used to sign the token.
For example, the Google Account server configuration indicates the public keys can be found at https://www.googleapis.com/oauth2/v3/certs (jwks_uri parameter).
At this url, you will find a list of keys (JWKSet) formatted in JWK (JSON Web Keys). These formats (JWK and JWKSet) are the standrad representations for keys and key sets used in a JWT context (see RFC7517).
How to generate them
Normally you already have at least one private key to sign your tokens so you don’t have to generate a new one.
Once generated what to do with them?
The private key you have should be a PEM (starting with ------ PRIVATE RSA KEY ----- or similar) or DER key.
You just have to convert this key into JWK.
I created a small PHAR application (PHP) that will help to convert your keys. This app is part of the Web Token Framework but can be installed as a standalone app. Make sure you have PHP 7.1, OpenSSL and JSON extensions installed.
When the app is installed, you can execute the following command:
jose key:convert:public $(jose key:load:key PATH_TO_YOUR_KEY)
This command will convert the private key into JWK and then convert it from private to public.
The result can be shared through the jwks_uri endpoint.
Does the exising code we have for the openid-connect need to change and use the JWK keys?
No. The way you build your tokens does not means you have to change anything.
Is there any way of validating an openid-connect configuration?
As far as I know, there is no tools to validate the configuration object. You have to refer to the OpenID Connect specification.
While Florent's answer the usage of public key, I like to point you to the specification that defines the contents of JWK and specific implementation details.
OpenID Connect discovery define the discovery document. There you find jwks_uri URL as part of the metadata, which points to JSON Web Key Set
3. OpenID Provider Metadata
wks_uri
REQUIRED. URL of the OP's JSON Web Key Set [JWK] document. This
contains the signing key(s) the RP uses to validate signatures from
the OP
Now this points to RFC7517 - JSON Web Key (JWK). You can find exact details of each field of the JSON payload there. Refer its samples and appendix section to see sample contents. And your implementation must follow specification requirements defined by this.
For coding, if you are using JAVA, I suggest you to use Nimbus JOSE+JWT library. Sample can be found from this link.
For example, if you already have X509Certificate loaded, then following is the minimalistic code to generate JWK. It is embedded into a Servlet [Full source].
import Common.CertificateLoader;
import Common.Exceptions.FrameworkUncheckedException;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.jwk.JWK;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.cert.X509Certificate;
#WebServlet(urlPatterns = "/openid-configuration/jwks_uri")
public class JWKDocument extends HttpServlet {
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
// NOTE - LOAD YOUR CERTIFICATE HERE
final X509Certificate certificate = CertificateLoader.getCertificateLoader().getX509Certificate();
final JWK jwk;
try {
jwk = JWK.parse(certificate);
} catch (JOSEException e) {
throw new FrameworkUncheckedException("Error while loading to JWK", e);
}
resp.getOutputStream().print(jwk.toJSONString());
}
}
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.
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.