How to pass Int to Base64 in PHP?

Base64 can store 6 bits for each character used. Assuming we are using int64 or uint64 we use 64 bits, which could be represented in ~11 characters.

I tried to answer this question, but PHP fails to convert the values correctly.

$int = 5460109885665973483;
echo base64_encode($int);

Returns:

NTQ2MDEwOTg4NTY2NTk3MzQ4Mw==

This is incorrect, we are using 26 characters to represent 64 bits! That's insane. I even understand the reason, it uses the value as string, not as int. only the conversion to string makes use of 19 bytes, which therefore (19*8)/6 characters are used by PHP.

However, other languages handle at the byte level, such as Golang:

bt := make([]byte, 8)
binary.BigEndian.PutUint64(bt, 5460109885665973483)

fmt.Print(base64.StdEncoding.EncodeToString(bt))

Returns:

S8Y1Axm4FOs=

S8Y1Axm4FOs= is exactly 11 characters (ignoring padding), which is exactly the 64-bit representation. in this case you can retrieve the value using the binary.BigEndian.Uint64 after the decode of Base64.


Which way could I get the same result as Golang in PHP?

 10
Author: Inkeliz, 2017-12-31

2 answers

The best way to do this in PHP is by using the pack. This function will allow you to have a big-endian implementation of byte order.

<?php

$byte_array = pack('J*', 5460109885665973483);    
var_export( base64_encode($byte_array) );

// Output: S8Y1Axm4FOs=

To reverse this process, you can use the opposite functionunpack

<?php

$encoded = "S8Y1Axm4FOs=";

$decoded = base64_decode($encoded);

var_export( unpack("J*", $decoded) );

// Output: [ 1 => 5460109885665973483 ]

The J * represents a 64 bit, big endian byte order

 11
Author: Valdeir Psr, 2017-12-31 21:32:32

The answer of @Valdeir Psr answers the question and solves the problem. However, I had a completely different idea of solving the situation, using bitwise.

I thought of simply dividing the value every 6 bits, then encoding it to base64. This would not approve of side-channel attacks (in the same way as the original PHP), but it would be enough for the purpose, I believe.

I tried to execute this idea, and... it worked. So, I'm sharing here, although I will use pack.


So just do:

function base64_encode_int64(int $int) : string {
    $alfabeto = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
    $base64 = '';

    for($l = 58; $l > 0; $l -= 6){
        $base64 .= $alfabeto[($int >> $l) & 0x3F];
    }
    $base64 .= $alfabeto[($int << 2) & 0x3F];

    return $base64;
}

The last shift should be reversed, because it has only 4 bits, 6 are needed. Then you need to add 2 bits to the end, for this reason the shift "to the opposite side".

To decode we use |, which is the simplest solution, I believe.

function base64_decode_int64(string $base64) {
    $alfabeto = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
    $int = 0;

    if(mb_strlen($base64, '8bit') !== 11){
        return false;
    }

    for($l = 58; $l > -3; $l -= 6){
        $letra = strpos($alfabeto, $base64[(58-$l)/6]);
        if($letra === false) {
            return false;
        }
        if($l > 0){
            $int |= ($letra) << $l;
        }else{
            $int |= ($letra) >> 2;
        }
    }


    return $int;
}

I don't believe that strpos is the best option, plus the amount of if is in disturbing a little. This was accurate because the entry ($base64) must use the same dictionary, so it must return false in case of error, as well as being limited to 11 characters.

The if($l > 0){ I brought into the for, but I do not believe that it is not ideal. I did this so as not to have to create a new condition outside the loop (duplicate the if($letra)), but I believe there must be a way to make this "universal", maybe by doing a few shifts before (to the opposite side), no you're .


Now the tests:

echo $int = 5460109885665973483;
echo PHP_EOL;
echo $b64 = base64_encode_int64($int);
echo PHP_EOL;
echo base64_decode_int64($b64);

Returns:

5460109885665973483
S8Y1Axm4FOs 
5460109885665973483

Test it here

 3
Author: Inkeliz, 2018-02-08 13:11:07