generate php access token - php

I'm trying to generate an access token in php for user authorization
I used the following:
base64_encode(com_create_guid());
is this correct use in this context? and what is meant by "cryptographically secure"?
EDIT:
First, I was going to generate a random string using a function like this,
function generateRandomString($length = 10) {
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$charactersLength = strlen($characters);
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$randomString .= $characters[rand(0, $charactersLength - 1)];
}
return $randomString;
}
but then I read about the token has to be cryptographically secure, and I can't really get what is this exactly or precisely.
then I went to com_create_guid() , but I noticed that com_create_guid() generates UUID, which does not include all characters (just numbers + a-f of hex). So I thought that base64 encoding may be suitable to convert this into a full character token (not for security).
here I'm asking about this being suitable for generating an access token, and if not is there a better way for generating it?

Returns a random string with crypto-strength given in $len. The string is always longer than $len by 3-4 expansion (that is, it takes 4 characters to encode 3 length). Pass a $len of at least 16 for good strength.
Stolen from https://stackoverflow.com/a/8429944/14768 and edited to use /dev/random instead of /dev/urandom
function generateRandomString($len)
{
$fp = #fopen('/dev/random','rb');
$result = '';
if ($fp !== FALSE) {
$result .= #fread($fp, $len);
#fclose($fp);
}
else
{
trigger_error('Can not open /dev/urandom.');
}
// convert from binary to string
$result = base64_encode($result);
// remove none url chars
$result = strtr($result, '+/', '-_');
// Remove = from the end
$result = str_replace('=', ' ', $result);
return $result;
}
When wanting crypt-strength stuff use crypt-strength stuff. guidv4 is not guaranteed to be crypt-strength.

You might want to use this:
function guidv4()
{
if (function_exists('com_create_guid') === true)
return trim(com_create_guid(), '{}');
$data = openssl_random_pseudo_bytes(16);
$data[6] = chr(ord($data[6]) & 0x0f | 0x40); // set version to 0100
$data[8] = chr(ord($data[8]) & 0x3f | 0x80); // set bits 6-7 to 10
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
source: http://php.net/manual/en/function.com-create-guid.php#117893
This code is more cryptographically secure, however, for what you are doing you should be fine.
Cryptographically secure in layman's terms is something that is random, and is hard to recreate or predict.

Related

NodeJS Crypto equivalent in PHP?

I'm looking for a way to decrypt a file since a few weeks. But it's impossible to recover the file intact in PHP, only with node. But I would like to do it without node.
If someone can tell me where I could be wrong... ?
I tried with openssl_encrypt/decrypt then with OPENSSL_NO_PADDING & ZERO_PADDING options. Transform the result in base64 but impossible to have the good result...
I thank you in advance I do not know what to do...
Here is my NodeJs crypto code :
decrypt(encryptedBuffer) {
const PASSPHRASE = "";
let decryptedBuffer = Buffer.alloc(encryptedBuffer.length);
let chunkSize = 2048;
let progress = 0;
while (progress < encryptedBuffer.length) {
if ((encryptedBuffer.length - progress) < 2048) {
chunkSize = encryptedBuffer.length - progress;
}
let encryptedChunk = encryptedBuffer.slice(progress, progress + chunkSize);
// Only decrypt every third chunk and only if not at the end
if (progress % (chunkSize * 3) === 0 && chunkSize === 2048) {
let cipher = crypto.createDecipheriv('bf-cbc', PASSPHRASE, Buffer.from([0, 1, 2, 3, 4, 5, 6, 7]));
cipher.setAutoPadding(false);
encryptedChunk = Buffer.concat([cipher.update(encryptedChunk), cipher.final()]);
}
decryptedBuffer.write(encryptedChunk.toString('binary'), progress, encryptedChunk.length, 'binary');
progress += chunkSize;
}
return decryptedBuffer;
}
Here in PHP
public function decrypt($encryptedBuffer)
{
ini_set('memory_limit', '1G');
$f = fopen('myfile', 'wb+');
$chunkSize = 2048;
$progress = 0;
$passphrase = "h5ihb>p9`'yjmkhf";
while ($progress > strlen($encryptedBuffer)) {
// If the buffer is if the end calculate the valid chunksize
if ((strlen($encryptedBuffer) - $progress) < 2048) {
$chunkSize = strlen($encryptedBuffer) - $progress;
}
/** Getting the encrypted chunk part */
$encryptedChunk = substr($encryptedBuffer, $progress, $progress + $chunkSize);
// Only decrypt every third chunk and only if not at the end
if ($progress % ($chunkSize * 3) === 0 && $chunkSize === 2048) {
$encryptedChunk = openssl_decrypt($encryptedChunk, 'bf-cbc', $passphrase, OPENSSL_ZERO_PADDING, '01234567');
}
fwrite($f, $encryptedChunk);
$progress += $chunkSize;
}
}
There are several flaws in the PHP code:
The condition in the while loop is wrong and should be:
$progress < strlen($encryptedBuffer).
$encryptedChunk is determined incorrectly because substr() expects the length in the third parameter. The correct way is:
$encryptedChunk = substr($encryptedBuffer, $progress, $chunkSize);
In the openssl_decrypt() call, too few flags are set in the fourth parameter:
Apart from disabling the padding, the default Base64 decoding has to be disabled with OPENSSL_RAW_DATA.
For key sizes smaller than 16 bytes, the padding of the key with 0x00 values to a length of 16 bytes has to be disabled with OPENSSL_DONT_ZERO_PAD_KEY. This is a PHP bug (s. here). The fix, i.e. the flag is available as of version 7.1.8.
Overall: OPENSSL_DONT_ZERO_PAD_KEY | OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING.
The wrong IV is used, correct would be: hex2bin('0001020304050607').
With these changes, decryption with the PHP code works.
Regarding security: The short block size makes Blowfish vulnerable to birthday attacks, see here. Using a static IV is generally insecure. Rather, a random IV should be generated for each encryption.

Sagepay integration without mcrypt

I am looking to integrate with Sagepay/Opayo Form using their guidance here:
https://www.sagepay.co.uk/file/25041/download-document/FORM_Integration_and_Protocol_Guidelines_270815.pdf?token=Cfj49hcaD4kpE0zk7179ZLOaQx2RH_3oatPOrAV6MyM
Sagepay have now stopped supporting SDKs which means I've had to find a 2013 utility set from here:
https://github.com/ammaar23/sagepay-sdk-php/blob/master/lib/classes/util.php
I am trying to migrate away from mcrypt towards openssl_encrypt but cannot seem to replicate the desired result.
There are 2 functions in play:
static protected function addPKCS5Padding($input)
{
$blockSize = 16;
$padd = "";
// Pad input to an even block size boundary.
$length = $blockSize - (strlen($input) % $blockSize);
for ($i = 1; $i <= $length; $i++)
{
$padd .= chr($length);
}
return $input . $padd;
}
static public function encryptAes($string, $key)
{
// AES encryption, CBC blocking with PKCS5 padding then HEX encoding.
// Add PKCS5 padding to the text to be encypted.
$string = self::addPKCS5Padding($string);
// AH updated as mcrypt is now deprecated! 2020
$cipher = 'AES-128-CBC';
$ivsize = openssl_cipher_iv_length($cipher);
$iv = openssl_random_pseudo_bytes($ivsize);
$crypt = openssl_encrypt($string,$cipher,$key,0,$iv);
// Perform hex encoding and return.
return "#" . strtoupper(bin2hex($crypt));
}
You can see my attempts to implement openssl_encrypt but it isn't working. The key is 55a51621a6648525 and the input string is VendorTxCode=TxCode-1310917599-223087284&Amount=36.95&Currency=GBP&Description=description&CustomerName=Fname Surname&CustomerEMail=customer#example.com&BillingSurname=Surname&BillingFirstnames=Fname&BillingAddress1=BillAddress Line 1&BillingCity=BillCity&BillingPostCode=W1A 1BL&BillingCountry=GB&BillingPhone=447933000000&DeliveryFirstnames=Fname&DeliverySurname=Surname&DeliveryAddress1=BillAddress Line 1&DeliveryCity=BillCity&DeliveryPostCode=W1A 1BL&DeliveryCountry=GB&DeliveryPhone=447933000000&SuccessURL=https://example.com/success&FailureURL=https://example.com/failure
and the output result is:
#2DCD27338114D4C39A14A855702FBAB2EF40BCAC2D76A3ABC0F660A07E9C1C921C2C755BA9B59C39F882FBF6DFED114F23141D94E50A01A665B1E31A86C07CA1CD1BB8EF5B6CF2C23D495CD 79F9C0F678D61773E7A1AA30AA5B23D56503FC0B52AC0694A8C341263D2C5FE1BAD93BDB94726761E155E900448F644AF1F67BE1AC77E852B9D90809A44F258EE9478B6D8C1C4ED58759263E7DBF 8871C6592287C0358F36F4EEC326CEDDD440DA2FED8AB35F1B630A5C6FA671E4D78CC8CACECF9DFDC31D6C5EC8270FB21E297E2C2E14F99A04223EFFD4F00062D440E78A3D2C7140EC8F123D24 7B75E7482AE98858DA34D37EDE6D7C69AA74391F559305CF675ADB3615244A107ABBB6AF26E29A2FFA059B12688D90FE09E0DE069325BFF3587A695F5DA36E4B809B69CC9A37034F166B63B5A62 B986F4DA34E9AC9516AFDE70642EC7DAD1AEBA93A1F347D6AC7046E967DCBFE7ACFCEE5DAFC0B29F1765032B3060EBE565CBD57D092075D15CF12725199C6881605B2E0F105698CE3ADD04361C A9D620C187B90E3F9849445B5C3C0FDF1768BFFD61F97E51316826F4F10E0E3E668F0A9F5ED9CCDA6F2C7CC957F12DB48F9041482E3D035E7A91852C404BFA325FED947E71F57B871DFAC6AF4FF2 9F4513A4A80B2D7ECC9D19D47ED04FA99CDFC881DFA771E1EA4F3F9B2C5AC673EF3DA2699A309CC8522993A63CB8D45D3CDF09B1DFDC573CD19679B250AD6721450B5042F201670B464505DCAE F59E2C67ABACC9AE2EEE793CE191FEBF66B8FAF4204EFFB359246B9C99FB52805C46375FF35140F74707FBC73C7731A28A2C883A
How can I create the desired output from my input code because the output I generate starts with: 403444324634333730363535313636373136413432363737353332363133303644373736
openssl_encrypt applies PKCS7 padding by default, so the addPKCS5Padding method is no longer needed.
In the mcrypt_encrypt code iv = key is used, so you must not generate a random IV for code equivalence.
In openssl_encrypt the flag OPENSSL_RAW_DATA must be applied as 4th parameter, otherwise the data will be returned Base64 encoded.
With these changes, the openssl_encrypt code produces the same result as the mcrypt_encrypt code:
<?php
function encryptAes($string, $key)
{
//$string = self::addPKCS5Padding($string); // don't pad explicitly
$cipher = 'AES-128-CBC';
//$ivsize = openssl_cipher_iv_length($cipher);
//$iv = openssl_random_pseudo_bytes($ivsize); // mcrypt_encrypt code: iv = key
$crypt = openssl_encrypt($string,$cipher,$key,OPENSSL_RAW_DATA,$key); // use raw data
return "#" . strtoupper(bin2hex($crypt));
}
$plain = "VendorTxCode=TxCode-1310917599-223087284&Amount=36.95&Currency=GBP&Description=description&CustomerName=Fname Surname&CustomerEMail=customer#example.com&BillingSurname=Surname&BillingFirstnames=Fname&BillingAddress1=BillAddress Line 1&BillingCity=BillCity&BillingPostCode=W1A 1BL&BillingCountry=GB&BillingPhone=447933000000&DeliveryFirstnames=Fname&DeliverySurname=Surname&DeliveryAddress1=BillAddress Line 1&DeliveryCity=BillCity&DeliveryPostCode=W1A 1BL&DeliveryCountry=GB&DeliveryPhone=447933000000&SuccessURL=https://example.com/success&FailureURL=https://example.com/failure";
$key = "55a51621a6648525";
print(encryptAes($plain, $key));
?>
For the choice iv = key note also the answers to this question.
#Topaco answered beautifully. Here is an additional bit, ( not related to the question ), while doing Sagepay integration you will need the decrypt function too for verifying payment/callback. Here is the function you can use.
function decrypytAES($string, $key) {
$cipher = 'AES-128-CBC';
$strIn = hex2bin(substr($strIn, 1));
return openssl_decrypt($strIn, $cipher, $key, OPENSSL_RAW_DATA, $key);
}

JWT decode includes unwanted html tags

im usin the following php function to return the value of the 'uid' claim in the payload of a jwt:
function isLoggedIn($headers)
{
$ret = false;
if (!empty($headers['Authorization']))
{
$parts = explode('.', $headers['Authorization']);
echo base64_decode($parts[1]);
return 7; //currently set a 7 just function
}
}
the string returned in
echo base64_decode($parts[1]);
has html tags included
<br />"iss": "www.thetenticle.com",<br />"iat": "1449405778",<br />"nbf": "1449405838",<br />"exp": "1449492238",<br />"uid": "batman"<br />}
i dont want this because i need to find out what is in the value of 'uid'.
what am i doing wrong?
ps i know there is more to handling a jwt than this, but for now i just need to get the id of the logged in in user.
i essentially need an array of claims
From another answer on SO:
The problem is related to the fact that the base64 alphabet is not URL-safe. In this particular case, your base64-encoded string contains a +, which is interpreted as a space.
Code from php-jwt to safely decode the input:
public static function urlsafeB64Decode($input) {
$remainder = strlen($input) % 4;
if($remainder) {
$padlen = 4 - $remainder;
$input .= str_repeat('=', $padlen);
}
return base64_decode(strtr($input, '-_', '+/'));
}

Sagepay Error - 3045 : The Currency field is missing

Please do not mark it as duplicate. I have already gone through almost all the previous post for same issue, but was not able to tackle my error.
I am trying to upgrade the Sagepay Protocol from v2.23 to v3.00
I have also deployed PHP kit provided by Sagepay and getting the same error.
Below is the AES encryption I am using to make it compatible for v3.00 in my includes.php
Any guidance would be much appreciated.
<?
/* Base 64 Encoding function **
** PHP does it natively but just for consistency and ease of maintenance, let's declare our own function **/
function base64Encode($plain) {
// Initialise output variable
$output = "";
// Do encoding
$output = base64_encode($plain);
// Return the result
return $output;
}
/* Base 64 decoding function **
** PHP does it natively but just for consistency and ease of maintenance, let's declare our own function **/
function base64Decode($scrambled) {
// Initialise output variable
$output = "";
// Fix plus to space conversion issue
$scrambled = str_replace(" ","+",$scrambled);
// Do encoding
$output = base64_decode($scrambled);
// Return the result
return $output;
}
/* The SimpleXor encryption algorithm **
** NOTE: This is a placeholder really. Future releases of Form will use AES or TwoFish. Proper encryption **
** This simple function and the Base64 will deter script kiddies and prevent the "View Source" type tampering **
** It won't stop a half decent hacker though, but the most they could do is change the amount field to something **
** else, so provided the vendor checks the reports and compares amounts, there is no harm done. It's still **
** more secure than the other PSPs who don't both encrypting their forms at all */
function simpleXor($InString, $Key) {
// Initialise key array
$KeyList = array();
// Initialise out variable
$output = "";
// Convert $Key into array of ASCII values
for($i = 0; $i < strlen($Key); $i++){
$KeyList[$i] = ord(substr($Key, $i, 1));
}
// Step through string a character at a time
for($i = 0; $i < strlen($InString); $i++) {
// Get ASCII code from string, get ASCII code from key (loop through with MOD), XOR the two, get the character from the result
// % is MOD (modulus), ^ is XOR
$output.= chr(ord(substr($InString, $i, 1)) ^ ($KeyList[$i % strlen($Key)]));
}
// Return the result
return $output;
}
//** Wrapper function do encrypt an encode based on strEncryptionType setting **
function encryptAndEncode($strPost) {
global $strEncryptionType
,$strEncryptionPassword;
if ($strEncryptionType=="XOR")
{
//** XOR encryption with Base64 encoding **
return base64Encode(simpleXor($strPost,$strEncryptionPassword));
}
else
{
//** AES encryption, CBC blocking with PKCS5 padding then HEX encoding - DEFAULT **
//** use initialization vector (IV) set from $strEncryptionPassword
$strIV = $strEncryptionPassword;
//** add PKCS5 padding to the text to be encypted
$strPost = addPKCS5Padding($strPost);
//** perform encryption with PHP's MCRYPT module
$strCrypt = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $strEncryptionPassword, $strPost, MCRYPT_MODE_CBC, $strIV);
//** perform hex encoding and return
return "#" . bin2hex($strCrypt);
}
}
//** Wrapper function do decode then decrypt based on header of the encrypted field **
function decodeAndDecrypt($strPost) {
global $strEncryptionPassword;
if (substr($strPost,0,1)=="#")
{
//** HEX decoding then AES decryption, CBC blocking with PKCS5 padding - DEFAULT **
//** use initialization vector (IV) set from $strEncryptionPassword
$strIV = $strEncryptionPassword;
//** remove the first char which is # to flag this is AES encrypted
$strPost = substr($strPost,1);
//** HEX decoding
$strPost = pack('H*', $strPost);
//** perform decryption with PHP's MCRYPT module
return mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $strEncryptionPassword, $strPost, MCRYPT_MODE_CBC, $strIV);
}
else
{
//** Base 64 decoding plus XOR decryption **
return simpleXor(base64Decode($strPost),$strEncryptionPassword);
}
}
//** PHP's mcrypt does not have built in PKCS5 Padding, so we use this
function addPKCS5Padding($input)
{
$blocksize = 16;
$padding = "";
// Pad input to an even block size boundary
$padlength = $blocksize - (strlen($input) % $blocksize);
for($i = 1; $i <= $padlength; $i++) {
$padding .= chr($padlength);
}
return $input . $padding;
}
/*************
function pkcs5_pad($text, $blocksize)
{
$pad = $blocksize - (strlen($text) % $blocksize);
//echo "<br/>Padding:".str_repeat(chr($pad), $pad)."<";
return $text . str_repeat(chr($pad), $pad);
}
function encryptFieldData($input)
{
$key = "[mykey]";
$iv = $key;
$cipher = mcrypt_module_open(MCRYPT_RIJNDAEL_128, "", MCRYPT_MODE_CBC, "");
if (mcrypt_generic_init($cipher, $key, $iv) != -1)
{
$cipherText = mcrypt_generic($cipher,$input );
mcrypt_generic_deinit($cipher);
$enc = bin2hex($cipherText);
}
return $enc;
}
$str = "Currency=GBP";
$datapadded = pkcs5_pad($str,16);
$cryptpadded = "#" . encryptFieldData($datapadded);
*************************/
?>
The problem was with my server mcrypt PHP extension. It wasn't install/enable on the server. I must have consider this before making all test attempts.
Thank you all for the efforts towards my issue.
Have you checked your encryption password is correct? 3045 is the first error to be thrown if the password is wrong. This is not so nuts as it sounds - encryption passwords are different between test and live.....

creating encrypted passwords in openfire MySQL via PHP

Openfire stores encrypted passwords in a database using blowfish encryption.
http://svn.igniterealtime.org/svn/repos/openfire/trunk/src/java/org/jivesoftware/util/Blowfish.java is the java implementation for how encrypt / decrypt functions work in openfire.
My goal is to create new user entries in the database via PHP and MySQLI. All of the variations I've tried have yielded results that don't match what already exists in the database. For example:
d3f499857b40ac45c41828ccaa5ee1f90b19ca4e0560d1e2dcf4a305f219a4a2342aa7364e9950db is one of the encrypted passwords. clear text, this is stackoverflow
I've tried a few variations:
echo mcrypt_cbc(MCRYPT_BLOWFISH, '1uY40SR771HkdDG', 'stackoverflow', MCRYPT_ENCRYPT, '12345678');
// result: áë*sY¶nŸÉX_33ô
Another based on mcrypt blowfish php slightly different results when compared to java and .net
$key = '1uY40SR771HkdDG';
$pass = 'stackoverflow';
$blocksize = mcrypt_get_block_size('blowfish', 'cbc'); // get block size
$pkcs = $blocksize - (strlen($data) % $blocksize); // get pkcs5 pad length
$data.= str_repeat(chr($pkcs), $pkcs); // append pkcs5 padding to the data
// encrypt and encode
$res = base64_encode(mcrypt_cbc(MCRYPT_BLOWFISH,$key, $pass, MCRYPT_ENCRYPT));
echo $res;
// result: 3WXKASjk35sI1+XJ7htOGw==
Any clever ideas, or any glaring problems? I simply want to implement Blowfish.encryptString() as referenced in the first link in this question.
Here's a class I made, it encrypts and decrypts properly.
Note, you need to save / [pre/app]end the IV in order to reproduce results.
Some test vectors for the java code would be nice.
<?php
/**
* Emulate OpenFire Blowfish Class
*/
class OpenFireBlowfish
{
private $key;
private $cipher;
function __construct($pass)
{
$this->cipher = mcrypt_module_open('blowfish','','cbc','');
$this->key = pack('H*',sha1($pass));
}
function encryptString($plaintext, $iv = '')
{
if ($iv == '') {
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($this->cipher));
}
else {
$iv = pack("H*", $iv);
}
mcrypt_generic_init($this->cipher, $this->key, $iv);
$bs = mcrypt_enc_get_block_size($this->cipher); // get block size
$plaintext = mb_convert_encoding($plaintext,'UTF-16BE'); // set to 2 byte, network order
$pkcs = $bs - (strlen($plaintext) % $bs); // get pkcs5 pad length
$pkcs = str_repeat(chr($pkcs), $pkcs); // create padding string
$plaintext = $plaintext.$pkcs; // append pkcs5 padding to the data
$result = mcrypt_generic($this->cipher, $plaintext);
mcrypt_generic_deinit($this->cipher);
return $iv.$result;
}
function decryptString($ciphertext)
{
$bs = mcrypt_enc_get_block_size($this->cipher); // get block size
$iv_size = mcrypt_enc_get_iv_size($this->cipher);
if ((strlen($ciphertext) % $bs) != 0) { // check string is proper size
return false;
}
$iv = substr($ciphertext, 0, $iv_size); // retrieve IV
$ciphertext = substr($ciphertext, $iv_size);
mcrypt_generic_init($this->cipher, $this->key, $iv);
$result = mdecrypt_generic($this->cipher, $ciphertext); // decrypt
$padding = ord(substr($result,-1)); // retrieve padding
$result = substr($result,0,$padding * -1); // and remove it
mcrypt_generic_deinit($this->cipher);
return $result;
}
function __destruct()
{
mcrypt_module_close($this->cipher);
}
}
$enckey = "1uY40SR771HkdDG";
$enciv = 'd3f499857b40ac45';
$javastring = 'd3f499857b40ac45c41828ccaa5ee1f90b19ca4e0560d1e2dcf4a305f219a4a2342aa7364e9950db';
$a = new OpenFireBlowfish($enckey);
$encstring = bin2hex($a->encryptString('stackoverflow',$enciv));
echo $encstring . "\n";
echo $a->decryptString(pack("H*", $encstring)) . "\n";
$b = new OpenFireBlowfish($enckey);
echo $b->decryptString(pack("H*", $javastring)) . "\n";
There is nothing wrong with your code, however to generate the same code as Openfire, you will need to add in two other items before the encrypted text.
length of ciphertext
CBCIV (initialization variable)
Read "public String decryptString(String sCipherText)" in java code, it's all there. Also check the docs on how to use CBCIV in PHP.
Openfire's code prepends the CBCIV passed with the output string. It also using Unicode as the character set. These together may be the problem area.
I don't know enough about Blowfish's internals to help more, sorry.

Categories