.Net 中的Guid

1. Guid 是什么?

  • GUID 在.Net开发中很常见,它表示全局唯一标识符(Globally Unique Identifier),是一个16个字节长度128位的数字,用16进制表示的话是32个字符的字符串,加上4个分隔符共36个字符;
  • GUID 被设计为全球唯一标识符。

2. Guid 规范与实现

从.NetCore 源码中可以查到:Guid.NewGuid()实际上遵循了UUID规范,源码地址:Guid.cs
UUID 规范 ietf.org/rfc/rfc4122.txt 主要规定了版本和实现方式:

  • 128 位(16 字节)的十六进制字符串,标准表示为:xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx M 表示版本号(1-5),N 表示变体(标识 UUID 布局)。
  • 版本与生成逻辑
版本 生成方式 典型用途
1 时间戳 + MAC 地址 历史系统
3/5 命名空间 + 名称哈希 确定性生成(MD5/SHA1)
4 随机或伪随机数 现代应用(主流推荐)
  • 互操作性保证

标准仅要求符合格式和算法规范的 UUID 实现互操作,非标准扩展或变种(如自定义位分配)可能存在解析冲突。
实践中,主流库(如 Python uuid、Java UUID 类)严格遵循 RFC 4122,跨系统兼容性通常可靠。

  • 安全与隐私

版本 1 和 2 可能泄露 MAC 地址或时间信息,版本 4 和 5 更适合敏感场景。

  • 用户建议
    优先使用版本4:随机生成更简单安全,且无隐私风险。
    谨慎解析 UUID:非标准变种(如缺少分隔符或大小写混合)需预处理。

3. Guid 在.NetCore 中的实现

现在.NetCore 生成NewGuid 时都采用版本4方式(随机字节),对于不同平台有不同的实现方式,服务端平台(非WebAssembly环境)实现主要使用了 random.h 库的随机函数。

源码地址:https://github.com/dotnet/runtime/blob/main/src/native/minipal/random.c

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#include "minipalconfig.h"  // 包含配置文件,定义了平台相关的宏

#include <stdlib.h>         // 标准库头文件
#include <stdint.h>         // 定义了标准整数类型
#include <stdbool.h>        // 定义了布尔类型
#include <assert.h>         // 断言宏
#include <time.h>           // 时间相关函数
#include <errno.h>          // 错误码定义

// 根据平台条件编译,包含不同的头文件
#if HAVE_BCRYPT_H
#include <windows.h>        // Windows 平台
#include <bcrypt.h>         // Windows 平台的加密库
#else
#include <sys/stat.h>       // POSIX 系统状态相关函数
#include <fcntl.h>          // 文件控制选项
#include <unistd.h>         // POSIX 标准符号常量和类型
#endif

#if defined(__APPLE__) && __APPLE__
#include <CommonCrypto/CommonRandom.h>  // macOS 平台的加密库
#endif

#include "random.h"  // 包含随机数生成相关的函数声明

/*

Generate random bytes. The generated bytes are not cryptographically strong.

*/
void minipal_get_non_cryptographically_secure_random_bytes(uint8_t* buffer, int32_t bufferLength)
{
    assert(buffer != NULL);  // 确保传入的缓冲区指针不为空

#if HAVE_ARC4RANDOM_BUF
    // 如果平台支持 arc4random_buf,直接使用它生成随机字节
    arc4random_buf(buffer, (size_t)bufferLength);
#elif HAVE_BCRYPT_H
    // 如果平台支持 BCrypt(Windows),调用安全版本的随机数生成函数
    minipal_get_cryptographically_secure_random_bytes(buffer, bufferLength);
#else
    // 其他平台的回退实现
    long num = 0;
    static bool sInitializedMRand = false;  // 静态变量,用于记录是否已初始化 srand48

    // 调用安全版本的随机数生成函数作为回退
    minipal_get_cryptographically_secure_random_bytes(buffer, bufferLength);

    if (!sInitializedMRand)
    {
        srand48((long int)time(NULL));  // 使用当前时间初始化随机数生成器
        sInitializedMRand = true;
    }

    // 遍历缓冲区,使用 srand48 生成的随机数对缓冲区内容进行异或操作
    for (int i = 0; i < bufferLength; i++)
    {
        if (i % 4 == 0)
        {
            num = lrand48();  // 每 4 个字节重新生成一个随机数
        }

        *(buffer + i) ^= num;  // 对缓冲区中的字节进行异或操作
        num >>= 8;             // 将随机数右移 8 位
    }
#endif // HAVE_ARC4RANDOM_BUF
}

/*

Generate cryptographically strong random bytes.

Return 0 on success, -1 on failure.
*/
int32_t minipal_get_cryptographically_secure_random_bytes(uint8_t* buffer, int32_t bufferLength)
{
    assert(buffer != NULL);  // 确保传入的缓冲区指针不为空

#ifdef __EMSCRIPTEN__
    // Emscripten 平台的特殊处理
    extern int32_t mono_wasm_browser_entropy(uint8_t* buffer, int32_t bufferLength);
    static bool sMissingBrowserCrypto = false;  // 标记浏览器加密功能是否缺失

    if (!sMissingBrowserCrypto)
    {
        int32_t bff = mono_wasm_browser_entropy(buffer, bufferLength);
        if (bff == -1)
            sMissingBrowserCrypto = true;
        else
            return 0;  // 如果成功生成随机字节,返回 0
    }
#elif defined(__APPLE__) && __APPLE__
    // macOS 平台使用 CommonCrypto 库生成随机字节
    CCRNGStatus status = CCRandomGenerateBytes(buffer, (size_t)bufferLength);

    if (status == kCCSuccess)
    {
        return 0;  // 如果成功生成随机字节,返回 0
    }
#elif HAVE_BCRYPT_H
    // Windows 平台使用 BCrypt 库生成随机字节
    NTSTATUS status = BCryptGenRandom(NULL, buffer, (ULONG)bufferLength, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
    return BCRYPT_SUCCESS(status) ? 0 : -1;  // 根据返回状态判断是否成功
#else
    // 其他平台(如 Linux)使用 /dev/urandom 生成随机字节

    static volatile int rand_des = -1;  // 静态变量,用于存储 /dev/urandom 的文件描述符
    static bool sMissingDevURandom = false;  // 标记 /dev/urandom 是否缺失

    if (!sMissingDevURandom)
    {
        if (rand_des == -1)
        {
            int fd;

            do
            {
#if HAVE_O_CLOEXEC    // 
                fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC);  // 打开 /dev/urandom,设置 O_CLOEXEC 标志
#else
                fd = open("/dev/urandom", O_RDONLY);  // 打开 /dev/urandom
                fcntl(fd, F_SETFD, FD_CLOEXEC);  // 设置文件描述符的 FD_CLOEXEC 标志
#endif
            }
            while ((fd == -1) && (errno == EINTR));  // 如果被中断,继续尝试打开

            if (fd != -1)
            {
                int expected = -1;
                if (!__atomic_compare_exchange_n(&rand_des, &expected, fd, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST))
                {
                    // 如果另一个线程已经设置了 rand_des,关闭当前文件描述符
                    close(fd);
                }
            }
            else if (errno == ENOENT)
            {
                sMissingDevURandom = true;  // 如果 /dev/urandom 不存在,标记为缺失
            }
        }

        if (rand_des != -1)
        {
            int32_t offset = 0;
            do
            {
                ssize_t n = read(rand_des, buffer + offset, (size_t)(bufferLength - offset));  // 从 /dev/urandom 读取随机字节
                if (n == -1)
                {
                    if (errno == EINTR)
                    {
                        continue;  // 如果被中断,继续尝试读取
                    }
                    return -1;  // 如果读取失败,返回 -1
                }

                offset += n;  // 更新已读取的字节数
            }
            while (offset != bufferLength);  // 继续读取,直到读取到足够的字节
            return 0;  // 如果成功读取,返回 0
        }
    }
#endif
    return -1;  // 如果所有方法都失败,返回 -1
}

posted @ 2025-06-12 11:38  aimigi  阅读(135)  评论(0)    收藏  举报