【问题标题】:PHP session IDs -- how are they generated? [duplicate]PHP 会话 ID——它们是如何生成的? [复制]
【发布时间】:2013-09-27 01:47:08
【问题描述】:

当我调用session_start()session_regenerate_id() 时,PHP 会为会话 ID 生成看似随机的字符串。我想知道的是,它只是一个随机的字符序列,还是和uniqid()函数一样?

因为如果只是随机字符,理论上你不会遇到冲突吗?如果用户 A 登录,然后用户 B 登录,并且用户 B 生成了相同的会话 ID(尽管可能性极小),那么用户 B 最终将访问用户 A 的帐户。

即使 PHP 检查是否已经存在具有相同 ID 的会话,如果存在,则重新生成一个 ID...我不认为我想要一个系统产生两次相同的 ID,即使在垃圾之后收藏——也许我想存储一张他们的表格并检查他们是否可能被劫持或其他任何事情。

如果它不是唯一的,我应该如何执行唯一性?我宁愿使用 PHP 配置来实现它,也不愿在我制作的每个脚本中实现它。 PHP 会话的好处是不用担心幕后的技术细节。

【问题讨论】:

  • 如果您担心安全问题,那么您应该在提问之前进行一些研究,因为关于 PHP 会话安全和劫持的主题有很多。对于安全环境以及诸如 PCI 合规性之类的事情,处于原始状态的会话是不允许的。
  • 128 或 160 位的有效随机性足以确保您在一生中永远不会看到碰撞——而且会话不会永远持续下去,所以问题比这更有限.
  • 这并不是说您不应该忽视安全性,但是让会话 ID“真正独一无二”并不是您可以采取的提高安全性的最佳方法。
  • 关键不是唯一性作为安全约束。我只想存储会话 ID 的历史记录。在开始删除旧的之前,我还不知道我打算存储多少,但我不希望在我的数据库中发生冲突,该数据库在会话 ID 上有一个主索引。

标签: php session cookies uniqueidentifier


【解决方案1】:

下面是生成 id 的代码: Session.c

特别是php_session_create_id函数:

PHPAPI char *php_session_create_id(PS_CREATE_SID_ARGS) /* {{{ */
{
    PHP_MD5_CTX md5_context;
    PHP_SHA1_CTX sha1_context;
#if defined(HAVE_HASH_EXT) && !defined(COMPILE_DL_HASH)
    void *hash_context = NULL;
#endif
    unsigned char *digest;
    int digest_len;
    int j;
    char *buf, *outid;
    struct timeval tv;
    zval **array;
    zval **token;
    char *remote_addr = NULL;

    gettimeofday(&tv, NULL);

    if (zend_hash_find(&EG(symbol_table), "_SERVER", sizeof("_SERVER"), (void **) &array) == SUCCESS &&
        Z_TYPE_PP(array) == IS_ARRAY &&
        zend_hash_find(Z_ARRVAL_PP(array), "REMOTE_ADDR", sizeof("REMOTE_ADDR"), (void **) &token) == SUCCESS
    ) {
        remote_addr = Z_STRVAL_PP(token);
    }

    /* maximum 15+19+19+10 bytes */
    spprintf(&buf, 0, "%.15s%ld%ld%0.8F", remote_addr ? remote_addr : "", tv.tv_sec, (long int)tv.tv_usec, php_combined_lcg(TSRMLS_C) * 10);

    switch (PS(hash_func)) {
        case PS_HASH_FUNC_MD5:
            PHP_MD5Init(&md5_context);
            PHP_MD5Update(&md5_context, (unsigned char *) buf, strlen(buf));
            digest_len = 16;
            break;
        case PS_HASH_FUNC_SHA1:
            PHP_SHA1Init(&sha1_context);
            PHP_SHA1Update(&sha1_context, (unsigned char *) buf, strlen(buf));
            digest_len = 20;
            break;
#if defined(HAVE_HASH_EXT) && !defined(COMPILE_DL_HASH)
        case PS_HASH_FUNC_OTHER:
            if (!PS(hash_ops)) {
                php_error_docref(NULL TSRMLS_CC, E_ERROR, "Invalid session hash function");
                efree(buf);
                return NULL;
            }

            hash_context = emalloc(PS(hash_ops)->context_size);
            PS(hash_ops)->hash_init(hash_context);
            PS(hash_ops)->hash_update(hash_context, (unsigned char *) buf, strlen(buf));
            digest_len = PS(hash_ops)->digest_size;
            break;
#endif /* HAVE_HASH_EXT */
        default:
            php_error_docref(NULL TSRMLS_CC, E_ERROR, "Invalid session hash function");
            efree(buf);
            return NULL;
    }
    efree(buf);

    if (PS(entropy_length) > 0) {
#ifdef PHP_WIN32
        unsigned char rbuf[2048];
        size_t toread = PS(entropy_length);

        if (php_win32_get_random_bytes(rbuf, MIN(toread, sizeof(rbuf))) == SUCCESS){

            switch (PS(hash_func)) {
                case PS_HASH_FUNC_MD5:
                    PHP_MD5Update(&md5_context, rbuf, toread);
                    break;
                case PS_HASH_FUNC_SHA1:
                    PHP_SHA1Update(&sha1_context, rbuf, toread);
                    break;
# if defined(HAVE_HASH_EXT) && !defined(COMPILE_DL_HASH)
                case PS_HASH_FUNC_OTHER:
                    PS(hash_ops)->hash_update(hash_context, rbuf, toread);
                    break;
# endif /* HAVE_HASH_EXT */
            }
        }
#else
        int fd;

        fd = VCWD_OPEN(PS(entropy_file), O_RDONLY);
        if (fd >= 0) {
            unsigned char rbuf[2048];
            int n;
            int to_read = PS(entropy_length);

            while (to_read > 0) {
                n = read(fd, rbuf, MIN(to_read, sizeof(rbuf)));
                if (n <= 0) break;

                switch (PS(hash_func)) {
                    case PS_HASH_FUNC_MD5:
                        PHP_MD5Update(&md5_context, rbuf, n);
                        break;
                    case PS_HASH_FUNC_SHA1:
                        PHP_SHA1Update(&sha1_context, rbuf, n);
                        break;
#if defined(HAVE_HASH_EXT) && !defined(COMPILE_DL_HASH)
                    case PS_HASH_FUNC_OTHER:
                        PS(hash_ops)->hash_update(hash_context, rbuf, n);
                        break;
#endif /* HAVE_HASH_EXT */
                }
                to_read -= n;
            }
            close(fd);
        }
#endif
    }

    digest = emalloc(digest_len + 1);
    switch (PS(hash_func)) {
        case PS_HASH_FUNC_MD5:
            PHP_MD5Final(digest, &md5_context);
            break;
        case PS_HASH_FUNC_SHA1:
            PHP_SHA1Final(digest, &sha1_context);
            break;
#if defined(HAVE_HASH_EXT) && !defined(COMPILE_DL_HASH)
        case PS_HASH_FUNC_OTHER:
            PS(hash_ops)->hash_final(digest, hash_context);
            efree(hash_context);
            break;
#endif /* HAVE_HASH_EXT */
    }

    if (PS(hash_bits_per_character) < 4
            || PS(hash_bits_per_character) > 6) {
        PS(hash_bits_per_character) = 4;

        php_error_docref(NULL TSRMLS_CC, E_WARNING, "The ini setting hash_bits_per_character is out of range (should be 4, 5, or 6) - using 4 for now");
    }

    outid = emalloc((size_t)((digest_len + 2) * ((8.0f / PS(hash_bits_per_character)) + 0.5)));
    j = (int) (bin_to_readable((char *)digest, digest_len, outid, (char)PS(hash_bits_per_character)) - outid);
    efree(digest);

    if (newlen) {
        *newlen = j;
    }

    return outid;
}

如您所见,实际 id 是各种事物的哈希值,例如一天中的时间。所以有可能会发生冲突,但是可能性很小。这么多,除非你有很多并发用户,否则不值得担心。

但是,如果你真的很担心,你可以通过设置不同的哈希算法来增加熵session.hash_function

就监控活动会话而言,这个问题很好地涵盖了它Is it possible to see active sessions using php?

如果你在一台机器上使用单个 php 实例,那么它实际上有一个内置的会话管理器,在分配它之前检查一个 id 是否已经存在。但是,如果您正在运行多个实例或多台机器,则无法知道其他机器分配了哪些 id。

【讨论】:

    【解决方案2】:

    如果您想了解 PHP 默认如何生成会话 ID,请查看 Github 上的源代码。 肯定不是随机的,它基于这些成分的哈希值(默认值:md5)(参见代码 sn-p 的第 310 行):

    1. 客户端的IP地址
    2. 当前时间
    3. PHP 线性同余生成器 - 伪随机数生成器 (PRNG)
    4. 操作系统特定的随机源 - 如果操作系统有可用的随机源(例如 /dev/urandom)

    如果操作系统有可用的随机源,则生成的 ID 作为会话 ID 的强度很高(/dev/urandom 和其他操作系统随机源(通常)是加密安全的 PRNG)。但是,如果它没有,那么它是令人满意的。

    生成会话标识的目标是:

    1. 尽量减少生成两个具有相同值的会话 ID 的概率
    2. 在计算上生成随机密钥并点击正在使用的密钥变得非常具有挑战性

    这是通过 PHP 的会话生成方法实现的。

    你不能绝对保证唯一性,但是两次命中同一个哈希的概率是如此之低,一般来说,不值得担心。

    【讨论】:

    • 感谢您的完整总结。我已经决定,鉴于会话的生命周期相对较短,这真的没什么好担心的。由于我还没有确定何时/多久截断我的数据库表,我将只在主索引中添加一个时间戳,以使查询错误不可能发生。另外,我将增加熵并将散列函数设置为我最喜欢的 Whirlpool。再次感谢!
    • 不用担心。是的,您可以将散列函数更改为 sha1 或此列表中的任何其他函数 (php.net/manual/en/function.hash-algos.php) - 包括漩涡!
    • 实际上,我们刚刚发现,当两个不同的服务器在反向代理后面运行并且它们的时钟同步时,它们在两个不同的服务器上生成相同会话 id 的概率非常高。在这种情况下,两个服务器的 REMOTE_ADDR 相同。而且,由于 PRNG 种子是基于当前秒的,我们在每台服务器上平均获得 2 个相同的会话 ID,总共 150 个!我强烈建议始终确保会话熵设置为足够大(以防万一)并使用共享会话处理程序,如 memcached 或 redis。
    • @BenoîtVidis 在唯一的服务器上,我希望 PHP 无论如何都要检查唯一性;至于 2 个以上的服务器,最好生成自己的算法(在要散列的字符串中包含服务器编号)并强制 PHP 使用该散列作为会话 ID,以大大降低具有 déjà 的风险-vus :)
    猜你喜欢
    • 1970-01-01
    • 2012-06-24
    • 1970-01-01
    • 2012-08-12
    • 2012-06-21
    • 2010-12-04
    • 2015-04-03
    • 1970-01-01
    • 2012-07-21
    相关资源
    最近更新 更多