Skip to content

Instantly share code, notes, and snippets.

@AntonnMal
Last active July 7, 2024 22:11
Show Gist options
  • Save AntonnMal/268ba76b260539e83644f3a58abd2935 to your computer and use it in GitHub Desktop.
Save AntonnMal/268ba76b260539e83644f3a58abd2935 to your computer and use it in GitHub Desktop.

About PSO2 NGS encryption (global)

Legend

  • u32 - 32 bit unsigned int
  • u16 - 16 bit unsigned int
  • u8 - 8 bit unsigned int

Notes

  1. This gist includes inline hex, and it might not render correctly on mobile phones.
  2. All offsets mentioned here are inclusive at the left bound and exclusive at the right bound.
  3. All data blobs are examples of real data.

Packet header

NGS's packet header differs slightly from pre-NGS:

Pre-NGS NGS
u32: length u32: length
u8: packet_id u8: flags1
u8: packet_subid u8: packet_id
u8: flags1 u16: packet_subid
u8: flags2?

Update (11.07.2023): It seems that NGS's packet_subid is actually u16.

Encryption format (as of 11.03.2023)

All packets are unencrypted until the client sends a "key exchange" packet (id: 0x11, subid: 0x0b).

     00 01 02 03 04 05 06 07  08 09 0A 0B 0C 0D 0E 0F

00:  0C 01 00 00 00 11 0B 00  A2 58 C3 12 7A 27 C0 10
10:  42 2F 7A 12 7E 06 AB 0A  EA F3 4B B6 6D EE 39 3B
20:  3C 2B 76 AD B5 B9 72 5E  04 29 FB F2 0F 78 A8 12
30:  FC 66 18 0F BB D5 6F 26  70 AE B1 F4 AF 9F 93 3F
40:  FB 36 A9 CA 7C 70 4A D4  5E 95 FF 4E 4E D9 B5 99
50:  DF E1 2D 9F DD FB EC E1  1B EC 1D C2 A1 0E 5E BC
60:  92 F9 84 2B 84 5F D6 59  A1 3E C2 73 02 C7 3F 7A
70:  ED 6F AA 39 90 E9 6D 85  C2 41 25 EE 2E 3D F5 15
80:  B4 55 E4 24 56 7F 7D 83  00 00 00 00 00 00 00 00
90:  ...

Listing 1: Key exchange packet (before decryption)

Data from 0x08 to 0x88 is an RSA-encrypted blob. After decrypting it, we get this block of data:

     00 01 02 03 04 05 06 07  08 09 0A 0B 0C 0D 0E 0F
 
00:  DA B3 48 79 DC BA 24 9A  8D 52 AE 41 EA B1 C1 BC
10:  99 BD C5 81 CE 5E D5 21  24 C1 45 9D 20 26 EE B5
20:  42 FD 29 0D A3 17 4A 5E  C9 EF 4B 91 03 05 15 8A

30:  22 69 4C 98 7A EB 6B 8C  67 A2 40 2D C2 3F 80 4C
40:  A9 61 3D 56 8B CD 82 7B  D0 6B D0 2C CD DC BD 8E

Listing 2: Key exchange packet (after RSA decryption)

This block is split into two parts:

  • [0x00 - 0x30] AES256-CBC-encrypted data
  • [0x30 - 0x50] AES key for the above part

To decrypt the data part, we take the key and IV, which is [0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF].

     00 01 02 03 04 05 06 07  08 09 0A 0B 0C 0D 0E 0F

00:  00 01 02 03 04 05 06 07  08 09 0A 0B 0C 0D 0E 0F
10:  10 11 12 13 14 15 16 17  18 19 1A 1B 1C 1D 1E 1F
20:  10 10 10 10 10 10 10 10  10 10 10 10 10 10 10 10

Listing 3: Key exchange packet (after RSA and AES decryption)

This block consists of:

  • [0x00 - 0x20] AES key for future packets
  • [0x20 - 0x30] Pkcs7 padding (might not show up, depending on decryption implemenation)

In response to this packet, the server sends an encrypted "key response" packet:

     00 01 02 03 04 05 06 07  08 09 0A 0B 0C 0D 0E 0F
 
00:  D2 FF 93 DC 9D 55 64 C8  FD 7E 37 51 2E 0A 24 CF
10:  36 70 9D 74 3C D4 4D 29  61 91 69 8F D8 A1 E5 2E
20:  B0 6F 29 A5 1B 6E 92 DA  0E F1 D8 50 06 80 4E FE
30:  59 07 5F 17 DD 88 92 00  FB FC 79 BF 40 38 7B 6D

40:  01 00 FF FF 88 00 00 00  DE 9E A7 FB 46 D9 A3 8F
50:  55 A5 EC 42 CC 04 F0 E8  7C 5F B6 9E 65 AE 04 38
60:  4D 91 8C 96 44 77 91 61  2F E4 36 94 48 45 4F 5D
70:  ED E3 34 57 BA 3E A6 DE  DF 83 82 F9 92 B3 55 C3
80:  35 9E 48 29 F6 65 92 E7

Listing 4: Server response (before decryption)

The packet format is common for all future packets:

Offset Description
0x00 - 0x20 SHA256 of the header
0x20 - 0x40 SHA256 of the data
0x40 - 0x44 Magic number(?)
0x44 - 0x48 Packet length
0x48 - ... AES-encrypted data

The initial IV for the client's and server's data is the same as above, but for the next packet, it is set to the previous packet's last 16 bytes (before decryption / after encryption). So in this example, the server's next packet IV is set to [0xDF, 0x83, 0x82, 0xF9, 0x92, 0xB3, 0x55, 0xC3, 0x35, 0x9E, 0x48, 0x29, 0xF6, 0x65, 0x92, 0xE7].

     00 01 02 03 04 05 06 07  08 09 0A 0B 0C 0D 0E 0F
 
00:  38 00 00 00 00 11 0C 00  00 01 02 03 04 05 06 07
10:  08 09 0A 0B 0C 0D 0E 0F  10 11 12 13 14 15 16 17
20:  18 19 1A 1B 1C 1D 1E 1F  10 10 10 10 10 10 10 10
30:  10 10 10 10 10 10 10 10

Listing 5: Server response (after decryption)

Note: Listings 2-5 can be used to check implementations of packet decryption.

Some packets may also look like this (after decryption):

     00 01 02 03 04 05 06 07  08 09 0A 0B 0C 0D 0E 0F
 
00:  28 B5 2F FD 20 38 85 01  00 94 02 38 00 00 00 00
10:  11 0C 00 00 01 02 03 04  05 06 07 08 09 0A 0B 0C
20:  0D 0E 0F 10 11 12 13 14  15 16 17 18 19 1A 1B 1C
30:  1D 1E 1F 10 01 00 E9 40  19                     

Listing 6: Example of packed data

If any packet starts with [0x28, 0xB5, 0x2F, 0xFD], then this packet is packed using zstd compression. When recompressing, be sure to pass the "pledged source size" to the compressor.

Calculating SHA256

  1. Create a zero-filled array of length 0x40.
  2. Append [0x01, 0x00, 0xFF, 0xFF].
  3. Append encrypted data size + 0x48 as u32 little-endian.
  4. Append encrypted data.
     00 01 02 03 04 05 06 07  08 09 0A 0B 0C 0D 0E 0F
 
00:  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
10:  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
20:  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
30:  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00

40:  01 00 FF FF 88 00 00 00  DE 9E A7 FB 46 D9 A3 8F
50:  55 A5 EC 42 CC 04 F0 E8  7C 5F B6 9E 65 AE 04 38
60:  4D 91 8C 96 44 77 91 61  2F E4 36 94 48 45 4F 5D
70:  ED E3 34 57 BA 3E A6 DE  DF 83 82 F9 92 B3 55 C3
80:  35 9E 48 29 F6 65 92 E7                         

Listing 7: SHA calculation (stage 0)

  1. Calculate the SHA256 of the array from 0x44 to the end.
  2. Place the resulting hash at 0x20.
     00 01 02 03 04 05 06 07  08 09 0A 0B 0C 0D 0E 0F
 
00:  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
10:  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
20:  B0 6F 29 A5 1B 6E 92 DA  0E F1 D8 50 06 80 4E FE
30:  59 07 5F 17 DD 88 92 00  FB FC 79 BF 40 38 7B 6D

40:  01 00 FF FF 88 00 00 00  DE 9E A7 FB 46 D9 A3 8F
50:  55 A5 EC 42 CC 04 F0 E8  7C 5F B6 9E 65 AE 04 38
60:  4D 91 8C 96 44 77 91 61  2F E4 36 94 48 45 4F 5D
70:  ED E3 34 57 BA 3E A6 DE  DF 83 82 F9 92 B3 55 C3
80:  35 9E 48 29 F6 65 92 E7                         

Listing 8: SHA calculation (stage 1)

  1. Calculate the SHA256 of the array from the beginning to 0x48.
  2. Place the resulting hash at the beginning.
     00 01 02 03 04 05 06 07  08 09 0A 0B 0C 0D 0E 0F
 
00:  D2 FF 93 DC 9D 55 64 C8  FD 7E 37 51 2E 0A 24 CF
10:  36 70 9D 74 3C D4 4D 29  61 91 69 8F D8 A1 E5 2E
20:  B0 6F 29 A5 1B 6E 92 DA  0E F1 D8 50 06 80 4E FE
30:  59 07 5F 17 DD 88 92 00  FB FC 79 BF 40 38 7B 6D

40:  01 00 FF FF 88 00 00 00  DE 9E A7 FB 46 D9 A3 8F
50:  55 A5 EC 42 CC 04 F0 E8  7C 5F B6 9E 65 AE 04 38
60:  4D 91 8C 96 44 77 91 61  2F E4 36 94 48 45 4F 5D
70:  ED E3 34 57 BA 3E A6 DE  DF 83 82 F9 92 B3 55 C3
80:  35 9E 48 29 F6 65 92 E7

Listing 9: SHA calculation (result)

  1. Data is ready

Base64 blobs of listings

Listing 2:

2rNIedy6JJqNUq5B6rHBvJm9xYHOXtUhJMFFnSAm7rVC/SkNoxdKXsnvS5EDBRWKImlMmHrra4xnokAtwj+ATKlhPVaLzYJ70GvQLM3cvY4=

Listing 3:

AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8QEBAQEBAQEBAQEBAQEBAQ

Listing 4, 9:

0v+T3J1VZMj9fjdRLgokzzZwnXQ81E0pYZFpj9ih5S6wbymlG26S2g7x2FAGgE7+WQdfF92IkgD7/Hm/QDh7bQEA//+IAAAA3p6n+0bZo49VpexCzATw6Hxftp5lrgQ4TZGMlkR3kWEv5DaUSEVPXe3jNFe6Pqbe34OC+ZKzVcM1nkgp9mWS5w==

Listing 5:

OAAAAAARDAAAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHxAQEBAQEBAQEBAQEBAQEBA=

Listing 6:

KLUv/SA4hQEAlAI4AAAAABEMAAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fEAEA6UAZ

Listing 7:

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEA//+IAAAA3p6n+0bZo49VpexCzATw6Hxftp5lrgQ4TZGMlkR3kWEv5DaUSEVPXe3jNFe6Pqbe34OC+ZKzVcM1nkgp9mWS5w==

Listing 8:

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwbymlG26S2g7x2FAGgE7+WQdfF92IkgD7/Hm/QDh7bQEA//+IAAAA3p6n+0bZo49VpexCzATw6Hxftp5lrgQ4TZGMlkR3kWEv5DaUSEVPXe3jNFe6Pqbe34OC+ZKzVcM1nkgp9mWS5w==

If something is unclear/confusing/wrong/etc., please write about it in the comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment