php – Why is chr() vulnerable to cache-timing attack and pack() not?


Out of curiosity I'm looking and slowing down a lot of things about cryptography and also looking for some libraries in pure PHP, such as Sodium_Compat , for the only reason that I understand much more about PHP compared to C, which is ideal for cryptography, as far as I know .

However, something caught my attention in the following snippet :

* Use pack() and binary operators to turn the two integers
* into hexadecimal characters. We don't use chr() here, because
* it uses a lookup table internally and we want to avoid
* cache-timing side-channels.
$hex .= pack(
(55 + $b + ((($b - 10) >> 8) & ~6)),
(55 + $c + ((($c - 10) >> 8) & ~6))

I just didn't find much information about cache-timing , remembering that there is cache-timing attack and timing attack , which are different things (or maybe not?), the second has more information.

I found this information here , which is the shortest and there is this other answer and comment that seems to be almost the answer to this question , but I'm not sure. Even based on the code comment I assume that the problem is that chr() uses some kind of "array" (would this be the lookup table internally ?) while pack() does not, but how then is pack() able to convert the integer to hexadecimal?

The thing is, pack() is not supposed to be vulnerable while chr() has such a problem, it's not even mentioned in the PHP documentation, maybe because it's too specific . What are the functions, inside, different? How do they convert integers and why does one convert "safer than the other"? Why aren't they both vulnerable?


The implementation of the pack() function is much more robust than the chr() function.

It has several sanitizations and validations, among them, the RFC 4648 Encoding

A snippet of the pack() function routines where input values ​​are filtered:

static void php_pack(zval *val, size_t size, int *map, char *output)
    size_t i;
    char *v;

    v = (char *) &Z_LVAL_P(val);

    for (i = 0; i < size; i++) {
        *output++ = v[map[i]];


Functions like ord() and chr() , don't have much to comment on. Just see how the simple implementation doesn't have filters like in the pack() function:

    char   *str;
    size_t str_len;

        Z_PARAM_STRING(str, str_len)

    RETURN_LONG((unsigned char) str[0]);
/* }}} */

    zend_long c;

    if (ZEND_NUM_ARGS() != 1) {


    c &= 0xff;
    if (CG(one_char_string)[c]) {
        ZVAL_INTERNED_STR(return_value, CG(one_char_string)[c]);
    } else {
        ZVAL_NEW_STR(return_value, zend_string_alloc(1, 0));
        Z_STRVAL_P(return_value)[0] = (char)c;
        Z_STRVAL_P(return_value)[1] = '\0';


This is simple that way for performance reasons because you don't always need to filter. Filters are more suitable for employing encryption, security, etc. routines.

table look-ups

Even based on the code comment I assume that the problem is that chr() uses some kind of "array" (is this the lookup table internally?)

Yes, that's how most programming languages ​​use it in their string manipulation functions.

The "table look-ups" method has known exploits and is heavily exploited by attacks. A proposed solution is to use logical operations of "constant-time" sequences (RFC 4648) or at least by references other than the secret key data (secret data). This issue is currently under open discussion, without a concrete definition.

Scroll to Top