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
|
||||
|
||||
Reference in New Issue
Block a user