AIWI คือ USB Hardware Security Token (Hardlock) ขนาดเล็กกะทัดรัด ราคาประหยัด รองรับได้หลาย OS เช่น mac, Win, Linux ฯลฯ และหลายสถาปัตยกรรม เช่น x86, x64, ARM ฯลฯ โดยใช้การสื่อสารแบบ HID ทำให้ไม่ต้องใช้ driver พิเศษใดๆ ซึ่งเหมาะมากหากคุณต้องการป้องกันระดับพื้นฐาน หรืองานทั่วไป
AIWI ทำหน้าที่เป็น "กล่องเก็บความลับ" ขนาดพกพา — นักพัฒนาสามารถเก็บข้อมูลที่มีความสำคัญ (รหัสผ่าน, key, ข้อมูลใบอนุญาต, ส่วน Header files ฯลฯ) ลงใน EEPROM ภายในของ AIWI ได้ ซึ่งนอกจากจะเข้ารหัสด้วย AES-256 แล้ว ก็ยังมีความสามารถป้องกันการโจมตีจากหลายช่องทาง และระบบทำลายตัวเองอัตโนมัติ หรือตามกำหนดได้อีกด้วย
| รายการ | รายละเอียด |
|---|---|
| AIWI device | AIWI Hardlock v1.04 ขึ้นไป |
| USB port | USB 1.1 หรือสูงกว่า (รองรับ Low Speed HID) |
| USB Hub | รองรับการเสียบผ่าน Hub (v1.05) |
AIWI ใช้ HID protocol มาตรฐาน จึงรองรับ library เชื่อมต่อ HID ได้ทุกตัว:
| ภาษา | Library | วิธีติดตั้ง |
|---|---|---|
| C / C++ | hidapi | sudo apt install libhidapi-dev |
| Python | hid (hidapi-python) | pip install hid |
| Go | go-hid | go get github.com/sstallion/go-hid |
| Java | hid4java | Maven: org.hid4java:hid4java:0.8.0 |
สำหรับการเข้ารหัส AES-256:
| ภาษา | Library / Module |
|---|---|
| Python | pip install pycryptodome → from Crypto.Cipher import AES |
| Go | Standard library: crypto/aes |
| Java | Standard library: javax.crypto |
| C | OpenSSL หรือ mbedTLS |
| อื่นๆ | โปรดติดต่อทีมงานเพื่อขอความช่วยเหลือ |
AIWI มีหน่วยความจำ 512 bytes แบ่งเป็น 4 Segment (A–D) แต่ละ Segment ขนาด 128 bytes แต่ละ Segment แบ่งได้อีก 4 block โดยมีขนาด block 32 bytes (โดยพื้นที่ User จะอยู่ที่ Segment B-D เท่านั้น):
ข้อมูลทั้ง block นี้ คือรหัส AES Owner Key ที่กำหนดโดยผู้ใช้ ยาว 32 bytes (สั่งเขียนได้เฉพาะครั้งแรกตอน initial AIWI เท่านั้น และเปลี่ยนแปลงไม่ได้อีก)
ข้อมูล 28 bytes แรกของ block 02 จะมีรูปแบบดังนี้:
[N_vendor][vendor_char_1]...[vendor_char_N] [N_device][device_char_1]...[device_char_N] [N_serial][serial_char_1]...[serial_char_N]
โดยที่ byte แรก (N) ของแต่ละกลุ่มคือ ความยาว ของ string ที่ตามมา ตัวอย่าง เช่น:
// Vendor Name = "DuinoThumb" (10 chars) [0x0A]['D']['u']['i']['n']['o']['T']['h']['u']['m']['b'] // Device Name = "AIWI-PRO" (8 chars) [0x08]['A']['I']['W']['I']['-']['P']['R']['O'] // Serial = "91D" (3 chars) [0x03]['9']['1']['D']
พื้นที่ส่วนนี้สั่งเขียนได้เฉพาะครั้งแรก (หลังจากกำหนดรหัส AES) และเขียนได้ครั้งเดียวเท่านั้น กำหนดขนาดสูงสุดไม่เกิน 28 Bytes (ทั้ง Vender_name, Device_name, Serial ใช้พื้นที่ร่วมกัน)
AIWI มีระบบป้องกันการ brute-force ด้วยการนับจำนวนครั้งที่ส่งคำสั่งผิด:
WRBL98 (ต้องเข้ารหัส)AIWI มีกลไกจำกัดอายุการใช้งานและ self-destruct (ทำลายตัวเอง):
WRBL98 (ต้องเข้ารหัส)| รูปแบบ LED | ความหมาย |
|---|---|
| กระพริบเร็ว (~250ms on/off) | ยังไม่มี owner / ยังไม่ได้ setup |
| กระพริบปกติ (~250ms on, 3s off) | Setup เสร็จแล้ว พร้อมใช้งาน |
| กระพริบช้ามาก (3s on/3s off) | Device locked หรือ Suicide ทำงาน |
AIWI ทำงานเป็น USB HID device ใช้ Feature Report สำหรับทั้งการส่งและรับข้อมูล:
| Operation | HID Request | hidapi function | ขนาด |
|---|---|---|---|
| ส่งข้อมูล/คำสั่ง | SET_REPORT | hid_send_feature_report() | 33 bytes |
| รับข้อมูล | GET_REPORT | hid_get_feature_report() | 33 bytes |
0x00 เสมอ
ดังนั้น buffer จริงมีขนาด 33 bytes: [0x00] + [data 32 bytes]
แต่ละ "packet" ที่ส่งไป AIWI มีขนาด 32 bytes เสมอ โดยมี 2 ประเภท:
| Byte | เนื้อหา | หมายเหตุ |
|---|---|---|
| 0–3 | Command string (4 chars) | เช่น 'R','D','B','L' |
| 4–5 | Parameter (2 digits ASCII) | เช่น '0','5' = block 05 |
| 6–31 | Padding / Junk | ค่าอะไรก็ได้ (เติมให้ครบ 32 bytes) |
| Byte | เนื้อหา |
|---|---|
| 0–31 | ข้อมูล 32 bytes ที่ต้องการเขียน |
เมื่อ AIWI ถูกลงทะเบียนไปแล้ว Command Packet 32 bytes ต้องผ่านการเข้ารหัส AES-256 ECB ก่อนส่งทุกครั้ง:
0x00 ด้านหน้า → รวมส่ง 33 bytes// ตัวอย่างการสร้าง AES-encrypted command สำหรับ "RDBL05" plaintext = b"RDBL05" + b"\x00" * 10 # 16 bytes encrypted_16 = AES256_ECB_Encrypt(plaintext, owner_key_32bytes) command_packet = encrypted_16 + b"\x00" * 16 # 32 bytes send_buffer = b"\x00" + command_packet # 33 bytes (เติม Report ID)
AIWI ที่ได้รับจากโรงงานจะอยู่ในสถานะ FACTORY_LOCKED ต้องทำการ setup ก่อนจึงจะใช้งาน read/write ได้:
// Command Packet 32 bytes (ส่งแบบ plaintext เพราะยังอยู่ในสถานะ FACTORY_LOCKED และ AIWI ไม่มีตัวถอดรหัส) buf[0..3] = 'W','R','B','L' // command buf[4..5] = '0','1' // parameter = block 01 buf[6..31] = 0x00 * 26 // padding
// Data Packet 32 bytes — ข้อมูล USB Descriptor buf[0] = VID_LOW // Vendor ID low byte เช่น 0x20 buf[1] = VID_HIGH // Vendor ID high byte เช่น 0xA0 (= 0x20A0) buf[2] = PID_LOW // Product ID low byte buf[3] = PID_HIGH // Product ID high byte // ต่อจากนี้เป็น USB Strings ในรูปแบบ [length][chars...] buf[4] = N_vendor // ความยาว Vendor Name buf[5..] = vendor_name_chars ... = N_device ... = device_name_chars ... = N_serial ... = serial_chars
ขั้นตอนใน code:
"RDBL" + "xx" ด้วย AES-256 ECB + Owner Keyhid_send_feature_report() ส่ง Command Packet 33 byteshid_get_feature_report() รับข้อมูล 33 bytes (ตัด byte แรก = Report ID ออก → ได้ data 32 bytes)ขั้นตอนใน code:
"WRBL" + "xx" ด้วย AES-256 ECB + Owner Keyhid_send_feature_report() ส่ง Command Packet 33 byteshid_send_feature_report() อีกครั้งด้วย Data Packet 33 bytes (data จริง 32 bytes)| Command | Parameter | ผลลัพธ์ |
|---|---|---|
| RDBL | 98 | อ่าน Security Counter (5 bytes: [anti_crack] [suicide 4 bytes]) |
| RDBL | 99 | อ่าน Owner ID (8 bytes) |
| WRBL | 98 | รีเซ็ต Anti-crack และ Suicide Counter (5 bytes data) |
data[0] = ค่า Anti-crack ใหม่ (เช่น 250)
data[1..4] = ค่า Suicide Counter ใหม่ (32-bit, big-endian)
เช่น 250000 = 0x00 0x03 0xD0 0x90
| Command | Parameter (xx) | คำอธิบาย |
|---|---|---|
WRBL |
01 | เขียน AES Owner Key 32 bytes ลง Segment A2 |
WRBL |
02 | เขียน USB Descriptor ลง Segment A3 → (Registered!) |
WRBL |
04–15 | เขียนข้อมูล 32 bytes ลง user block ที่ระบุ (ต้องเข้ารหัสคำสั่ง) |
WRBL |
98 | รีเซ็ต Anti-crack / Suicide counter (ต้องเข้ารหัสคำสั่ง) |
RDBL |
04–15 | อ่านข้อมูล 32 bytes จาก user block ที่ระบุ (ต้องเข้ารหัสคำสั่ง) |
RDBL |
98 | อ่านค่า Security counter 5 bytes (anti_crack + suicide) |
RDBL |
99 | อ่าน Owner ID 8 bytes |
ดูไฟล์ตัวอย่างในโฟลเดอร์ client-demo/:
| ไฟล์ | ภาษา | Platform |
|---|---|---|
aiwi-linux-embedded.c | C | Linux (ARM/x86) |
aiwi-python.py | Python 3 | Linux / Windows / macOS |
aiwi-golang.go | Go | Linux / Windows / macOS |
aiwi-java.java | Java | Linux / Windows / macOS |
from Crypto.Cipher import AES import hid AIWI_VID = 0x20A0 AIWI_PID = 0x413A def make_encrypted_cmd(command: str, block_num: int, owner_key: bytes) -> list: """สร้าง AES-256 encrypted command packet (32 bytes)""" plaintext = command.encode() + f"{block_num:02d}".encode() plaintext = plaintext + b"\x00" * (16 - len(plaintext)) # pad to 16 bytes cipher = AES.new(owner_key, AES.MODE_ECB) encrypted = cipher.encrypt(plaintext) # AES-256 ECB return list(encrypted) + [0x00] * 16 # 32 bytes # เชื่อมต่อ dev = hid.device() dev.open(AIWI_VID, AIWI_PID) owner_key = b"your_32_byte_owner_key_here....." # 32 bytes # อ่าน Block 05 cmd = make_encrypted_cmd("RDBL", 5, owner_key) dev.send_feature_report([0x00] + cmd) data = dev.get_feature_report(0x00, 33)[1:] # ตัด Report ID print(data) # เขียน Block 04 cmd = make_encrypted_cmd("WRBL", 4, owner_key) payload = list(b"MySecretData!!") + [0] * 18 # 32 bytes dev.send_feature_report([0x00] + cmd) dev.send_feature_report([0x00] + payload) dev.close()
import ( "crypto/aes" "fmt" hid "github.com/sstallion/go-hid" ) func makeEncryptedCmd(command string, blockNum int, ownerKey []byte) []byte { plaintext := make([]byte, 16) copy(plaintext, []byte(fmt.Sprintf("%s%02d", command, blockNum))) block, _ := aes.NewCipher(ownerKey) encrypted := make([]byte, 16) block.Encrypt(encrypted, plaintext) // AES-256 ECB result := append(encrypted, make([]byte, 16)...) // 32 bytes return result }
import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import java.util.Arrays; static byte[] makeEncryptedCmd(String command, int blockNum, byte[] ownerKey) throws Exception { byte[] plaintext = new byte[16]; byte[] cmdBytes = (command + String.format("%02d", blockNum)).getBytes(); System.arraycopy(cmdBytes, 0, plaintext, 0, cmdBytes.length); // pad with 0x00 SecretKeySpec keySpec = new SecretKeySpec(ownerKey, "AES"); Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding"); cipher.init(Cipher.ENCRYPT_MODE, keySpec); byte[] encrypted = cipher.doFinal(plaintext); // AES-256 ECB byte[] result = new byte[32]; System.arraycopy(encrypted, 0, result, 0, 16); // 32 bytes return result; }
| สถานการณ์ | สาเหตุ | วิธีแก้ไข |
|---|---|---|
| เปิด device ไม่ได้ | ไม่ได้เสียบ AIWI / VID:PID ผิด / ไม่มีสิทธิ์ | ตรวจสอบ USB connection, ตรวจสอบ VID/PID, บน Linux ต้องเพิ่ม udev rule |
| ส่งคำสั่งแล้วไม่ได้ข้อมูลกลับ / ได้ 0x00 ทั้งหมด | AES key ผิด / command ผิดรูปแบบ / anti-crack ถูกเรียก | ตรวจสอบ owner key, ตรวจสอบ block number ว่าอยู่ใน range |
| Device หยุดตอบสนอง | Anti-crack counter หมด | ต้อง reset counter ด้วย WRBL98 ด้วย key ที่ถูกต้อง |
| ข้อมูลหายหมด | Suicide counter หมด | ไม่สามารถกู้คืนได้ ต้อง reflash และ setup ใหม่ |
| ปัญหา enumerate บน USB Hub | เป็นข้อกำหนดห้ามใช้ AIWI v1.04 ผ่าน USB Hub | สามารถใช้ได้ใน AIWI v1.05 |
สร้างไฟล์ /etc/udev/rules.d/99-aiwi.rules:
SUBSYSTEM=="usb", ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="413a", MODE="0666" KERNEL=="hidraw*", ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="413a", MODE="0666"
sudo udevadm control --reload-rules && sudo udevadm trigger
AES/ECB/NoPadding หรือ AES.MODE_ECB
เสมอเมื่อสร้าง cipher object
VID=0x20A0, PID=0x413A
AIWI Developer Manual — Document v1.0 | สงวนลิขสิทธิ์