# RFC: Secure serialization by authentication code

- Status: Inactive
- Authors: Yasuo Ohgaki yohgaki@php.net
- Date: 2015-12-30
- Version: 0.1
- Original: https://wiki.php.net/rfc/secure_serialization
- Page: https://externals.io/rfc/secure_serialization
- More for agents: search with https://externals.io/search.md?q={query} · RFCs at https://externals.io/rfc.md · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

## Discussions on the mailing list

No thread found.

---

## Introduction

PHP variable serialization was source of security issues. The root cause of issues is “crafted serialized data”. Crafted serialized data can be rejected by message authentication code using crypt-graphic hash function. This RFC proposes serialize/unserialize function that supports authentication code.

Recent vulnerability examples are Joomla! 3.4.6 and 3.4.7.

- <https://developer.joomla.org/security-centre/639-20151206-core-session-hardening.html>
- <https://developer.joomla.org/security-centre/630-20151214-core-remote-code-execution-vulnerability.html>

Making sure if serialized data is generated by the server/system before unserialization is mandatory for security. Even though this is possible in userland, but it's rarely implemented. Therefore, PHP is better to provide function for secure serialization/unserialization. Proposed secure serialization functions prevent crafted serialized data to be passed to php\_unserialize(). Therefore, this RFC can also protect unknown serialize vulnerabilities even if they exist.

## Proposal

- Add serialize\_mhac() and unserialize\_mhac() supports message authentication code generation/validation and expiration.

```php
  string serialize_mhac(mixed $data_to_be_serialized , string $secret_key [, int $ttl=1800 [,bool $session_only=TRUE]])
  mixed unserialize_mhac(mixed $data_to_be_unserialized , mixed $secret_keys)
```

### How serialize\_mhac() works

Pseudo code

```php
function secure_serialize(string $data_to_be_serialized, string $secret_key, int $ttl=1800, bool $session_only = TRUE) : string {
  if (strlen($secret_key) < 32) {
    trigger_error('Too short secret key');
    return FALSE;
  }
  if ($ttl < 0) {
    trigger_error('Invalid TTL');
    return FALSE;
  }
  $ttl = $ttl ? time() + $ttl : 0; 
  $session_only = $session_only ? TRUE : FALSE;
  // Use random key to randomize $mac
  $key = sha256(random_bytes(64));
 
  $serialized_data = serialize($data_to_be_serialized);
  $keys = is_array($secret_key) ?: array(secret_key);
 
  if ($session_only && session_id()) {
    // Session ID is hashed by SHA256 to avoid session ID exposure.
    $mac = hash_hmac(
      'sha256',
      $ttl.$key.sha256($secret_key.session_id()).$serialized_data),
      $secret_key);
    // Serialize these data with special/simple format.
    return __serialize__(['mac'=>$mac, 'ttl'=>$ttl, 'key'=>$key, 'id'=>sha256($secret_key.session_id()),  'data'=>$serialized_data]);
  } else {
    $mac = hash_hmac(
      'sha256',
      $ttl.$key.$serialized_data),
      $secret_key);
    // Serialize these data with special/simple format.
    return __serialize__(['mac'=>$mac, 'ttl'=>$ttl, 'key'=>$key, 'data'=>$serialized_data]);
  }
}
```

### How unserialize\_mhac() works

Pseudo code

```php
function unserialize_mhac(string $data_to_be_unserialized, mixed $secret_key) : mixed {
  if (strlen($secret_key) < 32) {
    trigger_error('Too short secret key');
    return FALSE;
  }
 
  // Unserialize special format
  $tmp = __unserialize__($data_to_be_unserialized);
  if ($tmp['ttl'] && $tmp['ttl'] < time() {
    // Serialized data is expired
    return FALSE;
  }
 
  $keys = is_array($secret_key) ?: array[$secret_key];
  foreach ($keys in $k) { 
    if (isset($tmp['id'])) {
      // Old session ID may be used if session module stores old IDs in internal data.
      // https://externals.io/rfc/precise_session_management
      $mac = hash_hmac(
        'sha256',
        $tmp['ttl'].$tmp['key'].sha256($k.session_id()).$tmp['data'],
        $k);
    } else {
      $mac = hash_hmac(
        'sha256',
        $tmp['ttl'].$tmp['key'].$tmp['data'],
        $k);
    }
    if ($mac !== $tmp['mac']) {
       continue;
    }
    // Unserialize data normally and return
    return unserialize($tmp['data']);
  }
  return FALSE;
}
```

## Backward Incompatible Changes

None.

## Proposed PHP Version(s)

PHP 7.1

## RFC Impact

### To SAPIs

None.

### To Existing Extensions

None.

### To Opcache

None.

### New Constants

None.

### php.ini Defaults

If there are any php.ini settings then list:

- hardcoded default values
- php.ini-development values
- php.ini-production values

No changes.

## Open Issues

Make sure there are no open issues when the vote starts!

## Unaffected PHP Functionality

List existing areas/features of PHP that will not be changed by the RFC.

This helps avoid any ambiguity, shows that you have thought deeply about the RFC's impact, and helps reduces mail list noise.

## Future Scope

If session module stores old session ID, automatic fallback to old session ID may be supported.

Encryption is more secure than authentication code. Implement serialize\_crypt/unserialize\_crypt when standard encryption module is introduced.

## Proposed Voting Choices

50%+1 majority

## Patches and Tests

TBD

## Implementation

After the project is implemented, this section should contain

1. the version(s) it was merged to
2. a link to the git commit(s)
3. a link to the PHP manual entry for the feature

## References

Links to external references, discussions or RFCs

## Rejected Features

Keep this updated with features that were discussed on the mail lists.
