How can i use the public key from https://appleid.apple.com/auth/keys with openssl ?
<?php
$key = '-----BEGIN PUBLIC KEY-----'. "\n".
chunk_split("1JiU4l3YCeT4o0gVmxGTEK1IXR-Ghdg5Bzka12tzmtdCxU00ChH66aV-4HRBjF1t95IsaeHeDFRgmF0lJbTDTqa6_VZo2hc0zTiUAsGLacN6slePvDcR1IMucQGtPP5tGhIbU-HKabsKOFdD4VQ5PCXifjpN9R-1qOR571BxCAl4u1kUUIePAAJcBcqGRFSI_I1j_jbN3gflK_8ZNmgnPrXA0kZXzj1I7ZHgekGbZoxmDrzYm2zmja1MsE5A_JX7itBYnlR41LOtvLRCNtw7K3EFlbfB6hkPL-Swk5XNGbWZdTROmaTNzJhV-lWT0gGm6V1qWAK2qOZoIDa_3Ud0Gw", 64).
'-----END PUBLIC KEY-----';
print_r($key);
$res = openssl_pkey_get_public($key);
print_r(openssl_pkey_get_details($res));
Fatal error: Uncaught TypeError: openssl_pkey_get_details(): Argument #1 ($key) must be of type OpenSSLAsymmetricKey
Your problem is the initial data from Apple (line 1). It isn't a public key, it's a JWKS, consisting of a modulus (n), exponent (e), and other useless stuff (in this case).
You need both the n value, which you have, and the e value, which is probably AQAB (65537).
Unfortunately, php openssl functions are a mess, so there's no builtin functionality for converting this.
Still, you can convert it to your initial $key value with some work.
using a php function: https://www.identityblog.com/?p=389
recommended. simple. easy to see what is going on.
$key=kimssl_pkey_get_public($n,$e); #done
using a php class: http://phpseclib.sourceforge.net/
use phpseclib\Crypt\RSA; $key=Crypt_RSA()::loadKey($e,$n); #you's use use?!?!
using some openssl: https://baptistout.net/posts/convert-jwks-modulus-exponent-to-pem-or-ssh-public-key/
how the bash crowd does it. unnecessarily complex. asn1. useful for informational purposes.
The value of $key should now be what you expected.
References:
JWKS: https://www.rfc-editor.org/rfc/rfc7517
Convert using openssl asn1parse: https://baptistout.net/posts/convert-jwks-modulus-exponent-to-pem-or-ssh-public-key/
convert: How to convert JWK to PEM
build PEM from modulus and exponent: build public key PEM file with existing modulus and exponent in php with openssl library
Related
Most examples of generating a public key from an Azure B2C modulus and exponent use phpseclib and pass an XML string to the library to generate a public key.
However, phpseclib3 appears to switch this up by providing a PublicKeyLoader that takes a keyed array where the keys are e and n for the exponent and modulus as BigInteger instances.
What transformations need to happen to those e and n values provided by Azure B2C to make them appropriate for use with the PublicKeyLoader?
Many of the examples for the older versions of phpseclib would convert from a base64url to base64, but I don't know if that is purely for the benefit of the XML conversion method and if that will work with the BigInteger function.
Generation of this public key is for the purposes of verifying an access token signature via lcobucci/jwt.
After a bit of experimenting, and further searching the following method can be used.
Convert each value from base64url to base64 and decode. You might like to use the PHP package spomky-labs/base64url.
Unpack a value from hex
$value = unpack('H*', $value);
then convert to a BigInteger, using base 16
new BigInteger($value[1], 16);
The only bit to note is that some Base64URL decode examples add padding when preparing for the base64_decode. The cited lib does not, but it worked for me.
According to OpenSSL ChangeLog, OpenSSL 1.1.1 added support for EdDSA (which includes Ed25519). I'm running PHP 7.3.5 with OpenSSL 1.1.1b, which should support it. I tried to use an Ed25519 (the ones from https://www.rfc-editor.org/rfc/rfc8410#section-10.3). That got me the following error (as returned by openssl_error_string()) with the "Ed25519 private key without the public key" key.
error:0608D096:digital envelope routines:EVP_PKEY_sign_init:operation not supported for this keytype
The "Ed25519 private key encoded with an attribute and the public key" key got me a different error.
Warning: openssl_sign(): supplied key param cannot be coerced into a private key in /path/to/test.php on line 3 bad error:0D078094:asn1 encoding routines:asn1_item_embed_d2i:sequence length mismatch
This the code I used.
$r = openssl_sign('hello, world!', $signature, '-----BEGIN PRIVATE KEY-----
MHICAQEwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC
oB8wHQYKKoZIhvcNAQkJFDEPDA1DdXJkbGUgQ2hhaXJzgSEAGb9ECWmEzf6FQbrB
Z9w7lshQhqowtrbLDFw4rXAxZuE=
-----END PRIVATE KEY-----');
echo $r ? 'good' : 'bad';
echo "\n";
echo openssl_error_string();
I guess PHP just doesn't yet support Ed25519.
I guess not, if we go by the documentation, it looks like the signing/verification requirements are different from the normal usage of the openssl library.
The Ed25519 and Ed448 EVP_PKEY implementation supports key generation,
one-shot digest sign and digest verify using PureEdDSA and Ed25519 or
Ed448 (see RFC8032).
and comments like:
The PureEdDSA algorithm does not support the streaming mechanism of other signature algorithms using, for example, EVP_DigestUpdate(). The message to sign or verify must be passed using the one-shot EVP_DigestSign() and EVP_DigestVerify() functions.
When calling EVP_DigestSignInit() or EVP_DigestVerifyInit(), the digest type parameter MUST be set to NULL.
So, unless you can call the openssl api directly or can add more openssl glue functions to support one-shot signing/verification support then I guess not.
I have a RSA Public Key in this format:
OpenSSLRSAPublicKey{modulus=9ee9f82dd8429d9fa7f091c1d375b9c289bcf2c39ec57e175a2998b4bdd083465ef0fe6c7955c821b7e883929d017a9164a60290f1622f664a72096f5d2ffda7c7825c3d657c2d13d177445fa6cdd5d68b96346006a96040f5b09baae56d0c3efeaa77d57602f69018f5cefd60cb5c71b6b6f8a4b0472e8740367266917d8c13,publicExponent=10001}
So, I have the modulus and the exponent.
How can I convert this in a format that is accepted by openssl_encrypt() in PHP?
I have searched on Google and didn't find anything good. Isn't there an easy way to format it?
Assuming you mean openssl_public_encrypt() instead of openssl_encrypt(), then try this (using phpseclib 1.0):
<?php
include('Crypt/RSA.php')
include('Math/BigInteger.php');
$rsa = new Crypt_RSA();
$rsa->loadKey(array(
'n' => new Math_BigInteger('9ee9f82dd8429d9fa7f091c1d375b9c289bcf2c39ec57e175a2998b4bdd083465ef0fe6c7955c821b7e883929d017a9164a60290f1622f664a72096f5d2ffda7c7825c3d657c2d13d177445fa6cdd5d68b96346006a96040f5b09baae56d0c3efeaa77d57602f69018f5cefd60cb5c71b6b6f8a4b0472e8740367266917d8c13', 16),
'e' => new Math_BigInteger('10001', 16)
));
echo $rsa;
Altho that said, if you're gonna use phpseclib to convert the key, why not use it to encrypt whatever it is that you're trying to encrypt as well?:
//$rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
$rsa->encrypt('plaintext');
No, there isn't any way to do this. Public/private keys can only be used in asymmetric cryptography. openssl_encrypt() requires symmetric cryptography.
openssl_public_encrypt() which you need for asymmetric cryptography wants a pubic key resource. This can be obtained using openssl_pkey_get_public().
Now, openssl_pkey_get_public() needs a PEM file. The docs describe it as:
an X.509 certificate resource
a string having the format file://path/to/file.pem. The named file must contain a PEM encoded certificate/public key (it may contain both).
A PEM formatted public key.
So you need to create a PEM file from the public key configuration you have in your question. The modulus and exponent you have are enough to do this.
There are several examples on the internet on how to archive this. Have a look at:
Generate .pem RSA public key file using base 10 modulus and exponent?
rawrsa (https://github.com/JonathonReinhart/rawrsa)
how to convert raw modulus & exponent to RSA public key (.pem format)
Basically you need to create a "ASN1-formatted text file" and employ some openssl commands.
I am migrating an entire PHP API, and while I used PyCrypto before, I am not sure how to translate the following encryption call, since I need the exact same result. The PHP call is:
define('KEY', "-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC81t5iu5C0JxYq5/XNPiD5ol3Z
w8rw3LtFIUm7y3m8o8wv5qVnzGh6XwQ8LWypdkbBDKWZZrAUd3lybZOP7/82Nb1/
noYj8ixVRdbnYtbsSAbu9PxjB7a/7LCGKsugLkou74PJDadQweM88kzQOx/kzAyV
bS9gCCVUguHcq2vRRQIDAQAB
-----END PUBLIC KEY-----");
$cypher = "";
$result = openssl_public_encrypt($plain, $cypher, KEY, OPENSSL_PKCS1_PADDING);
echo bin2hex($cypher);
Assuming everything goes right, this prints the content from $cypher, passed to hexadecimal. For a sample input "azzzzzzzzzzzzdfdf" I get something like: "2281aeebc1166cdfb2f17a0a0775d927ca5a9ad999bae0e4954f58bd8082fdf7efe1fd284876530341f714456d7eb8cd44c57b20ab27029b84d5dc77a674bede3fe9065282931404286082e9df8607bdcff0818b90324dfee7d76b566d0f99bebc5cc913372c276ba373712128f1bcc226b59367cff93f7cdd6dbde25b366863".
I must assume this value as right, since the code was taken from an existing API I am migrating. However, trying the same with PyCrypto (yes, I am migrating the API to be available in Python), I use the following code:
def bin2hex(s):
return "".join([hex(ord(c))[2:].zfill(2) for c in s])
KEY = """-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC81t5iu5C0JxYq5/XNPiD5ol3Z
w8rw3LtFIUm7y3m8o8wv5qVnzGh6XwQ8LWypdkbBDKWZZrAUd3lybZOP7/82Nb1/
noYj8ixVRdbnYtbsSAbu9PxjB7a/7LCGKsugLkou74PJDadQweM88kzQOx/kzAyV
bS9gCCVUguHcq2vRRQIDAQAB
-----END PUBLIC KEY-----"""
from Crypto.Cipher import PKCS1_v1_5
from Crypto.PublicKey import RSA
encrypter = PKCS1_v1_5.new(RSA.importKey(KEY))
print bin2hex(encrypter.encrypt("azzzzzzzzzzzzdfdf"));
While I expect the same value be returned and printed, the value finally is "3dd94ffabd01bb0e94010c0fedbcd4eb648f12e5d9e6d934b77ae86f76681d8a1b790cad9fddf6e6720415b4d645e525c33c402fa9778739b8e461790387e9508f7158a5fdc5723f5fc26d166b11a00759f0e0ee3ba6719a2e7c6b918f66e1311d1fff878ee2ca8762e1d6120f1e9585a76cdc7719ca20129ae76182b4277170".
Using PKCS1_OAEP outputs "290f60f37088c2cb46ae9221b01ff46a463f270ef7cf70bbea49de0b5ae43aec34a0eb46e694cf22f689eb77e808c590fdc30eda09f9d3f3cb8c15e0505bf5a984c2a121bc9fa83c6b5ccf50235f072467b4ae9cdf0f0ee2e486626ffa62ad1aa715fbe29e8afe4ceab3ca5a5df4c1dc75d7f258285d7ff1f4f2b4dcb7a8413a".
It is easy to tell that I must fix my python code. How can I fix my python code so it returns the exact same result as the given PHP call?
Your code is fine. PKCS#1 v1.5 padding in pyCrypto is randomized (source). So the encryption will always be different even if you use the same key and plaintext. This is a desirable property.
If you want to check compatibility between pyCrypto and PHP's OpenSSL extension, then you would need to encrypt in one, decrypt in the other and check that you got what you expected.
PKCS#1 v1.5 padding should not be used nowadays, because there are efficient attacks against it. OAEP is a much better alternative.
I've struggled with the same problem for quite long but finally managed to solve this.
The main issue was setting proper SHA hashes, in case of PHP's openssl_public_encrypt() it should be SHA1.
Here's the code for openssl_public_encrypt() with OPENSSL_PKCS1_OAEP_PADDING:
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
def encrypt_key(message):
with open('public_key', 'rb') as key_file:
pub_key = key_file.read()
public_key = load_pem_public_key(pub_key, default_backend())
ciphertext = public_key.encrypt(
message,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA1()),
algorithm=hashes.SHA1(),
label=None
)
)
return ciphertext
basically a copy of cryptography.io documentation regarding RSA.
I'm having serious problems with what I was hoping would be a simple call to PHP's
openssl_public_encrypt();
Sadly, I get an invalid public key warning.
My goal is to simply RSA encrypt a users password using public key provided by their API which currently looks like this:
+Tir6+unMOaQ5tHqjjjwnlAMhccnCSMFEi3a0mhIxbW+O/GukjomGyzckQT2h0Ys70JezHbNq5YS3sYkNF29kCkz4HuNfy9eEjE/clA9/zyfT8ZcbnusLcLz2xNgbTp62fQdzBnReI5+dpj/N24krYvHaYIr8ACxDqBv2TR3E9M=AQAB
Apparently a working implementation using the same service I'm trying to use found a solution with these steps:
Extract the modulus and exponent from the public key
Re-constructs the key in the MS PUBLICKEYBLOB format
Call OpenSSL to convert the key to PEM format
Load the PEM public key from the converted file
Encrypts the password (after converting to UTF-16LE)
However not only that I dont know if its possible to do in PHP , I think there Must be an easier way!
One post I saw hinted that the exponent may come after the last = sign (so AQAB) but I don't know if this is reliable.
Something like this do what you want?:
<?php
$key = '+Tir6+unMOaQ5tHqjjjwnlAMhccnCSMFEi3a0mhIxbW+O/GukjomGyzckQT2h0Ys70JezHbNq5YS3sYkNF29kCkz4HuNfy9eEjE/clA9/zyfT8ZcbnusLcLz2xNgbTp62fQdzBnReI5+dpj/N24krYvHaYIr8ACxDqBv2TR3E9M=AQAB';
include('Crypt/RSA.php');
$rsa = new Crypt_RSA();
$rsa->loadKey(array(
'e' => new Math_BigInteger(65537),
'n' => new Math_BigInteger(substr($key, 0, -4), -256)
));
$ciphertext = $rsa->encrypt('password');
echo bin2hex($ciphertext);
?>
This example uses phpseclib, a pure PHP RSA implementation. Although phpseclib does not support the format of the key you posted it does support raw public keys and as such converting to PKCS1 style keys is unnecessar. And the ciphertext's produced by phpseclib are perfectly interoperable with OpenSSL.
OK, so what you need to do:
remove the AQAB part and use the value 65537 for the public exponent
base 64 decode the modulus
create a DER encoding of the public key in PKCS#1 format (see below)
base64 encode the DER encoding using 64 character line length and DOS line endings
add PEM header & footer
use the PEM encoded string to create a public key and finally
encrypt the password
The following ASN.1 spec defines the public key in PKCS#1 format:
RSAPublicKey ::= SEQUENCE {
modulus INTEGER, -- n
publicExponent INTEGER -- e
}
Now the identifier octet or tag of SEQUENCE is 30 in hex, INTEGER is 02 and the length should be specified like this. So you get something like:
30818902818100F938ABEBEBA730E690E6D1EA8E38F09E500C85C727092305122DDAD26848C5B5BE3BF1AE923A261B2CDC9104F687462CEF425ECC76CDAB9612DEC624345DBD902933E07B8D7F2F5E12313F72503DFF3C9F4FC65C6E7BAC2DC2F3DB13606D3A7AD9F41DCC19D1788E7E7698FF376E24AD8BC769822BF000B10EA06FD9347713D30203010001
in hexadecimals. So after base 64 encoding and adding the header and footer line you should get:
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQH5OKvr66cw5pDm0eqOOPCeUAyFxycJIwUSLdrSaEjFtb478a6SOiYbLNyRBPaHRizvQl7Mds2rlhLexiQ0Xb2QKTPge41/L14SMT9yUD3/PJ9Pxlxue6wt
wvPbE2BtOnrZ9B3MGdF4jn52mP83biSti8dpgivwALEOoG/ZNHcT0wIDAQAB
-----END RSA PUBLIC KEY-----
Happy coding.