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.
Related
I am currently working on translating an encryption algorithm from PHP to Typescript, to use in a very specific API that requires the posted data to be encrypted with the API key and Secret. Here is the provided example of how to correctly encrypt data in PHP for use with the API (the way of implementing the key and IV can't be changed):
$iv = substr(hash("SHA256", $this->ApiKey, true), 0, 16);
$key = md5($this->ApiSecret);
$output = openssl_encrypt($Data, "AES-256-CBC", $key, OPENSSL_RAW_DATA, $iv);
$completedEncryption = $this->base64Url_Encode($output);
return $completedEncryption;
In the above code, the only thing the base64Url_Encode function does is convert the binary data to a valid Base64URL string.
And now the code as I have implemented it inside Typescript:
import { createHash, createCipheriv } from 'node:crypto'
const secretIV = createHash('sha256').update(this.ApiKey).digest().subarray(0, 16)
// Generate key
/*
Because the OpenSSL function in PHP automatically pads the string with /null chars,
do the same inside NodeJS, so that CreateCipherIV can accept it as a 32-byte key,
instead of a 16-byte one.
*/
const md5 = createHash('md5').update(this.ApiSecret).digest()
const key = Buffer.alloc(32)
key.set(md5, 0)
// Create Cipher
const cipher = createCipheriv('aes-256-cbc', key, secretIV)
let encrypted = cipher.update(data, 'utf8', 'binary');
encrypted += cipher.final('binary');
// Return base64URL string
return Buffer.from(encrypted).toString('base64url');
The above Typescript code only does NOT give the same output as the PHP code given earlier. I have looked into the original OpenSSL code, made sure that the padding algorithms are matching (pcks5 and pcks7) and checked if every input Buffer had the same byte length as the input inside PHP. I am currently thinking if it is some kind of binary malform that is causing the data to change inside Javascript?
I hope some expert can help me out with this question. Maybe I have overlooked something. Thanks in advance.
The stupidity is in the md5 function in PHP, which defaults to hexadecimal output instead of binary output:
md5(string $string, bool $binary = false): string
This is also why the code doesn't complain about the key (constructed from the MD5 hash) is being too small, it is fed 32 bytes after ASCII or UTF8 encoding, instead of the 16 bytes you'd use for AES-128.
Apparently it is using lowercase encoding, although not even that has been specified. You can indicate the encoding for NodeJS as well, see the documentation of the digest method. It also seems to be using lowercase, although I cannot directly find the exact specification of the encoding either.
Once you have completed your assignment, please try and remove the code ASAP, as you should never calculate the IV from the key; they key and IV combination should be unique, so the above code is not IND-CPA secure if the key is reused.
In case you are wondering why it is so stupid: the output of MD5 has been specified in standards, and is binary. Furthermore, it is impossible from the function to see what it is doing, you have to lookup the code. It will also work very badly if you're doing a compare; even if you are comparing strings then it is easy to use upper instead of lowercase (and both are equally valid, uppercase hex is actually easier to read for humans as we focus on the top part of letters more for some reason or other).
Basically it takes the principle of least surprise and tosses it out of the window. The encoding of the output could be made optimal instead, the NodeJS implementation does this correctly.
So I am working with this API and using Laravel, and I am trying to build an auth string. This is the documentation I was given, but I am having a little trouble as this is something relatively new to me.
Here are the auth instructions:
The authentication parameter is a string and it can calculated by the
caller or the caller can choose to save this value as a parameter
together with connection ID and API key.
The authentication is a base64 string of a HMAC SHA1 hash. This is
computed by using the binary of API Key in in
########################## format in all lower case and UTF8 encoding as the key and computer HMAC SHA1 hash on the binary of
Connection ID in ################################ format in all lower
case and UTF8 encoding.
The result binary hash is then base64 encoded and the text result is
what should be passed as the authentication parameter. In C# the code
to calculate the authentication may look like:
HMACSHA1 hmac = new HMACSHA1(
UTF8Encoding.UTF8.GetBytes(apiKey.ToString("N").ToLower())
);
string authentication = Convert.ToBase64String(
hmac.ComputeHash(
UTF8Encoding.UTF8.GetBytes(connectionId.ToString("N").ToLower())
)
);
As an example the following credentials:
Connection ID: 5fecbc200f0e4a7cbf41040e11047e56
API Key: 2de51c4fd0f04b9fabeb95225e87da70
Should result in a computed authentication value of
m5/Vc1RzhUETQvEtx/JdIglQpTg=
So what i have been trying is:
$a = strtolower('5fecbc200f0e4a7cbf41040e11047e56');
$b = strtolower('2de51c4fd0f04b9fabeb95225e87da70');
$z = hash_hmac("sha1", utf8_encode(decbin($b)), utf8_encode(decbin($a)), true);
dd(base64_encode($z));
Which outputs QjG3kzUs7U1UukNd++3t24pBWNk=
I have tried a few more variations, but I am just lost on this one. First time really decoding or encoding anything. Would greatly appreciate any tips, ideas, or snippets that can help me figure this out. Already spent a few hours on this and it's bugging me..
First: Get rid of utf8_encode() and just generally don't use it. It assumes that the input string is ISO-88591-1 and if it is anything else it will silently corrupt the data. This function has an incredibly misleading name, and I would go as far as to suggest that no one should ever use it or the corresponding utf8_decode() which will break your data in the same manner, but reversed.
If you need to convert string encodings in PHP use something that explicitly defines the input and output encodings, eg: mb_convert_encoding(). [you still don't need it for this]
Second: Whatever you think decbin() does, you're incorrect. It converts an integer into a literal, capital-S String composed of 0 and 1 characters.
Third: PHP strings have no inherent encoding and are roughly equivalent to byte arrays if you twisted my arm for a description. The bytes you put into them are the bytes you get out of them.
Fourth: I'm not exactly a C# expert [or intermediate, or even beginner] but that example code is horrendous. What even is the significance of the N in connectionId.ToString("N")? I can't find any documentation about this.
Start simple, use meaningful variable names, build up, and read the docs.
$connectionID = strtolower('5fecbc200f0e4a7cbf41040e11047e56');
$apiKey = strtolower('2de51c4fd0f04b9fabeb95225e87da70');
$hash = hash_hmac("sha1", $connectionID, $apiKey, true);
var_dump(base64_encode($hash));
Output:
string(28) "m5/Vc1RzhUETQvEtx/JdIglQpTg="
I'm using esp32 (Arduino platform not esp-idf) with the "HTTPClient.h" library to send get requests with parameters to my PHP server.
I want to encrypt the parameter values and decrypt them in my PHP code And vice versa (my server sends back JSON data to my esp32).
I tried using the XXTEA protocol with these libraries for PHP, and for esp32.
But the encrypted string won't decrypt properly on PHP.
Example:
When I encrypt "HELLO WORLD" on my esp32 with the key "ENCRYPTION KEY" I get this:
35bd3126715874f741518f4d
And when I decrypt it on PHP it returns blank.
Moreover, when I encrypt it on my PHP server I get this:
T1YNYC4P4R2Y5eCxUqtjuw==
My esp32 sketch looks like this:
#include <xxtea-iot-crypt.h>
void setup() {
Serial.begin(115200);
}
void loop() {
String plaintext = "HELLO WORLD";
// Set the Password
xxtea.setKey("ENCRYPTION KEY");
// Perform Encryption on the Data
Serial.print(F(" Encrypted Data: "));
String result = xxtea.encrypt(plaintext);
Serial.println(result);
// Perform Decryption
Serial.print(F(" Decrypted Data: "));
Serial.println(xxtea.decrypt(result));
delay(2000);
}
My PHP code looks like this:
require_once('xxtea.php');
$str = "HELLO WORLD"
$key = "ENCRYPTION KEY";
$encrypt_data = xxtea_encrypt($str, $key);
error_log($encrypt_data);
Is there a way to have an encrypted strings communication between PHP and esp32?
Thanks in advance.
This problem may result from inputs being of different data type, since no current XXTEA implementation seems to do any type or range checking.
Or it could be due to different endian behavior of the two computers involved, since binary is typically stored as an array of words constructed from bytes.
Or it could be due to lack of official or standard reference examples for correct encryption of a specific string and key. In the absence of reference examples (using either hexadecimal or base64 conversion of the binary encryption result) there is no way to tell whether an implementation of encryption is correct, even if its results decrypt correctly using a corresponding decryption implementation.
ADDED:
I think I've found one compatibility problem in the published code for XXTEA. It may be worth taking some space here to discuss it.
Specifically, the problem is that different implementations create different results for encrypting the same plaintext and key.
Discussion:
This problem results from the addition of the length of the plaintext as the last element of the array of longs. While this solves the problem of plaintext that has a length that is not a multiple of 4, it generates a different encrypted value than is generated by the JavaScript implementation.
If you insert "$w=false;" at the start of the long2str and str2long functions, the encrypted value for the PHP implementation becomes the same as the JavaScript implementation, but the decrypted value has garbage at the end.
Here are some test case results with this change:
PHP:
text: >This is an example. !##$%^&*(){}[]:;<
Base64: PlRoaXMgaXMgYW4gZXhhbXBsZS4gIUAjJCVeJiooKXt9W106Ozw=
key: 8GmZWww5T97jb39W
encrypt: sIubYrII6jVXvMikX1oQivyOXC07bV1CoC81ZswcCV4tkg5CnrTtqQ==
decrypt: >This is an example. !##$%^&*(){}[]:;<��
Note: there are two UTF-8 question-mark characters at the end of the "decrypt" line.
JavaScript:
text: >This is an example. !##$%^&*(){}[]:;<
Base64: PlRoaXMgaXMgYW4gZXhhbXBsZS4gIUAjJCVeJiooKXt9W106Ozw=
key: 8GmZWww5T97jb39W
encrypt: sIubYrII6jVXvMikX1oQivyOXC07bV1CoC81ZswcCV4tkg5CnrTtqQ==
decrypt: >This is an example. !##$%^&*(){}[]:;<
The reason there is no garbage in the JavaScript implementation even though it does not save the length of the plaintext is given in a comment there: "note running off the end of the string generates nulls since bitwise operators treat NaN as 0". In other words, the generated string is padded with NULs that are never seen, even though JavaScript, like PHP, can include NULs in strings because it stores the length separately.
I don't have an opinion about which approach is best, but one should be chosen for all implementations.
The reason that there should be a standard for the result of encryption (regardless of whether the binary is converted to hex or to base64 for safe transit) is that one might want to use, say, PHP for encoding but JavaScript for decoding, depending on which languages are natural to use at two locations. After all, encryption is most often used to communicate between two locations, and the language used at the target location might not even be known.
Why not using the wificlientsecure library? Works great on the esp32.
There is one project I worked on where a teammate added phpseclib (for php5) and we use the code below to sign an encrypted string and send that with a request to an internal API for verification processes.
function GetSignature($message) {
$rsa = new Crypt_RSA();
$sPath = PRIVATE_KEY_FILE; //defined elsewhere
$sKey = file_get_contents($sPath);
$rsa->LoadKey($sKey);
$rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
return $rsa->sign($message);
};
For a string that contains a timestamp concatenated with a URL (e.g. 1490239371+https://api-domain/api/endpoint), that would generate a string like below. This string is sent in a request header and the string below is the output in the internal API's console log.
Rsh1dv5ZGaPjCb6pgEsMrXwZbAeVgxVK/+5d5bxu7BfDzaILl++/pi/WxDP4H2qJ7Ayp6QnYXGckIIYX9l9fKOJoShOZOkB19RxaNdBL5vLjKk409XVRY/GKGz3kHmZmTcyBYDPQaT/VFOQTd7+o0d1mBY4EbHadI3f+kahHz4U=
In a newer project, I am attempting to recreate this because we need to utilize the same internal API. The newer project is utilizing composer (and laravel) so adding phpseclib to it added version 2.0. The constants have been changed to class constants but other than that it appears the methods are identical. Below is the code I added to replicate the former code in the other codebase.
use phpseclib\Crypt\RSA as Crypt_RSA;
class EncyptionController {
public static function GetSignature($message) {
$rsa = new Crypt_RSA();
$path = config('app.encryption_key_path') . '/certs/private.pem';
$rsa->loadKey(file_get_contents($path));
$rsa->setSignatureMode(Crypt_RSA::SIGNATURE_PKCS1);
return $rsa->sign($message);
}
With the newer code, a string of the same format would yield an encrypted string that contains unicode characters. The string below appears in the internal API's console log (when logging the value received in the request headers).
=���˭\u0003�uʂ\u000e_\u0013\u000bd)w�8�\u0018�����q
Logging that in Laravels log shows it with the unicode characters (without the entities encoded - I opened it in notepad++):
=žšîË´uÊ‚_d)wË8ƒ”ñ‘¤úqýÁ‹þ:‹dºäÿLÈ?!F§?…ãIEìê*K®gUþèÎÅ
And then the internal API decrypts the encoded string using the public key. For the newer codebase this fails. The internal API is implemented in Nginx/NodeJS and uses crypto and its verifier.verify() method.
Update:
Based on the comment by Álvaro González, it has come to light that the version 2.0 sign() method returns a raw binary hash. I can use base64_encode() to base-64 encode it, but the string is quite a bit shorter (88 characters) than the strings produced by version 1.0 (172 characters).
return base64_encode($rsa->sign($message));
version 1.0 string - 172 characters long:
Rsh1dv5ZGaPjCb6pgEsMrXwZbAeVgxVK/+5d5bxu7BfDzaILl++/pi/WxDP4H2qJ7Ayp6QnYXGckIIYX9l9fKOJoShOZOkB19RxaNdBL5vLjKk409XVRY/GKGz3kHmZmTcyBYDPQaT/VFOQTd7+o0d1mBY4EbHadI3f+kahHz4U='
"Rsh1dv5ZGaPjCb6pgEsMrXwZbAeVgxVK/+5d5bxu7BfDzaILl++/pi/WxDP4H2qJ7Ayp6QnYXGckIIYX9l9fKOJoShOZOkB19RxaNdBL5vLjKk409XVRY/GKGz3kHmZmTcyBYDPQaT/VFOQTd7+o0d1mBY4EbHadI3f+kahHz4U=
version 2.0 string - 88 characters long:
mYT/MSb9UOuDDQ1RV893Ix7xh21IRHINs6o1PnhdhffgTAIeX4le1rfd+EzoPPJN9pvvwirm3CAbkeubGjXgWQ==
Is there a method to set the length of the signed string?
In v2.0 it's RSA() - not Crypt_RSA(). http://phpseclib.sourceforge.net/2.0.html elaborates.
Anyway, in base64 decoding your two ciphertext's and then getting the length of each... the first one (the one produced by phpseclib-php5) I get 256 bytes or 2048 bits. The second one (the one produced by phpseclib 2.0) is 64 bytes of 512 bits.
I think you're using different keys of different lengths for each one and that'd definitely affect the length.
I have an applet that uses a "foo.key" file and a string password to generate a privateKey object, using BouncyCastle apis. Then a signature is created using this privateKey object and a string such as "MYNAMEHERE". All I know is that the algorythm used to generate this signature is RSA.
What I want to do is to decrypt (or verify) this signature in PHP. I have both files publicKey (file.cer) and privateKey (file.key) that where used to generate the privateKey object in Java.
Im trying using the openssl_verify functions in PHP, passing the values:
openssl_verify("MYNAMEHERE", $signature, "file.cer"), where $signature contains the String representation of the signature object generated in Java: new String (signature).
I dont know if this process is correct to verify the signature, or what kind of encoding/decoding process i have to do before using this function.
I hope somebody points me the right direction!
Thanks in advance!
You haven't given enough information, such as the actual signature or how it is encoded. Normally RSA means RSA in PKCS#1 1.5 mode using SHA-1 (Google it) which is more or less the default signature generation/verification algorithm in use today. In that case, the verify should proceed as you've described. The password is not needed anymore, it might just be used to decrypt the private key. You can still use the private key to see if an sign in PHP/openssl does create the same data. If not, a different hash or PKCS#1 v2.1 signature may have been used.