Skip to content

Commit 31cb314

Browse files
added UID v4 generation functions
1 parent 66d0e36 commit 31cb314

File tree

4 files changed

+118
-5
lines changed

4 files changed

+118
-5
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.uid2.operator.model;
2+
3+
import com.uid2.operator.vertx.ClientInputValidationException;
4+
5+
public enum IdentityEnvironment {
6+
Test(0), Integ(1), Prod(2);
7+
8+
public final int value;
9+
10+
IdentityEnvironment(int value) { this.value = value; }
11+
12+
public static IdentityEnvironment fromValue(int value) {
13+
return switch (value) {
14+
case 0 -> Test;
15+
case 1 -> Integ;
16+
case 2 -> Prod;
17+
default -> throw new ClientInputValidationException("Invalid valid for IdentityEnvironment: " + value);
18+
};
19+
}
20+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.uid2.operator.model;
2+
3+
import com.uid2.operator.vertx.ClientInputValidationException;
4+
5+
public enum IdentityVersion {
6+
V3(0), V4(1);
7+
8+
public final int value;
9+
10+
IdentityVersion(int value) { this.value = value; }
11+
12+
public static IdentityVersion fromValue(int value) {
13+
return switch (value) {
14+
case 0 -> V3;
15+
case 1 -> V4;
16+
default -> throw new ClientInputValidationException("Invalid valid for IdentityVersion: " + value);
17+
};
18+
}
19+
}

src/main/java/com/uid2/operator/service/TokenUtils.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package com.uid2.operator.service;
22

3+
import com.uid2.operator.model.IdentityEnvironment;
34
import com.uid2.operator.model.IdentityScope;
45
import com.uid2.operator.model.IdentityType;
5-
6-
import java.util.HashSet;
7-
import java.util.Set;
6+
import com.uid2.operator.model.IdentityVersion;
7+
import com.uid2.shared.model.SaltEntry;
88

99
public class TokenUtils {
1010
public static byte[] getIdentityHash(String identityString) {
@@ -55,11 +55,18 @@ public static byte[] getAdvertisingIdV3FromIdentityHash(IdentityScope scope, Ide
5555
return getAdvertisingIdV3(scope, type, getFirstLevelHashFromIdentityHash(identityString, firstLevelSalt), rotatingSalt);
5656
}
5757

58-
public static byte encodeIdentityScope(IdentityScope identityScope) {
59-
return (byte) (identityScope.value << 4);
58+
public static byte[] getAdvertisingIdV4(IdentityScope scope, IdentityType type, IdentityEnvironment environment, byte[] firstLevelHash, SaltEntry.KeyMaterial encryptingKey, String rotatingSalt) throws Exception {
59+
byte metadata = (byte) (encodeIdentityVersion(IdentityVersion.V4) | encodeIdentityScope(scope) | encodeIdentityType(type) | encodeIdentityEnvironment(environment));
60+
return V4TokenUtils.buildAdvertisingIdV4(metadata, firstLevelHash, encryptingKey.id(), encryptingKey.key(), rotatingSalt);
6061
}
6162

63+
public static byte encodeIdentityScope(IdentityScope identityScope) { return (byte) (identityScope.value << 4); }
64+
6265
public static byte encodeIdentityType(IdentityType identityType) {
6366
return (byte) (identityType.value << 2);
6467
}
68+
69+
public static byte encodeIdentityVersion(IdentityVersion identityVersion) { return (byte) (identityVersion.value << 6); }
70+
71+
public static byte encodeIdentityEnvironment(IdentityEnvironment identityEnvironment) { return (byte) (identityEnvironment.value); }
6572
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.uid2.operator.service;
2+
3+
import javax.crypto.Cipher;
4+
import javax.crypto.spec.IvParameterSpec;
5+
import javax.crypto.spec.SecretKeySpec;
6+
import io.vertx.core.buffer.Buffer;
7+
import java.util.Arrays;
8+
9+
public class V4TokenUtils {
10+
public static byte[] generateIV(String salt, byte[] firstLevelHashLast16Bytes, byte metadata, int keyId) {
11+
int iv_length = 12;
12+
String iv_base = salt
13+
.concat(Arrays.toString(firstLevelHashLast16Bytes))
14+
.concat(Byte.toString(metadata))
15+
.concat(String.valueOf(keyId));
16+
return Arrays.copyOfRange(EncodingUtils.getSha256Bytes(iv_base), 0, iv_length);
17+
}
18+
19+
private static byte[] padIV16Bytes(byte[] iv) {
20+
// Pad the 12-byte IV to 16 bytes for AES-CTR (standard block size)
21+
byte[] paddedIV = new byte[16];
22+
System.arraycopy(iv, 0, paddedIV, 0, 12);
23+
// Remaining 4 bytes are already zero-initialized (counter starts at 0)
24+
return paddedIV;
25+
}
26+
27+
private static byte[] encryptHash(String encryptionKey, byte[] hash, byte[] iv) throws Exception {
28+
// Set up AES256-CTR cipher
29+
Cipher aesCtr = Cipher.getInstance("AES/CTR/NoPadding");
30+
SecretKeySpec secretKey = new SecretKeySpec(encryptionKey.getBytes(), "AES");
31+
IvParameterSpec ivSpec = new IvParameterSpec(padIV16Bytes(iv));
32+
33+
aesCtr.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
34+
return aesCtr.doFinal(hash);
35+
}
36+
37+
public static byte generateChecksum(byte[] data) {
38+
// Simple XOR checksum of all bytes
39+
byte checksum = 0;
40+
for (byte b : data) {
41+
checksum ^= b;
42+
}
43+
System.out.println("Checksum: 0x" + String.format("%02X", checksum));
44+
return checksum;
45+
}
46+
47+
public static byte[] buildAdvertisingIdV4(byte metadata, byte[] firstLevelHash, int keyId, String key, String salt) throws Exception {
48+
byte[] hash16Bytes = Arrays.copyOfRange(firstLevelHash, 0, 16);
49+
byte[] iv = V4TokenUtils.generateIV(salt, hash16Bytes, metadata, keyId);
50+
byte[] encryptedFirstLevelHash = V4TokenUtils.encryptHash(key, hash16Bytes, iv);
51+
52+
Buffer buffer = Buffer.buffer();
53+
buffer.appendByte(metadata);
54+
buffer.appendBytes(new byte[] {
55+
(byte) (keyId & 0xFF), // LSB
56+
(byte) ((keyId >> 8) & 0xFF), // Middle
57+
(byte) ((keyId >> 16) & 0xFF) // MSB
58+
});
59+
buffer.appendBytes(iv);
60+
buffer.appendBytes(encryptedFirstLevelHash);
61+
62+
byte checksum = generateChecksum(buffer.getBytes());
63+
buffer.appendByte(checksum);
64+
65+
return buffer.getBytes();
66+
}
67+
}

0 commit comments

Comments
 (0)