RSA from Android to PHP - php

I've been searching all day long(aprox. 12 hours) how to deal with this and couldn't find anything to work for me so I decided to make another question on the topic.
My setup:
Taking credentials from user
Encrypt them using RSA public key and then base64
Send to server
base64_decode, decrypt and check credentials
This is the code I use for encryption on Android:
public class Encrypter {
public static PublicKey loadPublicKey(Context context) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
InputStream is = context.getAssets().open("public_key.txt");
byte[] bytes = new byte[is.available()];
is.read(bytes);
PublicKey publicKey = KeyFactory.getInstance(RSA).generatePublic(
new X509EncodedKeySpec(Base64.decode(bytes)));
return publicKey;
}
private static byte[] enc(String text, PublicKey pubRSA) throws Exception {
Cipher cipher = Cipher.getInstance(RSA);
cipher.init(Cipher.ENCRYPT_MODE, pubRSA);
return cipher.doFinal(text.getBytes("UTF-8"));
}
public final static String encrypt(String text, PublicKey uk) {
try {
return Base64.encodeBytes(enc(text, uk));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
And how I use it:
String username = "admin";
PublicKey pubk = Encrypter.getPublicKey(this);
username = Encrypter.encrypt(username, pubk);
This is my public key without START and END:
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsfPDQ7tRqUw8oTPcNG3GR9OyhWnrtQXj2gmzTKdLncPIuolG1GTjYyZO25+cHSgHBlFmc20cKO0uasveZWCwuBTmMY3kVYtVblxUbHmHakLc2CRsKlCA3GAU/OAvfQfzcRUE0O1R138XcTydDGNgWKthqePJz97sEtg8nY55imVUTfRJPOeMvn1/vQQY50OTGniyoI+sh66P/0xpjrZQdIKaNADD5tQbDmrrJjoocC/GuwqBizi7rmU/p/9udNj5hCKqxL6PmejDsHss+UrhBx5t1/iem2yEBhvuOPDyvBPn/ZqAEKeAUKyf+Z5d3XQAlzJq5UjliRM2IA/K6uUBCQIDAQAB
My problem is that if I encrypt it using Android and then Base64.encode it, I get a different string than if I encrypt from PHP using the same public key
I tried encrypting the work "admin" on both sides and here's what I got:
String got from Android:
hF8ykDKLYVJOnHE5Uswq0+BasIRqTLnFIjvy2rLxfe/oDJ0GbhjTjoizNuSk2grKbAgkJqFN5uAFuhkqqjyxoSJlJNsQi5QRis9FDUIm1iAhjSD8olTBFky+q1pqYQsQ/Cj+9qSVTnoKpB2oJyeEk2Zx7mYegHKT/yItDtsSLa7fURaxygp1osj0Nz8pas21zXgMIyG2wKARG9IlxdBo4Vl2nj7iKwPCkHMrSeXzFjDsKOkwBzMoPuUUGSFUZ0QbL+b/Ha+Rgdb7oItzTvBfHsoL9m91j20NhqapKRYkJ2pPUhPPVDZtTzO/JPKK4ndzg32w7jKqb9zinOBcilQdGg==
String from PHP:
oh8fNeY8FwPqUkvJhFQr/2IPgdj7XEUNHjc7+KZwRGot+4DIQWtxv3N4UtzbpvkwcgI/kUjXZOz+mNSzvTEVpmZprOWBow/zlbCO7tLgH2Q131gATZdGxPEgOIVbTOWQkXL+d8x+jODnPhaXb8vUB2boQmd70ifBAq2C5mMCGPeA/gRwNquwdEG62W4zvaeXzXc6sXCXXvE6cgaWLOhZWFnLyo2ulFrkGk9XXOaWoS3HYnS35n8xHxulSEeAJOmGgEd56cSbDIlJrD9H5k6mb6PX0/eLoC7J9vdBhdM8nJVviL6NxtiOtDTCWVFb4k9il8Sksz8eascFM8yAB0KvRw==
For encryption in PHP I use phpseclib under Laravel 5.2 framework
Method to check credentials
public function creds_valid(Request $request) {
$inputs = $request->all();
$username = $inputs['username'];
$password = $inputs['password'];
$private_key = file_get_contents(storage_path() . "/phpseclib/public_key.txt");
$rsa = new RSA();
$rsa->loadKey($private_key);
$username = $rsa->decrypt(base64_decode($username));
$password = $rsa->decrypt(base64_decode($password));
$credentials = [
'username' => $username,
'password' => $password,
// 'active' => 1
];
return Auth::guard()->attempt($credentials);
}
Why aren't the two algorithms output the same string on encryption and base64? I suspect some encoding issues but I can't figure them out. Tried forcing UTF-8 on both but the result is the same.. Any ideas?
Thanks!
EDIT:
Using this base64 library for Java:
https://sourceforge.net/projects/iharder/files/base64/2.3/
The default one from Android gives the same results so, yeah.. :/
EDIT 2:
First comment suggested checking to see if any party changes the output on concurrent executions and weirdly, yes.. The PHP one.. Why?
This is an output from php artisan tinker
base64_encode($rsa->encrypt('admin'))
=> "T3dBTadyEjvSM96lCwELNzXHNh5bEJtW6QvgdrVT0wZ2KFq8Cs+s7/+IjSrISlNC+ygF+XPhYjEUHa+FSqzgT68KftcVQYL84w8Thbiy6ElLqs7WAbLAaKk10kBqEPNtI0jItOJFXGA07SsQu6g5+OUWfEFShBPg8uSxSYPsjhvATpw6lLCRtVQYqAL1MUwwmaho/ktih5UJgpiCYdwpa0ROiPboKloO0CFOAkR+5rybSLH73p3Fxf1y46qgA3BoPenillkMW8kgv1/bFj1rpRVi2Ca3ioMQt0fOPGchoe3ikz5I6IrrAK0aYhwj3kpoQ2+doLUV+EKXs8iFkA+cjQ=="
>>> base64_encode($rsa->encrypt('admin'))
=> "XJZrhFpw1UsBEC2yKMv0PVQ/H0vvBSXEZEsjQ4vlvjgktifCGAPsac3zwarlV2+TOoNKhn7nCqWer2Gz60UyoHS0xYyNvt/i3Ogsn2ccxhpKRtrB2CEMJOzSdLoUe+4JEObdU1b6SR4ysFbFEjFvo+zWUAfT7i2vM9bdZkwcrL6S2a4bBeWk3t2l91URatggzTOBFtxUB4zHTKyo1SSuR6uqm2q/Jbakj07e64ZZQmWiKY4inDZNgaVVLE+wNq++J4aLwRhSZG4wXuPgNeGHfwgZ3bytlRJUrrNXMXExa1C8eu/js3+WiYFSXPIiTeuQaxsih+suj0abDTFjJygNMA=="
>>> base64_encode($rsa->encrypt('admin'))
=> "DVAFTjZ1Ah6mpN699/PitPj0nLNLhz4zGux9uYLQHmANR/CYEt63+z0vE2xQI9oKE/V4K5a335wvJpz+70hMy6G30cKAwerZ8PudbZgnpGRaF0YlRwzZEQ+XqV5qdQXE6kb4plVZrYrpyHAiPTmO54V+UvCp2YPbNY3Qcr+vbIrn0CZJ7lMwgE9NCGWwiIJF6G6/z4zqc+UbBG9+WUtf16BU8CXhbeU4FOSeuxYr9xbGbjAvtUZXrXpaQPYwgCuWURTWcN2fxmm5fAZuU74rPVMW5slTxTVafcIcUI0bFuMdsx3xj9VIImYhmbM0DTXT/gm66nxSy7aAvj5ckmemfA=="
>>> base64_encode($rsa->encrypt('admin'))
=> "Po8lzluh+ChWdHBxvThrn06kZ1cugTvEvV6UU8JroeM2aYwX4XH9hMSt1U+XmKkmdgxLRXwKUMsYcT7rF7I92tLW6T6fjTED1HoXXWS35okc1zhPTvXFqvMdXq7r4meLrcPmJjfsJsKnso4Ws2aIzHjQxfbPAHQNE2FD1bbOA0aD21MU8HR1qckTFosOP3O8KpxBQW3Z0aTX+k0sy0edwHfHCjQxQ9ne3oWcY+JiKCSFbyWoYxGQML0N11jA5Jik4K3jMA7cDQQzUutWUGN/ABv7OoXYKbfv/3IA8Uqu6jxSgBZjsLb4iUdFtw9QlwOsr4w2flCsPtye0vnDsjOduQ=="
>>> base64_encode($rsa->encrypt('admin'))
=> "rnkB57WUXMw87Hxc6e6MGraGryEQ+3HVwbGEexIGrs4jlqzBHZig7/ykfHzGgZGlrtYGY/FB3Mn6kfkwH1SXVAV6QPdnp0ktxbrqHJmQDDrLxsBlQGbZnSSVglV2EHn9Vm++iiygspv+IRMmOB6XOBxWMcV2AeAGa86EwTj3AKdcOeSPzrIB04G2mH695rEwLHC69KeBZ5vCuPAvVZ5AjhzsutcNEK5WLIcxcFbi1PQb7Amp3mPMpW3g0w+LkU6RkW8GGZ5gPu15PfvT/r8CCDaJbTwCLN2XPAa1R+/x0IZgSM2Tv/qOJBGdkkjGM8/lXeTGW8/oKU85bdlVqEBxdA=="
>>> base64_encode($rsa->encrypt('admin'))
=> "cX+CkQR+t9fdWMo2jHs7FdbmFzcAYkLfhNo6J0O3rAMtg8iR/KoN+RfNA6WQN4DIWMiGsN1F/ipAxiOo8K0V7x7EODgpbw2zGQBL/ueWJyD00UIA0WsXo93ubIXOJ+62dFbz7Ioc9Gtwv3q7HhtmKIh2oDxpffU1uOWzBGE8MW5cYvuWGHRqsgcKxFfiOJht+GkZUS2gdUe7/ke5YsEGLJs4PJzuk/NnPvsKEdlNHoAT+Vyzc3yW9+FtL8OpXDplACFaRS/Urulmvkd5wiy7dhCh9L1QDdgUZshkQ0In2254LFME5tVkpES0ZlD8eLZih84sioTIyeGoUj1gdkHxdw=="
>>> base64_encode($rsa->encrypt('admin'))
=> "JFVZ2dhi6vsLeiXwPDxisxRa+56fCHIM9RORhJ51hgdAr7qeb7O2o0Xx3AVJB/CfX6ZXOFwZ38sATGDpHgEbYT5Tui12IMMLTgLnQnnxyxX8+A5AfYCpAzHOr9Xr41fHdtVDTexcI+77yEakhh57SGhfUQhoTll/k73CDymLkF/DkKw21EY9DXgscffKB5giyC3bd4CiqCZ3j/aa0T70NKtjDT3H5zqrHd3dhqblYRVE7rAbKHYPz6hrv5TUX2rX45er4o53cihGOuzTlwOGDa5f0HGlepnXSvGlgmqCRMbina4LepAlker93HVD56I7rtiRRLqz9BUahkt3GSoZfA=="
>>>

When encryption is randomized, it provides semantic security. This property doesn't allow a passive observer of ciphertext to determine whether the plaintext behind the observed ciphertext was sent before or not. This is achieved through randomized padding (PKCS#1 v1.5 padding type 2 or OAEP).
If you want to determine whether your code is compatible, you need to encrypt on one side and decrypt on the other.
It looks like your code should work as-is, depending on what the defaults are. You should always specify your own configuration to prevent the defaults from breaking compatibility when you switch systems.
In Java, you should always provide a fully qualified cipher string:
Cipher.getInstance("RSA/ECB/PKCS1Padding"); or
Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding"); (OAEP is preferred nowadays)
Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); (OAEP is preferred nowadays)
In phpseclib, you should always change the defaults:
$rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1); or
for OAEP with SHA-1:
$rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_OAEP);
$rsa->setHash("sha1");
$rsa->setMGFHash("sha1");
for OAEP with SHA-256:
$rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_OAEP);
$rsa->setHash("sha256");
$rsa->setMGFHash("sha1");

Related

Decoding JWE with web-token/jwt-framework in PHP takes too much time

I need to solve how to decode a JWE encrypted with our public key in PHP.
The case is that the userinfo of Oauth2 is encrypted and needs to be decrypted for the login to go through.
With the current code the userifo is decrypted but it takes up to 13 minutes which is not acceptable for logging in.
This is done in Totara 15 (based on Moodle) and with a hook and a local plugin so as not to have too much core changes.
The code I have now is using "web-token/jwt-framework": "2.2.11" as T15 does not support PHP 8.1 which is required for the latest version.
As far as I can tell web-token/jwt-framework should be able to do this efficiently. However if anyone has other suggestions then I am ready to test other frameworks as well.
As I have not worked with web-token/jwt-framework any suggestions are helpful.
So here is what I have at the moment. It's a function that takes the encoded JWE, a keyid and the path to our private key and returns the decoded payload.
I have debugged the code and it's only the line:
$jwe = $jweLoader->loadAndDecryptWithKey($token, $jwk, $recipient);
That takes time, everything else takes fractions of seconds.
<?php
namespace local_oauth2_extension;
require_once(__DIR__ . '/../vendor/autoload.php');
use Jose\Component\KeyManagement\JWKFactory;
use Jose\Component\Core\AlgorithmManager;
use Jose\Component\Encryption\Algorithm\KeyEncryption\A256KW;
use Jose\Component\Encryption\Algorithm\KeyEncryption\RSAOAEP;
use Jose\Component\Encryption\Algorithm\ContentEncryption\A256CBCHS512;
use Jose\Component\Encryption\Algorithm\ContentEncryption\A128CBCHS256;
use Jose\Component\Encryption\Compression\CompressionMethodManager;
use Jose\Component\Encryption\Compression\Deflate;
use Jose\Component\Encryption\JWEDecrypter;
use Jose\Component\Core\JWK;
use Jose\Component\Encryption\Serializer\JWESerializerManager;
use Jose\Component\Encryption\Serializer\CompactSerializer;
use Jose\Component\Encryption\JWELoader;
use Jose\Component\Checker\HeaderCheckerManager;
use Jose\Component\Checker\AlgorithmChecker;
use Jose\Component\Signature\JWSTokenSupport;
class jwe_helper
{
public static function decode_response($token, $kid, $pem_path)
{
try{
$jwk = JWKFactory::createFromKeyFile(
$pem_path, // The filename
null,
[
'use' => 'enc', // Additional parameters
'kid' => $kid
]
);
// The key encryption algorithm manager with the A256KW algorithm.
$keyEncryptionAlgorithmManager = new AlgorithmManager([
new RSAOAEP()
]);
// The content encryption algorithm manager with the A256CBC-HS256 algorithm.
$contentEncryptionAlgorithmManager = new AlgorithmManager([
new A128CBCHS256()
]);
// The compression method manager with the DEF (Deflate) method.
$compressionMethodManager = new CompressionMethodManager([
new Deflate(),
]);
// We instantiate our JWE Decrypter.
$jweDecrypter = new JWEDecrypter(
$keyEncryptionAlgorithmManager,
$contentEncryptionAlgorithmManager,
$compressionMethodManager
);
// The serializer manager. We only use the JWE Compact Serialization Mode.
$serializerManager = new JWESerializerManager([
new CompactSerializer(),
]);
$jweLoader = new JWELoader(
$serializerManager,
$jweDecrypter,
null
);
$jwe = $jweLoader->loadAndDecryptWithKey($token, $jwk, $recipient);
$payload = $jwe->getPayload();
$payload = base64_decode($payload);
$userinfo = self::extract_userinfo($payload);
return $userinfo;
} catch (\Exception $exception) {
return $token;
}
return $token;
}
........
}
As per the documentation
For RSA-based encryption algorithms, it is highly recommended to install GMP or BCMath extension.
It is also mentioned after composer install
GMP or BCMath is highly recommended to improve the library performance
You should make sure either GMP or BCMath is installed on your platform.

Why am I getting random characters prepended to a decrypted value in Node.js?

I am using PHP to encrypt a string using a public key. I then pass the private key to a Node.js microservice for decryption. I am getting a successful decryption, but there is strange encoded characters before the true decrypted value.
PHP Generate Keys & Encrypt Value
$user_passphrase = '***'
$string = '123ABC';
// Generate key pair
$pkey = openssl_pkey_new([
'private_key_bits' => 2048,
'private_key_type' => OPENSSL_KEYTYPE_RSA,
]);
// Extract private key
openssl_pkey_export($pkey, $private_key, $user_passphrase);
// Base64 encode private key
$private_key = base64_encode($private_key);
// Extract public key
$pkey_details = openssl_pkey_get_details($pkey);
// Base64 encoded public key
$public_key = base64_encode($pkey_details['key']);
// Encrypt value
openssl_public_encrypt($string, $encrypted_value, base64_decode($public_key));
var_dump('Encrypted Value: ' . base64_encode($encrypted_value));
Node.js Decrypt Value
const crypto = require('crypto');
function decrypt (value, privateKey, passphrase) {
value = Buffer.from(value, 'base64');
privateKey = Buffer.from(privateKey, 'base64');
return crypto.privateDecrypt({
key: privateKey,
passphrase: passphrase,
padding: crypto.constants.RSA_NO_PADDING
}, value).toString('utf8');
}
const decryptedValue = decrypt(encryptedString, encryptedPrivateKey, userPassphrase);
console.log('Decrypted Value:' + decryptedValue);
When I run the php script, I pass the necessary values to the Node.js script. When the Node.js script decodes the value, the correct decoded value (123ABC) is at the end of a long string with strange encoding characters. Why am I seeing these strange characters?
�R^�z���p��LUZ��ϓ)�V'�5I��T�Z&jzD���TM�d��U�#��3�D�z�ו"��k��I��p���D�?����Oe���Ȯzl�Mss�L��;�j���yX�j�ŭ�]�� 123ABC

What's causing this InvalidSignature exception? (public key and signature are created by PHP and the verification is done in Python)

I'm basically trying to create a signature using "openssl" (PHP) and verify that using "criptografy" (PYTHON). And I'm always getting the error "InvalidSignature", which means I'm doing something wrong. I know that they are different languages and libraries but once I'm using the same algorithm for both I expected to get a valid signature. I appreciate any help.
PHP: creating public key and signature without problems
$config = array(
"digest_alg" => "sha256",
"private_key_bits" => 4096,
"private_key_type" => OPENSSL_KEYTYPE_RSA,
);
$res = openssl_pkey_new($config);
openssl_pkey_export($res, $privKey);
$pubKey = openssl_pkey_get_details($res);
$pubKey = $pubKey["key"];
error_log($pubKey);
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtXyDj9vqTGkR/ITYZ6+e
xXATzalUOdBcqee+qcb5NJ6Z5DgNdYi9lWz/4YfitYKp0EFwPzbem1zBzKbuQSko
y7zRjpmyGbw8Q6wexO4SyA44jxs75JNXMA2x22dkNKajRE5kXngBIF1ixpzCxvvc
kfyewM8C8y2iAy5j02YZYw9ysrQWJegamq6sidnMCJBtokOnPQaNJwbDQTqwrSRS
8IDy7BtBHB7F/bBwLArwxG7aLFjJ9vf2F7HpmZ3VvJa69OhY0pZMSqePQpJBIQ+2
ztIywpKkOukJz22Brqoe0ygMQzVrcYoj2MZ8CSiKUCJL6Wm9ErFXvBh/XqPWjX1t
nWdnF6qSD/2itIw18+PzCWYaoeu6w064dcbRrUQ4UOYxp69IFtrv5OHAsuWPJ27q
2IUCZ9DWWphlwhz+lI4rAb6whd2R8Sb7vEhvSz4Kd5kIjel9Dt8mJ+jGyhTjqIhP
7amgcOQLKZJfmeltYI+F0U8oJcOPhxtlxfFB1MIxPDHvCcdR93LJGgU6NboTwcpx
hnoI86xKblJmnxMxuQbUfPRU8vAuiizKVrpQS8z2k58mlxa9+hykjMcqpAvQ6STM
vLswdj0j9aqyv6I94z2Q2Lgcuoh7xSJcLhKN9QGaarUqjAY/zoZPiDnCxXlnVrav
BMyQZ9PqbsaHsd7pVVpuW8MCAwEAAQ==
-----END PUBLIC KEY-----
$data = 'oi';
$pkeyid = openssl_pkey_get_private($privKey);
openssl_sign($data, $signature, $pkeyid);
error_log(base64_encode($signature));
MoUZzfaHCwME2mmxJnMBQWgo+lTLK2QfpfD+5IEnSwi0wmRtgZgFl7IGsWYlTO+zTQCcB/aKg91CMClmb5P2jEtOkY3ie2pNflEIsc3aGqfWoVpHJUMoft+4hytzgMszqwgbJdo6eac7zIxlrzmeb4jb58APtc4aaLLQru2Gga9oPRyCqbXrD0TXnQ6GzjDNGwi6wP30NU9KaHWaGfeq1WSqsAIaIbi2oG4MeWJYX6SOB/CX1Jg8SJYzWQahpJeybE4Z8fR6ncuvfbjp9aet1aKsPB/DFPQ5VFaAS4oItVb0Ha4wkQ8YgJu5fEUK7X9KzuOwidja5RLZtwDPa0bBSveCw9D8FUsguilaoIu7ueDPTRueK4bQkYstOc2OvVlzInukyOzFXVgZsiV23FTHrQ5FqHd9nCUbfuhgrMqHRYQmRFUgDaeyY+B5ve3bQ4lT3qm282ngKi8AZ+clB0VeaFiQxpF8SCCRMu0bX3dsQMaXRUeSVDipf3ZMABl9xXbzEjqXoJzuvC6tCnEv8K3HzKbjz2S1876eGy7opIixaUMwDu9QOiPoSiFUY/xCsX8D1Im8Mv4eyzWNV3FL1U/GS3LZzkq/NuJdB7wjCib3ieHaYX/bvTpmAp/oO0+vbI2pzq9w71cpqQpsNsOZSGkgY2q1sO6u0I5jCFScdJVbJl0=
PYTHON: InvalidSignature error
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.hazmat.primitives.serialization import load_pem_public_key
signature= 'MoUZzfaHCwME2mmxJnMBQWgo+lTLK2QfpfD+5IEnSwi0wmRtgZgFl7IGsWYlTO+zTQCcB/aKg91CMClmb5P2jEtOkY3ie2pNflEIsc3aGqfWoVpHJUMoft+4hytzgMszqwgbJdo6eac7zIxlrzmeb4jb58APtc4aaLLQru2Gga9oPRyCqbXrD0TXnQ6GzjDNGwi6wP30NU9KaHWaGfeq1WSqsAIaIbi2oG4MeWJYX6SOB/CX1Jg8SJYzWQahpJeybE4Z8fR6ncuvfbjp9aet1aKsPB/DFPQ5VFaAS4oItVb0Ha4wkQ8YgJu5fEUK7X9KzuOwidja5RLZtwDPa0bBSveCw9D8FUsguilaoIu7ueDPTRueK4bQkYstOc2OvVlzInukyOzFXVgZsiV23FTHrQ5FqHd9nCUbfuhgrMqHRYQmRFUgDaeyY+B5ve3bQ4lT3qm282ngKi8AZ+clB0VeaFiQxpF8SCCRMu0bX3dsQMaXRUeSVDipf3ZMABl9xXbzEjqXoJzuvC6tCnEv8K3HzKbjz2S1876eGy7opIixaUMwDu9QOiPoSiFUY/xCsX8D1Im8Mv4eyzWNV3FL1U/GS3LZzkq/NuJdB7wjCib3ieHaYX/bvTpmAp/oO0+vbI2pzq9w71cpqQpsNsOZSGkgY2q1sO6u0I5jCFScdJVbJl0='
decoded = base64.b64decode(signature)
plaintextMessage="oi"
# /tmp/public contains the public key
alicePubKey = load_pem_public_key(open('/tmp/public', 'rb').read(),default_backend())
ciphertext = alicePubKey.verify(
decoded,
plaintextMessage,
padding.PSS(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH,
),
hashes.SHA256()
)
It's pretty clear that your PHP code:
Signed using RSA with SHA1, not SHA256
Used PKCS#1 version 1.5 padding, not PSS
It looks like you just copy&pasted your code from the cryptography package documentation.
Thus the following python snippet should verify:
ciphertext = alicePubKey.verify(
decoded,
b'oi',
padding.PKCS1v15(),
hashes.SHA1()
)
PSS and SHA-256 are better choices, so you should investigate modifying your PHP code to use those instead of modifying your python code.
As James said, the function openssl_sign was using SHA1 by default, what could be solved with the parameter OPENSSL_ALGO_SHA256.
So I did these changes:
PHP
openssl_sign($data, $signature, $pkeyid, OPENSSL_ALGO_SHA256);
Python
ciphertext = alicePubKey.verify(
decoded,
plaintextMessage,
padding.PKCS1v15(),
hashes.SHA256()
)

Verify JWT signature with RSA public key in PHP

In PHP, I'm trying to validate an AWS auth token (JWT returned from getOpenIdTokenForDeveloperIdentity) using the AWS's RSA public key (which I generated from modulus/exponent at https://cognito-identity.amazonaws.com/.well-known/jwks_uri). The key begins with the appropriate headers/footers -----BEGIN RSA PUBLIC KEY----- etc. I've looked at a few PHP libraries like Emarref\Jwt\Jwt, however I get the error: error:0906D06C:PEM routines:PEM_read_bio:no start line. It all boils down to the basic php function: openssl_verify.
I've looked at the php.net/manual for openssl-verify, but I'm still not clear on the parameter details. The algorithm needed is RS512.
I am able to verify the JWT token using node.js with no problems (same key and token). For that I used the library: https://github.com/auth0/node-jsonwebtoken
Not sure why this doesn't work in PHP. Can I not use an RSA Public Key?
function verifyKey($public_key) {
$jwt = new Emarref\Jwt\Jwt();
$algorithm = new Emarref\Jwt\Algorithm\Rs512();
$factory = new Emarref\Jwt\Encryption\Factory();
$encryption = $factory->create($algorithm);
$encryption->setPublicKey($public_key);
$context = new Emarref\Jwt\Verification\Context($encryption);
$token = $jwt->deserialize($authToken);
try {
$jwt->verify($token, $context);
} catch (Emarref\Jwt\Exception\VerificationException $e) {
debug($e->getMessage());
}
}
Could you try using another PHP library: https://github.com/Spomky-Labs/jose
// File test.php
require_once __DIR__.'/vendor/autoload.php';
use Jose\Checker\ExpirationChecker;
use Jose\Checker\IssuedAtChecker;
use Jose\Checker\NotBeforeChecker;
use Jose\Factory\KeyFactory;
use Jose\Factory\LoaderFactory;
use Jose\Factory\VerifierFactory;
use Jose\Object\JWKSet;
use Jose\Object\JWSInterface;
// We create a JWT loader.
$loader = LoaderFactory::createLoader();
// We load the input
$jwt = $loader->load($input);
if (!$jws instanceof JWSInterface) {
die('Not a JWS');
}
// Please note that at this moment the signature and the claims are not verified
// To verify a JWS, we need a JWKSet that contains public keys (from RSA key in your case).
// We create our key object (JWK) using a RSA public key
$jwk = KeyFactory::createFromPEM('-----BEGIN RSA PUBLIC KEY-----...');
// Then we set this key in a keyset (JWKSet object)
// Be careful, the JWKSet object is immutable. When you add a key, you get a new JWKSet object.
$jwkset = new JWKSet();
$jwkset = $jwkset->addKey($jwk);
// We create our verifier object with a list of authorized signature algorithms (only 'RS512' in this example)
// We add some checkers. These checkers will verify claims or headers.
$verifier = VerifierFactory::createVerifier(
['RS512'],
[
new IssuedAtChecker(),
new NotBeforeChecker(),
new ExpirationChecker(),
]
);
$is_valid = $verifier->verify($jws, $jwkset);
// The variable $is_valid contains a boolean that indicates the signature is valid or not.
// If a claim is not verified (e.g. the JWT expired), an exception is thrown.
//Now you can use the $jws object to retreive all claims or header key/value pairs
I was able to get this library to work. However I had to build the key using KeyFactory::createFromValues instead of KeyFactory::createFromPEM. THANK YOU!

PhpSeclib <-> BouncyCastle RSA

I generated on server side a pair public/private keys using phpseclib like
include 'Crypt/RSA.php';
$rsa = new Crypt_RSA();
$rsa->setPrivateKeyFormat(CRYPT_RSA_PRIVATE_FORMAT_PKCS1);
$rsa->setPublicKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_PKCS1);
extract($rsa->createKey());
echo $privatekey;
echo "\n\n\n";
echo $publickey;
Now I want import on client side Public key using Java Bouncy Castle engine.
Here my Public key
-----BEGIN PUBLIC KEY-----
MIGJAoGBAJEGAmaQejDgJaCg/B5+g68arqpMpl6jZ9+p8TBzNRIq+Ygt/n3iqz+pAtltrlRnmqSD
svx0LMluw1wXezQ1pz2tTJTEhg6b69Qui0o//W5UDfle4yOyAHaOs8MD5nubJjXFU8vGiEdektET
jgKqiSr5TBgZoHy+YDWpd4yTemXVAgMBAAE=
-----END PUBLIC KEY-----
But I can do it. I tried to do it several ways but I always get errors.
AsymmetricKeyParameter publicKey =
(AsymmetricKeyParameter) PublicKeyFactory.createKey(b64.decodeBuffer(key));
AsymmetricKeyParameter publicKey =
(AsymmetricKeyParameter) PublicKeyFactory.createKey(key.getBytes())
Also
PEMReader pemReader = new org.bouncycastle.openssl.PEMReader (reader);
PemObject pem = pemReader.readPemObject();
All these ways generate error.
How should I import Public key using Java Bouncy Castle engine?
I found solution
key = key.replaceAll("PUBLIC KEY", "RSA PUBLIC KEY");
final Reader reader = new StringReader(key);
PEMReader pemReader = new PEMReader(reader);
Object obj = pemReader.readObject();
pemReader.close();
BCRSAPublicKey bcPublicKey = (BCRSAPublicKey) obj;
AsymmetricKeyParameter publicKey = (AsymmetricKeyParameter) PublicKeyFactory.createKey(bcPublicKey.getEncoded());
AsymmetricBlockCipher e = new RSAEngine();
e = new org.bouncycastle.crypto.encodings.PKCS1Encoding(e);
e.init(true, publicKey);
byte[] messageBytes = inputData.getBytes();
encryptedData = e.processBlock(messageBytes, 0, messageBytes.length);
Now i can encrypt on Java side and decrypt on Server (PHP) side
Might be worthwhile to try with the latest Git version of phpseclib. Quoting from a semi-recent commit:
https://github.com/phpseclib/phpseclib/commit/2f8d1055ea5a6b06cd7a40eb85661ba688a31320
Quoting from it, the commit "[makes] Crypt_RSA's public keys compatible with OpenSSL".

Categories