2

PostgreSQL:

 create extension if not exists "uuid-ossp";
 select uuid_generate_v3(uuid_nil(), 'this is a test');
           uuid_generate_v3           
--------------------------------------
 e1e27115-9f5b-366d-90e8-e07b1b36b99c
(1 row)

Java:

java> java.util.UUID.nameUUIDFromBytes("this is a test".getBytes());
java.util.UUID res9 = 54b0c58c-7ce9-32a8-b551-351102ee0938

How do I generate the same UUID's in Java as PostgreSQL does?

neu242
  • 15,796
  • 20
  • 79
  • 114

4 Answers4

3

Though this is not an answer for the original question however I had to do this conversion in the other way around, that is, mimic UUID.nameUUIDFromBytes behaviour in PostgreSQL during a migration:

CREATE EXTENSION IF NOT EXISTS "pgcrypto";
CREATE EXTENSION IF NOT EXISTS "plpgsql";

CREATE OR REPLACE FUNCTION uuidv3(bytes bytea)
  RETURNS uuid
  RETURNS null on null input
  STABLE
  PARALLEL SAFE
AS $$
DECLARE
  md5bytes  bytea;
BEGIN
  md5bytes := digest(bytes, 'md5');
  --       md5Bytes[6]  &= 0x0f;  /* clear version        */
  --       md5Bytes[6]  |= 0x30;  /* set to version 3     */
  md5bytes := set_byte(md5bytes, 6, (get_byte(md5bytes, 6) & x'0F'::int) | x'30'::int);
  --       md5Bytes[8]  &= 0x3f;  /* clear variant        */
  --       md5Bytes[8]  |= 0x80;  /* set to IETF variant  */
  md5bytes := set_byte(md5bytes, 8, (get_byte(md5bytes, 8) & x'3F'::int)|  x'80'::int);
  RETURN (encode(md5bytes, 'hex'))::uuid;
END
$$
  LANGUAGE plpgsql;

and you should use internal textsend to convert text columns to bytea:

SELECT uuidv3(textsend('this is a test'));

Though I must admit I have never tried the other way.

Győző Papp
  • 148
  • 1
  • 5
3

The algorithm for generating a version 3 UUID is described here https://www.rfc-editor.org/rfc/rfc4122#section-4.3

But the key steps are:

  • Allocate a UUID to use as a "name space ID" for all UUIDs generated from names in that name space.
  • Choose either MD5 or SHA-1 as the hash algorithm
  • Convert the name to a canonical sequence of octets
  • Compute the hash of the name space ID concatenated with the name.
  • Change certain bytes of the to predefined values (see link above)
  • Convert the resulting UUID to local byte order.

The postgres function signature is uuid_generate_v3(namespace uuid, name text) so it takes the namespace UUID and name as arguments.
The Java method nameUUIDFromBytes(byte[] name) takes only the name and hashes it with MD5 to create the UUID. To get the same output as with PostgreSQL you have to concatenate the the namespace bytes and the name bytes together yourself.

For namespace you have used uuid_nil() (all zeroes) which is new UUID(0L, 0L) in Java.

Putting it all together this would look something like this:

byte[] bytes = Arrays.concatenate(toByteArray(new UUID(0L, 0L)), "this is a test".getBytes(StandardCharsets.UTF_8));
System.out.println(UUID.nameUUIDFromBytes(bytes)); // prints out e1e27115-9f5b-366d-90e8-e07b1b36b99c

And you can convert the namespace UUID to byte array like so:

private static byte[] toByteArray(UUID uuid) {
    ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
    bb.putLong(uuid.getMostSignificantBits());
    bb.putLong(uuid.getLeastSignificantBits());
    return bb.array();
}
Community
  • 1
  • 1
binoternary
  • 1,865
  • 1
  • 13
  • 25
1

As far as I know there isn't a straight-forward way and you'll have to do dig into the source code of PostgreSQL (and its libraries) and replicate the algorithm in Java yourself, or call the same library function natively. An easier option would be to ask a PostgreSQL database to generate them for you, but I'm assuming that's not possible for some reason.

As you can see from the documentation, PostgreSQL takes an MD5 hash first to protect against reverse-engineering. They then use a UUID generator from the OSSP UUID library.

I tried:

java.util.UUID.nameUUIDFromBytes(Md5Utils.getMd5Digest("this is a test"));

But that produced:

d69495fb-d538-3991-b96b-aa262ab6dce5
ᴇʟᴇvᴀтᴇ
  • 12,285
  • 4
  • 43
  • 66
0

This is an exemple using System.arraycopy() to concatenate namespace and name.

package com.example;

import java.nio.charset.StandardCharsets;
import java.util.UUID;

public class Example {
    
    /**
     * Returns a name-based UUID using NIL UUID as namespace.
     * 
     * @param name a string
     * @return a UUID
     */
    public static UUID getNameBasedUsingNil(String name) {
        
        // 1. Get NIL and NAME bytes
        final byte[] nil = new byte[16]; // NIL namespace
        final byte[] nam = name.getBytes(StandardCharsets.UTF_8);
        
        // 2. Concatenate NIL and NAME bytes
        final byte[] bytes = new byte[nil.length + nam.length];
        System.arraycopy(nil, 0, bytes, 0, 16);
        System.arraycopy(nam, 0, bytes, 16, nam.length);
        
        // 3. Generate a name-based UUID
        return UUID.nameUUIDFromBytes(bytes);
    }
    
    public static void main(String[] args) {       
        String name = "this is a test";
        UUID uuid = getNameBasedUsingNil(name);
        System.out.println(uuid);
    }    
}

OUTPUT:

e1e27115-9f5b-366d-90e8-e07b1b36b99c
fabiolimace
  • 972
  • 11
  • 13