186 lines
5.7 KiB
Elixir
186 lines
5.7 KiB
Elixir
defmodule Odinsea.CryptoTest do
|
|
@moduledoc """
|
|
Test module for MapleStory crypto implementation.
|
|
Run with: mix test test/crypto_test.exs
|
|
"""
|
|
|
|
use ExUnit.Case
|
|
|
|
alias Odinsea.Net.Cipher.{ClientCrypto, IGCipher, ShandaCipher, AESCipher}
|
|
alias Odinsea.Util.BitTools
|
|
|
|
describe "IGCipher (IV transformation)" do
|
|
test "inno_hash transforms IV correctly" do
|
|
iv = <<0x34, 0x9A, 0x0F, 0x0C>>
|
|
result = IGCipher.inno_hash(iv)
|
|
|
|
# Result should be 4 bytes
|
|
assert byte_size(result) == 4
|
|
|
|
# Result should be different from input (usually)
|
|
assert result != iv
|
|
end
|
|
|
|
test "inno_hash produces deterministic results" do
|
|
iv = <<0xA8, 0xBC, 0x0D, 0xB3>>
|
|
result1 = IGCipher.inno_hash(iv)
|
|
result2 = IGCipher.inno_hash(iv)
|
|
|
|
assert result1 == result2
|
|
end
|
|
end
|
|
|
|
describe "ShandaCipher" do
|
|
test "encrypt and decrypt are inverse operations" do
|
|
original = <<1, 0, 7, 112, 0, 4, 0>> # CP_PermissionRequest payload
|
|
encrypted = ShandaCipher.encrypt(original)
|
|
decrypted = ShandaCipher.decrypt(encrypted)
|
|
|
|
assert decrypted == original
|
|
end
|
|
|
|
test "encrypt produces different output" do
|
|
original = <<1, 0, 7, 112, 0, 4, 0>>
|
|
encrypted = ShandaCipher.encrypt(original)
|
|
|
|
assert encrypted != original
|
|
end
|
|
end
|
|
|
|
describe "AESCipher" do
|
|
test "crypt is self-inverse (XOR property)" do
|
|
data = <<1, 0, 7, 112, 0, 4, 0>>
|
|
iv = <<0xA8, 0xBC, 0x0D, 0xB3>>
|
|
|
|
encrypted = AESCipher.crypt(data, iv)
|
|
decrypted = AESCipher.crypt(encrypted, iv)
|
|
|
|
assert decrypted == data
|
|
end
|
|
end
|
|
|
|
describe "ClientCrypto" do
|
|
test "new creates crypto with random IVs" do
|
|
crypto = ClientCrypto.new(112)
|
|
|
|
assert byte_size(crypto.send_iv) == 4
|
|
assert byte_size(crypto.recv_iv) == 4
|
|
assert crypto.version == 112
|
|
end
|
|
|
|
test "new_from_ivs creates crypto with specified IVs" do
|
|
send_iv = <<0x34, 0x9A, 0x0F, 0x0C>>
|
|
recv_iv = <<0xA8, 0xBC, 0x0D, 0xB3>>
|
|
|
|
crypto = ClientCrypto.new_from_ivs(112, send_iv, recv_iv)
|
|
|
|
assert crypto.send_iv == send_iv
|
|
assert crypto.recv_iv == recv_iv
|
|
end
|
|
|
|
test "new_from_client_ivs swaps IVs correctly" do
|
|
# Client's perspective
|
|
client_send_iv = <<0xA8, 0xBC, 0x0D, 0xB3>>
|
|
client_recv_iv = <<0x34, 0x9A, 0x0F, 0x0C>>
|
|
|
|
# Server's perspective (should be swapped)
|
|
crypto = ClientCrypto.new_from_client_ivs(112, client_send_iv, client_recv_iv)
|
|
|
|
# Server's send = client's recv
|
|
assert crypto.send_iv == client_recv_iv
|
|
# Server's recv = client's send
|
|
assert crypto.recv_iv == client_send_iv
|
|
end
|
|
|
|
test "decrypt updates recv_iv" do
|
|
crypto = ClientCrypto.new(112)
|
|
original_recv_iv = crypto.recv_iv
|
|
|
|
encrypted_data = <<0x7C, 0xA8, 0x7B, 0xA8, 0xBF, 0x0A, 0xCD, 0xDE>>
|
|
{new_crypto, _decrypted} = ClientCrypto.decrypt(crypto, encrypted_data)
|
|
|
|
# IV should be updated after decryption
|
|
assert new_crypto.recv_iv != original_recv_iv
|
|
end
|
|
|
|
test "encrypt updates send_iv" do
|
|
crypto = ClientCrypto.new(112)
|
|
original_send_iv = crypto.send_iv
|
|
|
|
data = <<1, 0, 7, 112, 0, 4, 0>>
|
|
{new_crypto, _encrypted, _header} = ClientCrypto.encrypt(crypto, data)
|
|
|
|
# IV should be updated after encryption
|
|
assert new_crypto.send_iv != original_send_iv
|
|
end
|
|
|
|
test "header encoding produces 4 bytes" do
|
|
crypto = ClientCrypto.new(112)
|
|
data = <<1, 0, 7, 112, 0, 4, 0>>
|
|
|
|
{_new_crypto, _encrypted, header} = ClientCrypto.encrypt(crypto, data)
|
|
|
|
assert byte_size(header) == 4
|
|
end
|
|
|
|
test "header validation works correctly" do
|
|
crypto = ClientCrypto.new(112)
|
|
data = <<1, 0, 7, 112, 0, 4, 0>>
|
|
|
|
{new_crypto, encrypted, header} = ClientCrypto.encrypt(crypto, data)
|
|
|
|
# Extract raw_seq from header
|
|
<<raw_seq::little-16, _raw_len::little-16>> = header
|
|
|
|
# Validation should pass
|
|
assert ClientCrypto.decode_header_valid?(new_crypto, raw_seq)
|
|
end
|
|
|
|
test "full encrypt/decrypt roundtrip" do
|
|
# Create crypto instances with same IVs
|
|
send_iv = <<0x34, 0x9A, 0x0F, 0x0C>>
|
|
recv_iv = <<0xA8, 0xBC, 0x0D, 0xB3>>
|
|
|
|
# Server's crypto (for sending)
|
|
server_crypto = ClientCrypto.new_from_ivs(112, send_iv, recv_iv)
|
|
|
|
# Client's crypto would have swapped IVs
|
|
# For testing, we'll use the same crypto to verify roundtrip
|
|
original_data = <<1, 0, 7, 112, 0, 4, 0>>
|
|
|
|
# Encrypt
|
|
{server_crypto_after, encrypted, header} = ClientCrypto.encrypt(server_crypto, original_data)
|
|
|
|
# Decrypt (using recv_iv which should match server's send_iv after swap)
|
|
# For this test, we'll create a matching client crypto
|
|
client_crypto = ClientCrypto.new_from_ivs(112, recv_iv, send_iv)
|
|
{client_crypto_after, decrypted} = ClientCrypto.decrypt(client_crypto, encrypted)
|
|
|
|
assert decrypted == original_data
|
|
end
|
|
end
|
|
|
|
describe "BitTools" do
|
|
test "roll_left rotates bits correctly" do
|
|
# 0b11001101 = 205
|
|
# rotate left by 3: 0b01101101 = 109 (wrapping around)
|
|
result = BitTools.roll_left(205, 3)
|
|
assert result == 109
|
|
end
|
|
|
|
test "roll_right rotates bits correctly" do
|
|
# Test that roll_right is inverse of roll_left
|
|
original = 205
|
|
rotated = BitTools.roll_left(original, 3)
|
|
back = BitTools.roll_right(rotated, 3)
|
|
assert back == original
|
|
end
|
|
|
|
test "multiply_bytes repeats correctly" do
|
|
input = <<1, 2, 3, 4>>
|
|
result = BitTools.multiply_bytes(input, 4, 2)
|
|
assert result == <<1, 2, 3, 4, 1, 2, 3, 4>>
|
|
end
|
|
end
|
|
end
|