Skip to main content

Bytes 位元運算

在處理二進位資料時,位元運算(Bitwise Operations)是不可或缺的工具。無論是資料加密、校驗碼計算,還是效能優化,理解 XOR、OR、AND 這些基本運算都能讓你的程式碼更強大且高效。

🔢 什麼是位元運算?

位元運算直接操作資料的二進位表示,**逐位元(bit by bit)**進行運算。在 Python 中,bytes 物件支援這些操作,讓我們能夠高效地處理二進位資料。

基本規則表

ABAND (∧)OR (∨)XOR (⊕)
00000
01011
10011
11110

⚙️ Python Bytes 位元運算語法

在 Python 中,bytes 物件支援三種位元運算符號:

  • &:AND(按位與)
  • |:OR(按位或)
  • ^:XOR(按位異或)

⚠️ 重要限制: 兩個 bytes 物件必須長度相同才能進行位元運算。

# ✅ 正確:長度相同
result = b'\x01\x02' & b'\x03\x04'

# ❌ 錯誤:長度不同會拋出 TypeError
result = b'\x01' & b'\x02\x03' # TypeError: operands must be of equal length

🔷 AND 運算(&):按位與

AND 運算會將兩個位元組的對應位元進行「與」運算,只有當兩個位元都是 1 時,結果才是 1。

基本用法

a = b'\x0F'  # 0000 1111 (二進位)
b = b'\x33' # 0011 0011 (二進位)

result = bytes(a[0] & b[0] for a, b in zip(a, b))
# 結果: 0x03
# 計算過程:
# 0000 1111 (0x0F)
# & 0011 0011 (0x33)
# -----------
# 0000 0011 (0x03)

實際應用:遮罩(Masking)

AND 運算常用於遮罩操作,用來提取或清除特定的位元:

def extract_low_nibble(byte_value: int) -> int:
"""提取低 4 位元(低半位元組)"""
mask = 0x0F # 0000 1111
return byte_value & mask

def extract_high_nibble(byte_value: int) -> int:
"""提取高 4 位元(高半位元組)"""
mask = 0xF0 # 1111 0000
return (byte_value & mask) >> 4

def clear_bits(data: bytes, mask: bytes) -> bytes:
"""使用遮罩清除特定位元(1 表示保留,0 表示清除)"""
return bytes(d & m for d, m in zip(data, mask))

# 使用範例
value = 0xAB # 1010 1011
low = extract_low_nibble(value) # 0xB
high = extract_high_nibble(value) # 0xA

original = b'\xFF\xFF'
clear_mask = b'\xF0\x0F' # 清除第一個位元組的低 4 位和第二個位元組的高 4 位
cleared = clear_bits(original, clear_mask) # b'\xF0\x0F'

🔶 OR 運算(|):按位或

OR 運算會將兩個位元組的對應位元進行「或」運算,只要其中一個位元是 1,結果就是 1。

基本用法

a = b'\x0F'  # 0000 1111
b = b'\x30' # 0011 0000

result = bytes(a[0] | b[0] for a, b in zip(a, b))
# 結果: 0x3F
# 計算過程:
# 0000 1111 (0x0F)
# | 0011 0000 (0x30)
# -----------
# 0011 1111 (0x3F)

實際應用:設定位元(Setting Bits)

OR 運算常用於設定特定位元為 1

def set_bits(data: bytes, mask: bytes) -> bytes:
"""使用遮罩設定特定位元為 1(1 表示設定,0 表示保持原樣)"""
return bytes(d | m for d, m in zip(data, mask))

def set_flag(byte_value: int, bit_position: int) -> int:
"""設定指定位元為 1(bit_position: 0-7,0 是最低位元)"""
mask = 1 << bit_position
return byte_value | mask

# 使用範例
original = b'\x00\x00'
set_mask = b'\xF0\x0F' # 設定第一個位元組的高 4 位和第二個位元組的低 4 位
set_result = set_bits(original, set_mask) # b'\xF0\x0F'

value = 0x00
value = set_flag(value, 3) # 設定第 3 位元 → 0x08 (0000 1000)

🔸 XOR 運算(^):按位異或

XOR 運算是最有趣的位元運算,當兩個位元不同時結果為 1,相同時結果為 0。

基本用法

a = b'\x0F'  # 0000 1111
b = b'\x33' # 0011 0011

result = bytes(a[0] ^ b[0] for a, b in zip(a, b))
# 結果: 0x3C
# 計算過程:
# 0000 1111 (0x0F)
# ^ 0011 0011 (0x33)
# -----------
# 0011 1100 (0x3C)

XOR 的重要特性

XOR 運算有幾個非常有用的特性:

特性說明範例
可逆性A ^ B ^ B = A加密後再 XOR 一次就能解密
交換律A ^ B = B ^ A運算順序不影響結果
結合律(A ^ B) ^ C = A ^ (B ^ C)可以任意組合
自反性A ^ A = 0相同值 XOR 結果為 0

實際應用 1:簡單加密/解密

利用 XOR 的可逆性,我們可以實現簡單的加密:

def simple_xor_cipher(data: bytes, key: bytes) -> bytes:
"""
使用 XOR 進行簡單的加密/解密

注意:這只是教學範例,不適合生產環境使用!
"""
if not key:
raise ValueError("金鑰不能為空")

# 循環使用金鑰
key_cycle = (key * ((len(data) // len(key)) + 1))[:len(data)]
return bytes(d ^ k for d, k in zip(data, key_cycle))

# 使用範例
plaintext = b"Hello, World!"
key = b"SECRET"

# 加密
encrypted = simple_xor_cipher(plaintext, key)

# 解密(XOR 的特性:加密和解密是同一個操作)
decrypted = simple_xor_cipher(encrypted, key)
assert decrypted == plaintext # True

實際應用 2:校驗碼計算

XOR 常用於計算簡單的校驗碼:

def calculate_xor_checksum(data: bytes) -> int:
"""計算 XOR 校驗碼"""
checksum = 0
for byte in data:
checksum ^= byte
return checksum

def verify_xor_checksum(data: bytes, expected_checksum: int) -> bool:
"""驗證 XOR 校驗碼"""
return calculate_xor_checksum(data) == expected_checksum

# 使用範例
message = b"Hello, World!"
checksum = calculate_xor_checksum(message)
is_valid = verify_xor_checksum(message, checksum) # True

實際應用 3:交換變數值(不使用暫存變數)

雖然這在 bytes 中不常用,但展示了 XOR 的巧妙應用:

def swap_without_temp(a: int, b: int) -> tuple[int, int]:
"""使用 XOR 交換兩個整數值(不使用暫存變數)"""
a = a ^ b # a 現在包含 a 和 b 的差異
b = a ^ b # b 現在等於原始的 a
a = a ^ b # a 現在等於原始的 b
return a, b

# 使用範例
x, y = 10, 20
x, y = swap_without_temp(x, y)
# 結果: x=20, y=10

🛠️ 綜合應用:位元操作工具類

class BytesBitwise:
"""Bytes 位元運算工具類"""

@staticmethod
def and_op(data1: bytes, data2: bytes) -> bytes:
"""執行 AND 位元運算"""
if len(data1) != len(data2):
raise ValueError(f"位元組長度必須相同: {len(data1)} != {len(data2)}")
return bytes(a & b for a, b in zip(data1, data2))

@staticmethod
def or_op(data1: bytes, data2: bytes) -> bytes:
"""執行 OR 位元運算"""
if len(data1) != len(data2):
raise ValueError(f"位元組長度必須相同: {len(data1)} != {len(data2)}")
return bytes(a | b for a, b in zip(data1, data2))

@staticmethod
def xor_op(data1: bytes, data2: bytes) -> bytes:
"""執行 XOR 位元運算"""
if len(data1) != len(data2):
raise ValueError(f"位元組長度必須相同: {len(data1)} != {len(data2)}")
return bytes(a ^ b for a, b in zip(data1, data2))

@staticmethod
def flip_bits(data: bytes) -> bytes:
"""翻轉所有位元(相當於與 0xFF 進行 XOR)"""
return bytes(byte ^ 0xFF for byte in data)

@staticmethod
def to_binary_string(data: bytes, separator: str = " ") -> str:
"""將位元組序列轉換為二進位字串表示"""
return separator.join(f"{byte:08b}" for byte in data)

# 使用範例
tool = BytesBitwise()
data1 = b'\xAA\x55'
data2 = b'\xF0\x0F'

print(tool.to_binary_string(data1)) # 10101010 01010101
print(tool.to_binary_string(data2)) # 11110000 00001111

and_result = tool.and_op(data1, data2) # b'\xA0\x05'
or_result = tool.or_op(data1, data2) # b'\xFA\x5F'
xor_result = tool.xor_op(data1, data2) # b'\x5A\x5A'

⚡ 效能考量與最佳實踐

1. 長度檢查

在進行位元運算前,務必確保兩個 bytes 物件長度相同:

def safe_bitwise_op(data1: bytes, data2: bytes, operation: str = "xor") -> bytes:
"""安全的位元運算,自動處理長度不匹配的情況"""
min_len = min(len(data1), len(data2))
truncated1 = data1[:min_len]
truncated2 = data2[:min_len]

match operation:
case "and":
return bytes(a & b for a, b in zip(truncated1, truncated2))
case "or":
return bytes(a | b for a, b in zip(truncated1, truncated2))
case "xor":
return bytes(a ^ b for a, b in zip(truncated1, truncated2))
case _:
raise ValueError(f"不支援的運算: {operation}")

2. 錯誤處理

實作穩健的錯誤處理機制:

def robust_bitwise_op(data1: bytes, data2: bytes, operation: str = "xor") -> bytes:
"""帶有完整錯誤處理的位元運算"""
# 類型檢查
if not isinstance(data1, bytes) or not isinstance(data2, bytes):
raise TypeError("輸入必須是 bytes 類型")

# 長度檢查
if len(data1) != len(data2):
raise ValueError(f"位元組長度必須相同: {len(data1)} != {len(data2)}")

# 執行運算
match operation.lower():
case "and":
return bytes(a & b for a, b in zip(data1, data2))
case "or":
return bytes(a | b for a, b in zip(data1, data2))
case "xor":
return bytes(a ^ b for a, b in zip(data1, data2))
case _:
raise ValueError(f"不支援的運算類型: {operation}")

⚠️ 常見問題與陷阱

1. 長度不匹配錯誤

# ❌ 錯誤示範
try:
result = bytes(a & b for a, b in zip(b'\x01', b'\x02\x03'))
# 這不會拋出錯誤,但只會處理第一個位元組
except Exception as e:
print(f"錯誤: {e}")

# ✅ 正確做法
def safe_operation(data1: bytes, data2: bytes) -> bytes:
if len(data1) != len(data2):
raise ValueError("長度必須相同")
return bytes(a & b for a, b in zip(data1, data2))

2. 不可變性

bytes 是不可變的,運算會產生新的物件:

original = b'\x01\x02'
mask = b'\xFF\x00'
result = bytes(a & b for a, b in zip(original, mask))

# original 不會被修改
print(original.hex()) # 0102
print(result.hex()) # 0100

3. 與整數運算的差異

# bytes 運算需要逐位元組處理
bytes_data1 = b'\x01\x02'
bytes_data2 = b'\x03\x04'
result_bytes = bytes(a & b for a, b in zip(bytes_data1, bytes_data2))

# 整數運算可以直接使用運算符
int1 = 0x0102
int2 = 0x0304
result_int = int1 & int2

print(result_bytes.hex()) # 0100
print(f"0x{result_int:04X}") # 0x0100

✅ 小結

運算符號主要用途關鍵特性
AND&遮罩、提取特定位元只有兩個位元都是 1 時結果為 1
OR|設定特定位元為 1只要有一個位元是 1 結果就是 1
XOR^加密、校驗碼、資料交換兩個位元不同時結果為 1,可逆

關鍵要點

  1. ✅ 兩個 bytes 物件必須長度相同才能進行位元運算
  2. bytes 是不可變的,運算會產生新物件
  3. ✅ XOR 具有可逆性,適合簡單的加密場景
  4. ✅ 使用適當的錯誤處理確保程式碼穩健性

延伸學習

  • 學習更多位元操作技巧(如位移運算 <<>>
  • 探索位元運算在密碼學中的應用
  • 研究如何優化大型資料的位元運算效能

掌握這些基礎後,你就能更自信地處理二進位資料,無論是網路通訊、檔案格式解析,還是效能優化,都能游刃有餘!