fix login issue*
This commit is contained in:
@@ -6,9 +6,12 @@ defmodule Odinsea.Net.Cipher.AESCipher do
|
||||
Ported from: src/handling/netty/cipher/AESCipher.java
|
||||
"""
|
||||
|
||||
import Bitwise
|
||||
|
||||
@block_size 1460
|
||||
|
||||
# MapleStory AES key (32 bytes, expanded from the Java version)
|
||||
# MapleStory AES key (32 bytes = AES-256)
|
||||
# Must match the Java AES_KEY exactly
|
||||
@aes_key <<
|
||||
0x13, 0x00, 0x00, 0x00,
|
||||
0x08, 0x00, 0x00, 0x00,
|
||||
@@ -21,7 +24,14 @@ defmodule Odinsea.Net.Cipher.AESCipher do
|
||||
>>
|
||||
|
||||
@doc """
|
||||
Encrypts or decrypts packet data in place using AES-ECB with IV.
|
||||
Encrypts or decrypts packet data using AES-ECB with IV.
|
||||
|
||||
The algorithm (per block of 1456/1460 bytes):
|
||||
1. Expand the 4-byte IV to 16 bytes by repeating it 4 times
|
||||
2. Use AES-ECB to encrypt the expanded IV, producing a 16-byte keystream
|
||||
3. XOR the next 16 bytes of data with the keystream
|
||||
4. The encrypted IV becomes the new IV for the next 16-byte chunk
|
||||
5. Repeat until the block is processed
|
||||
|
||||
## Parameters
|
||||
- data: Binary data to encrypt/decrypt
|
||||
@@ -32,74 +42,52 @@ defmodule Odinsea.Net.Cipher.AESCipher do
|
||||
"""
|
||||
@spec crypt(binary(), binary()) :: binary()
|
||||
def crypt(data, <<_::binary-size(4)>> = iv) when is_binary(data) do
|
||||
crypt_recursive(data, iv, 0, byte_size(data), @block_size - 4)
|
||||
data_list = :binary.bin_to_list(data)
|
||||
result = crypt_blocks(data_list, 0, @block_size - 4, iv)
|
||||
:binary.list_to_bin(result)
|
||||
end
|
||||
|
||||
# Recursive encryption/decryption function
|
||||
@spec crypt_recursive(binary(), binary(), non_neg_integer(), non_neg_integer(), non_neg_integer()) :: binary()
|
||||
defp crypt_recursive(data, _iv, start, remaining, _length) when remaining <= 0 do
|
||||
# Return the portion of data we've processed
|
||||
binary_part(data, 0, start)
|
||||
end
|
||||
# Process data in blocks (first block: 1456 bytes, subsequent: 1460 bytes)
|
||||
defp crypt_blocks([], _start, _length, _iv), do: []
|
||||
|
||||
defp crypt_recursive(data, iv, start, remaining, length) do
|
||||
# Multiply the IV by 4
|
||||
seq_iv = multiply_bytes(iv, byte_size(iv), 4)
|
||||
|
||||
# Adjust length if remaining is smaller
|
||||
defp crypt_blocks(data, start, length, iv) do
|
||||
remaining = Kernel.length(data)
|
||||
actual_length = min(remaining, length)
|
||||
|
||||
# Extract the portion of data to process
|
||||
data_bytes = :binary.bin_to_list(data)
|
||||
# Expand 4-byte IV to 16 bytes (repeat 4 times)
|
||||
seq_iv = :binary.copy(iv, 4)
|
||||
seq_iv_list = :binary.bin_to_list(seq_iv)
|
||||
|
||||
# Process the data chunk
|
||||
{new_data_bytes, _final_seq_iv} =
|
||||
process_chunk(data_bytes, seq_iv, start, start + actual_length, 0)
|
||||
# Process this block's bytes, re-encrypting keystream every 16 bytes
|
||||
{block, rest} = Enum.split(data, actual_length)
|
||||
{processed, _final_iv} = process_block_bytes(block, seq_iv_list, 0)
|
||||
|
||||
# Convert back to binary
|
||||
new_data = :binary.list_to_bin(new_data_bytes)
|
||||
|
||||
# Continue with next chunk
|
||||
new_start = start + actual_length
|
||||
new_remaining = remaining - actual_length
|
||||
new_length = @block_size
|
||||
|
||||
crypt_recursive(new_data, iv, new_start, new_remaining, new_length)
|
||||
# Continue with next block using fresh IV expansion
|
||||
processed ++ crypt_blocks(rest, start + actual_length, @block_size, iv)
|
||||
end
|
||||
|
||||
# Process a single chunk of data
|
||||
@spec process_chunk(list(byte()), binary(), non_neg_integer(), non_neg_integer(), non_neg_integer()) ::
|
||||
{list(byte()), binary()}
|
||||
defp process_chunk(data_bytes, seq_iv, x, end_x, _offset) when x >= end_x do
|
||||
{data_bytes, seq_iv}
|
||||
end
|
||||
# Process bytes within a single block, re-encrypting keystream every 16 bytes
|
||||
defp process_block_bytes([], iv_list, _offset), do: {[], iv_list}
|
||||
|
||||
defp process_chunk(data_bytes, seq_iv, x, end_x, offset) do
|
||||
# Check if we need to re-encrypt the IV
|
||||
{new_seq_iv, new_offset} =
|
||||
if rem(offset, byte_size(seq_iv)) == 0 do
|
||||
# Encrypt the IV using AES
|
||||
encrypted_iv = aes_encrypt_block(seq_iv)
|
||||
{encrypted_iv, 0}
|
||||
defp process_block_bytes(data, iv_list, offset) do
|
||||
# Re-encrypt keystream at every 16-byte boundary
|
||||
iv_list =
|
||||
if rem(offset, 16) == 0 do
|
||||
new_iv = aes_encrypt_block(:binary.list_to_bin(iv_list))
|
||||
:binary.bin_to_list(new_iv)
|
||||
else
|
||||
{seq_iv, offset}
|
||||
iv_list
|
||||
end
|
||||
|
||||
# XOR the data byte with the IV byte
|
||||
seq_iv_bytes = :binary.bin_to_list(new_seq_iv)
|
||||
iv_index = rem(new_offset, length(seq_iv_bytes))
|
||||
iv_byte = Enum.at(seq_iv_bytes, iv_index)
|
||||
data_byte = Enum.at(data_bytes, x)
|
||||
xor_byte = Bitwise.bxor(data_byte, iv_byte)
|
||||
[byte | rest] = data
|
||||
iv_byte = Enum.at(iv_list, rem(offset, 16))
|
||||
xored = bxor(byte, iv_byte)
|
||||
|
||||
# Update the data
|
||||
updated_data = List.replace_at(data_bytes, x, xor_byte)
|
||||
|
||||
# Continue processing
|
||||
process_chunk(updated_data, new_seq_iv, x + 1, end_x, new_offset + 1)
|
||||
{rest_result, final_iv} = process_block_bytes(rest, iv_list, offset + 1)
|
||||
{[xored | rest_result], final_iv}
|
||||
end
|
||||
|
||||
# Encrypt a single 16-byte block using AES-ECB
|
||||
# Encrypt a single 16-byte block using AES-256-ECB
|
||||
@spec aes_encrypt_block(binary()) :: binary()
|
||||
defp aes_encrypt_block(block) do
|
||||
# Pad or truncate to 16 bytes for AES
|
||||
@@ -110,15 +98,9 @@ defmodule Odinsea.Net.Cipher.AESCipher do
|
||||
size when size > 16 -> binary_part(block, 0, 16)
|
||||
end
|
||||
|
||||
# Perform AES encryption in ECB mode
|
||||
:crypto.crypto_one_time(:aes_128_ecb, @aes_key, padded_block, true)
|
||||
end
|
||||
|
||||
# Multiply bytes - repeats the first `count` bytes of `input` `mul` times
|
||||
@spec multiply_bytes(binary(), non_neg_integer(), non_neg_integer()) :: binary()
|
||||
defp multiply_bytes(input, count, mul) do
|
||||
# Take first `count` bytes and repeat them `mul` times
|
||||
chunk = binary_part(input, 0, min(count, byte_size(input)))
|
||||
:binary.copy(chunk, mul)
|
||||
# Perform AES encryption in ECB mode (AES-256)
|
||||
result = :crypto.crypto_one_time(:aes_256_ecb, @aes_key, padded_block, true)
|
||||
# Take only first 16 bytes (OpenSSL may add PKCS padding)
|
||||
binary_part(result, 0, 16)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,9 +6,9 @@ defmodule Odinsea.Net.Cipher.ClientCrypto do
|
||||
Ported from: src/handling/netty/ClientCrypto.java
|
||||
"""
|
||||
|
||||
use Bitwise
|
||||
import Bitwise
|
||||
|
||||
alias Odinsea.Net.Cipher.{AESCipher, IGCipher}
|
||||
alias Odinsea.Net.Cipher.{AESCipher, IGCipher, ShandaCipher}
|
||||
|
||||
defstruct [
|
||||
:version,
|
||||
@@ -32,7 +32,7 @@ defmodule Odinsea.Net.Cipher.ClientCrypto do
|
||||
Creates a new ClientCrypto instance with random IVs.
|
||||
|
||||
## Parameters
|
||||
- version: MapleStory version number (e.g., 342)
|
||||
- version: MapleStory version number (e.g., 112)
|
||||
- use_custom_crypt: If false, uses AES encryption. If true, uses basic XOR with 0x69
|
||||
|
||||
## Returns
|
||||
@@ -50,38 +50,97 @@ defmodule Odinsea.Net.Cipher.ClientCrypto do
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new ClientCrypto instance from existing IVs (for server handshake).
|
||||
The server generates its own IVs and sends them to the client.
|
||||
|
||||
## Parameters
|
||||
- version: MapleStory version number
|
||||
- send_iv: 4-byte binary for send IV (server encrypts with this)
|
||||
- recv_iv: 4-byte binary for recv IV (server decrypts with this)
|
||||
|
||||
## Returns
|
||||
- New ClientCrypto struct
|
||||
"""
|
||||
@spec new_from_ivs(integer(), binary(), binary()) :: t()
|
||||
def new_from_ivs(version, send_iv, recv_iv) do
|
||||
%__MODULE__{
|
||||
version: version,
|
||||
use_custom_crypt: false,
|
||||
send_iv: send_iv,
|
||||
send_iv_old: <<0, 0, 0, 0>>,
|
||||
recv_iv: recv_iv,
|
||||
recv_iv_old: <<0, 0, 0, 0>>
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new ClientCrypto instance from client's IVs (after handshake).
|
||||
The IVs must be SWAPPED because:
|
||||
- Server's send IV = Client's recv IV
|
||||
- Server's recv IV = Client's send IV
|
||||
|
||||
## Parameters
|
||||
- version: MapleStory version number
|
||||
- client_send_iv: Client's send IV (from client's hello packet)
|
||||
- client_recv_iv: Client's recv IV (from client's hello packet)
|
||||
|
||||
## Returns
|
||||
- New ClientCrypto struct with properly swapped IVs
|
||||
"""
|
||||
@spec new_from_client_ivs(integer(), binary(), binary()) :: t()
|
||||
def new_from_client_ivs(version, client_send_iv, client_recv_iv) do
|
||||
# Swap the IVs: server's send = client's recv, server's recv = client's send
|
||||
%__MODULE__{
|
||||
version: version,
|
||||
use_custom_crypt: false,
|
||||
send_iv: client_recv_iv,
|
||||
send_iv_old: <<0, 0, 0, 0>>,
|
||||
recv_iv: client_send_iv,
|
||||
recv_iv_old: <<0, 0, 0, 0>>
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Encrypts outgoing packet data and updates the send IV.
|
||||
Applies Shanda encryption first, then AES encryption.
|
||||
|
||||
## Parameters
|
||||
- crypto: ClientCrypto state
|
||||
- data: Binary packet data to encrypt
|
||||
|
||||
## Returns
|
||||
- {updated_crypto, encrypted_data}
|
||||
- {updated_crypto, encrypted_data, header}
|
||||
"""
|
||||
@spec encrypt(t(), binary()) :: {t(), binary()}
|
||||
@spec encrypt(t(), binary()) :: {t(), binary(), binary()}
|
||||
def encrypt(%__MODULE__{} = crypto, data) when is_binary(data) do
|
||||
# Backup current send IV
|
||||
updated_crypto = %{crypto | send_iv_old: crypto.send_iv}
|
||||
|
||||
# Encrypt the data
|
||||
# Generate header BEFORE encryption (uses current IV)
|
||||
header = encode_header_len(updated_crypto, byte_size(data))
|
||||
|
||||
# Apply Shanda encryption first
|
||||
shanda_encrypted = ShandaCipher.encrypt(data)
|
||||
|
||||
# Apply AES encryption
|
||||
encrypted_data =
|
||||
if crypto.use_custom_crypt do
|
||||
basic_cipher(data)
|
||||
basic_cipher(shanda_encrypted)
|
||||
else
|
||||
AESCipher.crypt(data, crypto.send_iv)
|
||||
AESCipher.crypt(shanda_encrypted, crypto.send_iv)
|
||||
end
|
||||
|
||||
# Update the send IV using InnoGames hash
|
||||
# Update the send IV using InnoGames hash (AFTER encryption)
|
||||
new_send_iv = IGCipher.inno_hash(crypto.send_iv)
|
||||
final_crypto = %{updated_crypto | send_iv: new_send_iv}
|
||||
|
||||
{final_crypto, encrypted_data}
|
||||
{final_crypto, encrypted_data, header}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decrypts incoming packet data and updates the recv IV.
|
||||
Applies AES decryption first, then Shanda decryption.
|
||||
|
||||
## Parameters
|
||||
- crypto: ClientCrypto state
|
||||
@@ -95,15 +154,18 @@ defmodule Odinsea.Net.Cipher.ClientCrypto do
|
||||
# Backup current recv IV
|
||||
updated_crypto = %{crypto | recv_iv_old: crypto.recv_iv}
|
||||
|
||||
# Decrypt the data
|
||||
decrypted_data =
|
||||
# Apply AES decryption
|
||||
aes_decrypted =
|
||||
if crypto.use_custom_crypt do
|
||||
basic_cipher(data)
|
||||
else
|
||||
AESCipher.crypt(data, crypto.recv_iv)
|
||||
end
|
||||
|
||||
# Update the recv IV using InnoGames hash
|
||||
# Apply Shanda decryption
|
||||
decrypted_data = ShandaCipher.decrypt(aes_decrypted)
|
||||
|
||||
# Update the recv IV using InnoGames hash (AFTER decryption)
|
||||
new_recv_iv = IGCipher.inno_hash(crypto.recv_iv)
|
||||
final_crypto = %{updated_crypto | recv_iv: new_recv_iv}
|
||||
|
||||
@@ -123,13 +185,14 @@ defmodule Odinsea.Net.Cipher.ClientCrypto do
|
||||
"""
|
||||
@spec encode_header_len(t(), non_neg_integer()) :: binary()
|
||||
def encode_header_len(%__MODULE__{} = crypto, data_len) do
|
||||
<<s0, s1, s2, s3>> = crypto.send_iv
|
||||
<<_s0, _s1, s2, s3>> = crypto.send_iv
|
||||
|
||||
# Calculate the encoded version
|
||||
new_version = -(crypto.version + 1) &&& 0xFFFF
|
||||
enc_version = (((new_version >>> 8) &&& 0xFF) ||| ((new_version <<< 8) &&& 0xFF00)) &&& 0xFFFF
|
||||
|
||||
# Calculate raw sequence from send IV
|
||||
# Note: Using s3 and s2 (high bytes) as in Java version
|
||||
raw_seq = bxor((((s3 &&& 0xFF) ||| ((s2 <<< 8) &&& 0xFF00)) &&& 0xFFFF), enc_version)
|
||||
|
||||
# Calculate raw length
|
||||
@@ -155,8 +218,8 @@ defmodule Odinsea.Net.Cipher.ClientCrypto do
|
||||
## Returns
|
||||
- Decoded packet length
|
||||
"""
|
||||
@spec decode_header_len(integer(), integer()) :: integer()
|
||||
def decode_header_len(raw_seq, raw_len) do
|
||||
@spec decode_header_len(t(), integer(), integer()) :: integer()
|
||||
def decode_header_len(%__MODULE__{}, raw_seq, raw_len) do
|
||||
bxor(raw_seq, raw_len) &&& 0xFFFF
|
||||
end
|
||||
|
||||
@@ -175,6 +238,7 @@ defmodule Odinsea.Net.Cipher.ClientCrypto do
|
||||
<<_r0, _r1, r2, r3>> = crypto.recv_iv
|
||||
|
||||
enc_version = crypto.version &&& 0xFFFF
|
||||
# Note: Using r2 and r3 as in Java version
|
||||
seq_base = ((r2 &&& 0xFF) ||| ((r3 <<< 8) &&& 0xFF00)) &&& 0xFFFF
|
||||
|
||||
bxor((raw_seq &&& 0xFFFF), seq_base) == enc_version
|
||||
@@ -197,7 +261,7 @@ defmodule Odinsea.Net.Cipher.ClientCrypto do
|
||||
defp basic_cipher(data) do
|
||||
data
|
||||
|> :binary.bin_to_list()
|
||||
|> Enum.map(fn byte -> Bitwise.bxor(byte, 0x69) end)
|
||||
|> Enum.map(fn byte -> bxor(byte, 0x69) end)
|
||||
|> :binary.list_to_bin()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,7 +6,28 @@ defmodule Odinsea.Net.Cipher.IGCipher do
|
||||
Ported from: src/handling/netty/cipher/IGCipher.java
|
||||
"""
|
||||
|
||||
use Bitwise
|
||||
import Bitwise
|
||||
|
||||
# Shuffle table - 256 bytes used for IV transformation
|
||||
# Must be defined before functions that use it
|
||||
@shuffle_table {
|
||||
0xEC, 0x3F, 0x77, 0xA4, 0x45, 0xD0, 0x71, 0xBF, 0xB7, 0x98, 0x20, 0xFC, 0x4B, 0xE9, 0xB3, 0xE1,
|
||||
0x5C, 0x22, 0xF7, 0x0C, 0x44, 0x1B, 0x81, 0xBD, 0x63, 0x8D, 0xD4, 0xC3, 0xF2, 0x10, 0x19, 0xE0,
|
||||
0xFB, 0xA1, 0x6E, 0x66, 0xEA, 0xAE, 0xD6, 0xCE, 0x06, 0x18, 0x4E, 0xEB, 0x78, 0x95, 0xDB, 0xBA,
|
||||
0xB6, 0x42, 0x7A, 0x2A, 0x83, 0x0B, 0x54, 0x67, 0x6D, 0xE8, 0x65, 0xE7, 0x2F, 0x07, 0xF3, 0xAA,
|
||||
0x27, 0x7B, 0x85, 0xB0, 0x26, 0xFD, 0x8B, 0xA9, 0xFA, 0xBE, 0xA8, 0xD7, 0xCB, 0xCC, 0x92, 0xDA,
|
||||
0xF9, 0x93, 0x60, 0x2D, 0xDD, 0xD2, 0xA2, 0x9B, 0x39, 0x5F, 0x82, 0x21, 0x4C, 0x69, 0xF8, 0x31,
|
||||
0x87, 0xEE, 0x8E, 0xAD, 0x8C, 0x6A, 0xBC, 0xB5, 0x6B, 0x59, 0x13, 0xF1, 0x04, 0x00, 0xF6, 0x5A,
|
||||
0x35, 0x79, 0x48, 0x8F, 0x15, 0xCD, 0x97, 0x57, 0x12, 0x3E, 0x37, 0xFF, 0x9D, 0x4F, 0x51, 0xF5,
|
||||
0xA3, 0x70, 0xBB, 0x14, 0x75, 0xC2, 0xB8, 0x72, 0xC0, 0xED, 0x7D, 0x68, 0xC9, 0x2E, 0x0D, 0x62,
|
||||
0x46, 0x17, 0x11, 0x4D, 0x6C, 0xC4, 0x7E, 0x53, 0xC1, 0x25, 0xC7, 0x9A, 0x1C, 0x88, 0x58, 0x2C,
|
||||
0x89, 0xDC, 0x02, 0x64, 0x40, 0x01, 0x5D, 0x38, 0xA5, 0xE2, 0xAF, 0x55, 0xD5, 0xEF, 0x1A, 0x7C,
|
||||
0xA7, 0x5B, 0xA6, 0x6F, 0x86, 0x9F, 0x73, 0xE6, 0x0A, 0xDE, 0x2B, 0x99, 0x4A, 0x47, 0x9C, 0xDF,
|
||||
0x09, 0x76, 0x9E, 0x30, 0x0E, 0xE4, 0xB2, 0x94, 0xA0, 0x3B, 0x34, 0x1D, 0x28, 0x0F, 0x36, 0xE3,
|
||||
0x23, 0xB4, 0x03, 0xD8, 0x90, 0xC8, 0x3C, 0xFE, 0x5E, 0x32, 0x24, 0x50, 0x1F, 0x3A, 0x43, 0x8A,
|
||||
0x96, 0x41, 0x74, 0xAC, 0x52, 0x33, 0xF0, 0xD9, 0x29, 0x80, 0xB1, 0x16, 0xD3, 0xAB, 0x91, 0xB9,
|
||||
0x84, 0x7F, 0x61, 0x1E, 0xCF, 0xC5, 0xD1, 0x56, 0x3D, 0xCA, 0xF4, 0x05, 0xC6, 0xE5, 0x08, 0x49
|
||||
}
|
||||
|
||||
@doc """
|
||||
Applies the InnoGames hash transformation to a 4-byte IV.
|
||||
@@ -39,11 +60,12 @@ defmodule Odinsea.Net.Cipher.IGCipher do
|
||||
input = value &&& 0xFF
|
||||
table = shuffle_byte(input)
|
||||
|
||||
# Apply the transformation operations
|
||||
new_k0 = k0 + shuffle_byte(k1) - input
|
||||
new_k1 = k1 - bxor(k2, table)
|
||||
new_k2 = bxor(k2, shuffle_byte(k3) + input)
|
||||
new_k3 = k3 - (k0 - table)
|
||||
# Apply the transformation operations SEQUENTIALLY
|
||||
# Java modifies key[0] first, then uses modified key[0] for key[3]
|
||||
new_k0 = (k0 + (shuffle_byte(k1) - input)) &&& 0xFF
|
||||
new_k1 = (k1 - bxor(k2, table)) &&& 0xFF
|
||||
new_k2 = bxor(k2, (shuffle_byte(k3) + input) &&& 0xFF)
|
||||
new_k3 = (k3 - (new_k0 - table)) &&& 0xFF
|
||||
|
||||
# Combine into 32-bit value (little-endian)
|
||||
val =
|
||||
@@ -69,24 +91,4 @@ defmodule Odinsea.Net.Cipher.IGCipher do
|
||||
defp shuffle_byte(index) do
|
||||
elem(@shuffle_table, index &&& 0xFF)
|
||||
end
|
||||
|
||||
# Shuffle table - 256 bytes used for IV transformation
|
||||
@shuffle_table {
|
||||
0xEC, 0x3F, 0x77, 0xA4, 0x45, 0xD0, 0x71, 0xBF, 0xB7, 0x98, 0x20, 0xFC, 0x4B, 0xE9, 0xB3, 0xE1,
|
||||
0x5C, 0x22, 0xF7, 0x0C, 0x44, 0x1B, 0x81, 0xBD, 0x63, 0x8D, 0xD4, 0xC3, 0xF2, 0x10, 0x19, 0xE0,
|
||||
0xFB, 0xA1, 0x6E, 0x66, 0xEA, 0xAE, 0xD6, 0xCE, 0x06, 0x18, 0x4E, 0xEB, 0x78, 0x95, 0xDB, 0xBA,
|
||||
0xB6, 0x42, 0x7A, 0x2A, 0x83, 0x0B, 0x54, 0x67, 0x6D, 0xE8, 0x65, 0xE7, 0x2F, 0x07, 0xF3, 0xAA,
|
||||
0x27, 0x7B, 0x85, 0xB0, 0x26, 0xFD, 0x8B, 0xA9, 0xFA, 0xBE, 0xA8, 0xD7, 0xCB, 0xCC, 0x92, 0xDA,
|
||||
0xF9, 0x93, 0x60, 0x2D, 0xDD, 0xD2, 0xA2, 0x9B, 0x39, 0x5F, 0x82, 0x21, 0x4C, 0x69, 0xF8, 0x31,
|
||||
0x87, 0xEE, 0x8E, 0xAD, 0x8C, 0x6A, 0xBC, 0xB5, 0x6B, 0x59, 0x13, 0xF1, 0x04, 0x00, 0xF6, 0x5A,
|
||||
0x35, 0x79, 0x48, 0x8F, 0x15, 0xCD, 0x97, 0x57, 0x12, 0x3E, 0x37, 0xFF, 0x9D, 0x4F, 0x51, 0xF5,
|
||||
0xA3, 0x70, 0xBB, 0x14, 0x75, 0xC2, 0xB8, 0x72, 0xC0, 0xED, 0x7D, 0x68, 0xC9, 0x2E, 0x0D, 0x62,
|
||||
0x46, 0x17, 0x11, 0x4D, 0x6C, 0xC4, 0x7E, 0x53, 0xC1, 0x25, 0xC7, 0x9A, 0x1C, 0x88, 0x58, 0x2C,
|
||||
0x89, 0xDC, 0x02, 0x64, 0x40, 0x01, 0x5D, 0x38, 0xA5, 0xE2, 0xAF, 0x55, 0xD5, 0xEF, 0x1A, 0x7C,
|
||||
0xA7, 0x5B, 0xA6, 0x6F, 0x86, 0x9F, 0x73, 0xE6, 0x0A, 0xDE, 0x2B, 0x99, 0x4A, 0x47, 0x9C, 0xDF,
|
||||
0x09, 0x76, 0x9E, 0x30, 0x0E, 0xE4, 0xB2, 0x94, 0xA0, 0x3B, 0x34, 0x1D, 0x28, 0x0F, 0x36, 0xE3,
|
||||
0x23, 0xB4, 0x03, 0xD8, 0x90, 0xC8, 0x3C, 0xFE, 0x5E, 0x32, 0x24, 0x50, 0x1F, 0x3A, 0x43, 0x8A,
|
||||
0x96, 0x41, 0x74, 0xAC, 0x52, 0x33, 0xF0, 0xD9, 0x29, 0x80, 0xB1, 0x16, 0xD3, 0xAB, 0x91, 0xB9,
|
||||
0x84, 0x7F, 0x61, 0x1E, 0xCF, 0xC5, 0xD1, 0x56, 0x3D, 0xCA, 0xF4, 0x05, 0xC6, 0xE5, 0x08, 0x49
|
||||
}
|
||||
end
|
||||
|
||||
150
lib/odinsea/net/cipher/shanda_cipher.ex
Normal file
150
lib/odinsea/net/cipher/shanda_cipher.ex
Normal file
@@ -0,0 +1,150 @@
|
||||
defmodule Odinsea.Net.Cipher.ShandaCipher do
|
||||
@moduledoc """
|
||||
Shanda cipher implementation for MapleStory packet encryption.
|
||||
Direct port from Java ShandaCipher.java
|
||||
"""
|
||||
|
||||
import Bitwise
|
||||
|
||||
alias Odinsea.Util.BitTools
|
||||
|
||||
@doc """
|
||||
Encrypts data using the Shanda cipher.
|
||||
"""
|
||||
@spec encrypt(binary()) :: binary()
|
||||
def encrypt(data) when is_binary(data) do
|
||||
bytes = :binary.bin_to_list(data)
|
||||
len = length(bytes)
|
||||
|
||||
result =
|
||||
Enum.reduce(0..5, bytes, fn j, acc ->
|
||||
do_encrypt_pass(acc, len, j)
|
||||
end)
|
||||
|
||||
:binary.list_to_bin(result)
|
||||
end
|
||||
|
||||
defp do_encrypt_pass(data, len, j) do
|
||||
data_length = len &&& 0xFF
|
||||
|
||||
if rem(j, 2) == 0 do
|
||||
# Forward pass: iterate 0..length-1
|
||||
{result, _remember, _dl} =
|
||||
Enum.reduce(data, {[], 0, data_length}, fn cur, {out, remember, dl} ->
|
||||
new_cur =
|
||||
cur
|
||||
|> BitTools.roll_left(3)
|
||||
|> Kernel.+(dl)
|
||||
|> band(0xFF)
|
||||
|> bxor(remember)
|
||||
|
||||
new_remember = new_cur
|
||||
|
||||
final_cur =
|
||||
new_cur
|
||||
|> BitTools.roll_right(dl &&& 0xFF)
|
||||
|> bxor(0xFF)
|
||||
|> Kernel.+(0x48)
|
||||
|> band(0xFF)
|
||||
|
||||
{[final_cur | out], new_remember, (dl - 1) &&& 0xFF}
|
||||
end)
|
||||
|
||||
Enum.reverse(result)
|
||||
else
|
||||
# Backward pass: iterate length-1..0
|
||||
# Process reversed list, prepend results -> naturally restores forward order
|
||||
{result, _remember, _dl} =
|
||||
Enum.reduce(Enum.reverse(data), {[], 0, data_length}, fn cur, {out, remember, dl} ->
|
||||
new_cur =
|
||||
cur
|
||||
|> BitTools.roll_left(4)
|
||||
|> Kernel.+(dl)
|
||||
|> band(0xFF)
|
||||
|> bxor(remember)
|
||||
|
||||
new_remember = new_cur
|
||||
|
||||
final_cur =
|
||||
new_cur
|
||||
|> bxor(0x13)
|
||||
|> BitTools.roll_right(3)
|
||||
|
||||
{[final_cur | out], new_remember, (dl - 1) &&& 0xFF}
|
||||
end)
|
||||
|
||||
# Do NOT reverse - prepending from reversed input naturally gives forward order
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decrypts data using the Shanda cipher.
|
||||
"""
|
||||
@spec decrypt(binary()) :: binary()
|
||||
def decrypt(data) when is_binary(data) do
|
||||
bytes = :binary.bin_to_list(data)
|
||||
len = length(bytes)
|
||||
|
||||
# Java: for (int j = 1; j <= 6; j++)
|
||||
result =
|
||||
Enum.reduce(1..6, bytes, fn j, acc ->
|
||||
do_decrypt_pass(acc, len, j)
|
||||
end)
|
||||
|
||||
:binary.list_to_bin(result)
|
||||
end
|
||||
|
||||
defp do_decrypt_pass(data, len, j) do
|
||||
data_length = len &&& 0xFF
|
||||
|
||||
if rem(j, 2) == 0 do
|
||||
# Forward pass (even j in decrypt = matches encrypt's forward)
|
||||
{result, _remember, _dl} =
|
||||
Enum.reduce(data, {[], 0, data_length}, fn cur, {out, remember, dl} ->
|
||||
new_cur =
|
||||
cur
|
||||
|> Kernel.-(0x48)
|
||||
|> band(0xFF)
|
||||
|> bxor(0xFF)
|
||||
|> BitTools.roll_left(dl &&& 0xFF)
|
||||
|
||||
next_remember = new_cur
|
||||
|
||||
final_cur =
|
||||
new_cur
|
||||
|> bxor(remember)
|
||||
|> Kernel.-(dl)
|
||||
|> band(0xFF)
|
||||
|> BitTools.roll_right(3)
|
||||
|
||||
{[final_cur | out], next_remember, (dl - 1) &&& 0xFF}
|
||||
end)
|
||||
|
||||
Enum.reverse(result)
|
||||
else
|
||||
# Backward pass (odd j in decrypt = matches encrypt's backward)
|
||||
{result, _remember, _dl} =
|
||||
Enum.reduce(Enum.reverse(data), {[], 0, data_length}, fn cur, {out, remember, dl} ->
|
||||
new_cur =
|
||||
cur
|
||||
|> BitTools.roll_left(3)
|
||||
|> bxor(0x13)
|
||||
|
||||
next_remember = new_cur
|
||||
|
||||
final_cur =
|
||||
new_cur
|
||||
|> bxor(remember)
|
||||
|> Kernel.-(dl)
|
||||
|> band(0xFF)
|
||||
|> BitTools.roll_right(4)
|
||||
|
||||
{[final_cur | out], next_remember, (dl - 1) &&& 0xFF}
|
||||
end)
|
||||
|
||||
# Do NOT reverse - prepending from reversed input naturally gives forward order
|
||||
result
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user