diff --git a/src/Symfony/Component/Uid/CHANGELOG.md b/src/Symfony/Component/Uid/CHANGELOG.md index 70a35a9291..b93119ec20 100644 --- a/src/Symfony/Component/Uid/CHANGELOG.md +++ b/src/Symfony/Component/Uid/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.2.0 +----- + + * made UUIDv6 always return truly random node fields to prevent leaking the MAC of the host + 5.1.0 ----- diff --git a/src/Symfony/Component/Uid/Tests/UuidTest.php b/src/Symfony/Component/Uid/Tests/UuidTest.php index 875f99e582..43beb5b07d 100644 --- a/src/Symfony/Component/Uid/Tests/UuidTest.php +++ b/src/Symfony/Component/Uid/Tests/UuidTest.php @@ -89,6 +89,14 @@ class UuidTest extends TestCase $this->assertSame('3499710062d0', $uuid->getNode()); } + public function testV6IsSeeded() + { + $uuidV1 = Uuid::v1(); + $uuidV6 = Uuid::v6(); + + $this->assertNotSame(substr($uuidV1, 24), substr($uuidV6, 24)); + } + public function testBinary() { $uuid = new UuidV4(self::A_UUID_V4); diff --git a/src/Symfony/Component/Uid/UuidV6.php b/src/Symfony/Component/Uid/UuidV6.php index 9d0ca9fdc0..c5739529e6 100644 --- a/src/Symfony/Component/Uid/UuidV6.php +++ b/src/Symfony/Component/Uid/UuidV6.php @@ -14,6 +14,8 @@ namespace Symfony\Component\Uid; /** * A v6 UUID is lexicographically sortable and contains a 60-bit timestamp and 62 extra unique bits. * + * Unlike UUIDv1, this implementation of UUIDv6 doesn't leak the MAC address of the host. + * * @experimental in 5.1 * * @author Nicolas Grekas @@ -22,11 +24,27 @@ class UuidV6 extends Uuid { protected const TYPE = 6; + private static $seed; + public function __construct(string $uuid = null) { if (null === $uuid) { $uuid = uuid_create(\UUID_TYPE_TIME); - $this->uid = substr($uuid, 15, 3).substr($uuid, 9, 4).$uuid[0].'-'.substr($uuid, 1, 4).'-6'.substr($uuid, 5, 3).substr($uuid, 18); + $this->uid = substr($uuid, 15, 3).substr($uuid, 9, 4).$uuid[0].'-'.substr($uuid, 1, 4).'-6'.substr($uuid, 5, 3).substr($uuid, 18, 6); + + // uuid_create() returns a stable "node" that can leak the MAC of the host, but + // UUIDv6 prefers a truly random number here, let's XOR both to preserve the entropy + + if (null === self::$seed) { + self::$seed = [random_int(0, 0xffffff), random_int(0, 0xffffff)]; + } + + $node = unpack('N2', hex2bin('00'.substr($uuid, 24, 6)).hex2bin('00'.substr($uuid, 30))); + + $this->uid .= sprintf('%06x%06x', + (self::$seed[0] ^ $node[1]) | 0x010000, + self::$seed[1] ^ $node[2] + ); } else { parent::__construct($uuid); }