General knowledge in Cryptography - kiến thức tổng quan trong mật mã học (phần 2)
II. Một số kỹ thuật mã hóa cơ bản (tiếp)
3. Mã hóa Base64
Base64 là một dạng chương trình mã hóa thực hiện mã hóa đối tượng bằng cách sử dụng ký tự trong bảng mã ASCII: bao gồm các ký tự từ A-Z, a-z, 0-9, ký tự /
và +
, ngoài ra có thêm ký tự đệm =
. So sánh với mã ASCII, một ký tự biểu diễn theo mã ASCII sẽ cần bit, trong khi với Base64 chỉ cần bit, giúp giảm đi một phần không gian lưu trữ tài nguyên.
Để mã hóa một đối tượng sang dạng Base64, chúng ta thực hiện các bước sau:
- Bước : Chuyển đối tượng sang dạng bit.
- Bước : Chia chuỗi bit thành từng nhóm, mỗi nhóm ký tự.
- Bước : Chuyển từng nhóm tương ứng với một ký tự dựa vào bảng tra như trên.
- Bước : Thêm các ký tự đệm
=
nếu cần.
Chúng ta cùng xem xét ví dụ sau để hiểu rõ hơn các bước chuyển đổi: Mã hóa từ Viblo sang dạng Base64. Đầu tiên, biểu diễn Viblo sang dạng bit:
Viblo 01010110
01101001
01100010
01101100
01101111
Chia thành từng nhóm gồm bit mỗi nhóm:
010101
100110
100101
100010
011011
000110
1111
Dễ thấy nhóm cuối cùng chỉ có bit, như vậy sẽ cần bổ sung thêm hai bit 0
vào cuối, chúng ta có nhóm:
010101
100110
100101
100010
011011
000110
111100
Chuyển từng nhóm sang các ký tự tương ứng của Base64:
V
m
l
i
b
G
8
Chuỗi VmlibG8
có ký tự, dựa vào tỉ lệ bit mã hóa thành bit - byte được biểu diễn bằng ký tự, chúng ta sẽ bổ sung lần lượt các ký tự đệm =
tới khi thu được output có số ký tự là bội của . Trong trường hợp này chỉ cần bổ sung ký tự đệm. Như vậy kết quả mã hóa từ Viblo là VmlibG8=
. Một mẹo bổ sung ký tự đệm nhanh hơn là cứ mượn thêm bit 0
sẽ cần trả lại một ký tự đệm =
.
Quá trình giải mã có thể thực hiện dễ dàng bằng cách làm ngược lại các bước trên (Chú ý mỗi ký tự đệm tương đương hai bit 0
).
Base64 thường được ứng dụng trong việc truyền tải các tệp hình ảnh, âm thanh, video, ... Tuy nhiên, do số lượng ký tự cần sử dụng cho việc mã hóa âm thanh và video khá lớn, nên ngày nay Base64 được dùng chủ yếu trong việc truyền tải hình ảnh.
(Ví dụ mã nguồn hiển thị hình ảnh bằng cách mã hóa dưới dạng Base64)
Ngôn ngữ Python chứa module base64 hỗ trợ làm việc với Base64, trong đó có thể mã hóa đối tượng bằng hàm base64.b64encode()
, giải mã ngược lại bằng hàm base64.b64decode()
. Đối tượng truyền vào và kết quả trả về đều ở dạng bytes. Ví dụ:
import base64
str = 'Base64_is_amazing'
str_en_b64 = base64.b64encode(str.encode())
print('Base64 encode: ', str_en_b64)
str_de_b64 = base64.b64decode(str_en_b64)
print('Base64 decode: ', str_de_b64)
Challenge CTF: Hex64.
Từ tên đề bài và mô tả, có thể dự đoán công việc chúng ta cần thực hiện là giải mã một file về dạng plain text (thường là flag), khả năng lớn liên quan đến mã hóa Hex và Base64. Tệp hex64
chứa một chuỗi ký tự lớn (bao gồm ký tự), quan sát nhận thấy chuỗi chỉ bao gồm ký tự khác nhau từ 0-9, a-f khả năng lớn là mã hóa Hex.
Thử giải mã từ Hex, kiểm tra nhận thấy số lượng ký tự đã giảm đi và chuỗi ký tự trở thành dạng mã hóa Base64:
Thử decode một lần nữa từ Base64 chúng ta lại thu được chuỗi ở dạng mã hóa của Hex.
Như vậy, ý tưởng giải quyết bài CTF này đã rõ, chúng ta sẽ liên tục decode xen kẽ giữa Hex và Base64 cho tới plain text cuối cùng chính là flag cần tìm. Lưu ý dạng dữ liệu truyền vào của các hàm decode. Lời giải tham khảo:
import base64
f = open('hex64', 'r')
data = f.read()
f.close()
data = data.encode()
for i in range(15):
data = bytes.fromhex(data.decode())
data = base64.b64decode(data)
print(data.decode())
III. Phép toán XOR
XOR (Exclusive or) là một phép toán trên thao tác bit (bitwise operation): phép toán thực hiện tại cấp độ của từng bit riêng biệt, thường được ký hiệu là . Đặc điểm chung của các phép toán thao tác bit là tốc độ thực hiện nhanh, thứ tự tính toán ưu tiên và được hỗ trợ tốt trên đa nền tảng.
(Ký hiệu một số logic gate symbols)
Kết quả của phép toán XOR giữa hai bit riêng biệt được thể hiện qua bảng chân lý (truth table) như sau:
A | B | A B |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
Một mẹo dễ nhớ dành cho kết quả phép XOR là: Hai bit giống nhau trả về 0
, hai bit khác nhau trả về 1
. Ví dụ phép tính: 0110 1010 = 1100
Từ truth table trên, chúng ta có thể suy ra một số tính chất cơ bản của phép toán XOR áp dụng với chuỗi bit:
Tính chất | Biểu diễn |
---|---|
XOR với 0 | A 0 = A |
Chuỗi XOR với chính nó | A A = 0 |
Giao hoán | A B = B A |
Kết hợp | (A B) C = A (B C) |
Trong Python, toán tử XOR được biểu diễn bằng ký tự ^
. Chúng ta có thể thực hiện phép toán trực tiếp với số nguyên mà không cần chuyển sang dạng bit. Kết quả trả về cũng là số nguyên.
Challenge luyện tập: XOR easy
Cipher text đưa ra có đầy đủ dấu hiệu của một chuỗi hex, kết hợp với tên challenge "XOR easy" chúng ta có ý tưởng giải quyết như sau: Cần thực hiện phép toán XOR chuỗi cipher với một chuỗi khác (tạm gọi là chuỗi key) để thu được flag. Tuy nhiên, chúng ta đang chưa được cung cấp chuỗi key.
Tiếp tục để ý Hint #1 "one letter" có thể dự đoán chuỗi key có một ký tự, như vậy phép toán XOR cần thực hiện là XOR từng ký tự của cipher text với key. Tới đây, có hai hướng giải quyết:
Hướng 1: Brute force key
Thực hiện tấn công vét cạn key bằng cách vét cạn giá trị ASCII của ký tự, bắt đầu từ 0. Với từng trường hợp đều thực hiện XOR key với tất cả ký tự của cipher text để thu được flag. Lời giải tham khảo:
import time
cipher = '321815130f0c44062b1d072b07441911001c451a4d09'
cipher = bytes.fromhex(cipher).decode()
key = 0
while True:
flag = ''
for i in cipher:
flag += chr(key ^ ord(i))
print('key: %s: flag = %s' % (key, flag))
key += 1
time.sleep(2)
Ngoài ra, thay vì sử dụng hàm time.sleep()
rồi chờ quan sát output như trên, chúng ta có thể dựa vào dấu hiệu flag bắt đầu bằng Flag{...
để in ra flag nếu thỏa mãn dấu hiệu này.
cipher = '321815130f0c44062b1d072b07441911001c451a4d09'
cipher = bytes.fromhex(cipher).decode()
key = 0
while True:
flag = ''
for i in cipher:
flag += chr(key ^ ord(i))
if flag.startswith('Flag{'):
print(flag)
break
key += 1
Hướng 2: Dựa vào dấu hiệu flag tìm ra key
Như trên chúng ta đã nhắc tới flag có dạng Flag{...
, chú ý hình thức thực hiện phép toán XOR là XOR từng ký tự của cipher text với key:
flag[0] ^ key = cipher[0]
flag[1] ^ key = cipher[1]
flag[2] ^ key = cipher[2]
flag[3] ^ key = cipher[3]
...
Dựa vào các tính chất của phép toán XOR, ta có:
cipher[0] ^ flag[0] = flag[0] ^ key ^ flag[0] = (flag[0] ^ flag[0]) ^ key = 0 ^ key = key
Vì flag[0] = F
nên có thể dễ dàng tìm ra chuỗi key rồi tìm ra flag đầy đủ. Lời giải tham khảo:
cipher = '321815130f0c44062b1d072b07441911001c451a4d09'
cipher = bytes.fromhex(cipher).decode()
key = chr(ord('F') ^ ord(cipher[0]))
flag = ''
for i in cipher:
flag += chr(ord(key) ^ ord(i))
print(flag)
Tài liệu tham khảo
All rights reserved