คู่มือเข้าใจ Module C — DCS-CLS
อธิบายภาษาไทยแบบเทียบโค้ดจริงทีละช่วง พร้อมตัวอย่างคำนวณและคำถามสอบปากเปล่า • จัดทำ 12 ตุลาคม 2026
ขอบเขตหลักคือทุก procedure ใน module_C.asm รวมจุดเชื่อมกับ A/B และพื้นฐานที่จำเป็น เอกสารนี้อธิบาย implementation ที่มีอยู่ ไม่ได้แก้ source หรือเขียนฟังก์ชันเฉลยใหม่ให้ ตัวอย่างที่เป็นโค้ดคัดจากไฟล์จริงโดยคงคำสั่งและลำดับไว้ เลขบรรทัดเป็นเลขของ snapshot นี้ ถ้า source เปลี่ยนต้องตรวจใหม่
| รายการ | หลักฐาน |
|---|---|
| Repository | https://github.com/Applelyyyy/DCS-CLS |
| Commit | 363ba58d884ba699c40fd7a6539be729f36eb4f7 |
| Source | C:\Users\iponm\DCS-CLS\module_C.asm |
| SHA-256 ของ module_C.asm | 797b14c2fa8c57db97afd26e68b2fadb40d3931caef5c92c7fec87dd186811f7 |
| หลักฐานการรัน | ผู้ใช้ส่ง output การ build และ manual demo ในแชต |
| การตรวจรอบจัดทำเอกสาร | อ่าน source ปัจจุบัน; คำนวณ trace แยกเพื่อตรวจตัวเลข; ไม่ได้รัน test.bat หรือ runtime MASM ใหม่ |
ไม่อ้างว่าทั้งโปรแกรมไม่มีบั๊ก การคำนวณแยกตรวจตารางและตัวเลขได้ แต่ไม่พิสูจน์ stack/register และพฤติกรรม executable ของ Assembly ทุกเส้นทาง
1. แผนอ่านและภาพรวม
อ่านบท 1–3 เพื่อจับ input/output แล้วอ่านบท 4–6 เพื่อเข้าใจ DES หนึ่ง block หลังจากนั้นอ่านบท 7–8 เรื่อง ECB/padding และบท 9 เรื่อง helpers ปิดท้ายด้วย trace, ขอบเขตการตรวจ และคำถามอาจารย์ หากงงให้หยุดที่หนึ่งช่วง: ระบุค่าก่อนเข้า ค่าหลังออก และเหตุผลของแต่ละ jump
| Module | หน้าที่จริง | เกี่ยวกับคำสั่ง |
|---|---|---|
| A | รับ/parse/validate คำสั่ง แปลง key text เป็น bytes เปิดอ่านและเขียนไฟล์ เรียกโมดูลอื่น | ทุกคำสั่ง |
| B | GenerateKeySchedule สร้าง subkeys และ DisplayKeySchedule แสดงผล | KEYGEN; A เรียกสร้างอีกครั้งใน ENCRYPT/DECRYPT |
| C | DES block core, ECB, PKCS#7; รับ buffers ไม่รับชื่อไฟล์ | ENCRYPT, DECRYPT |
| D | แสดง hex/ASCII และนับ histogram จาก buffer ที่ A อ่านแล้ว | DUMP, STATS |
เส้นทาง ENCRYPT
A: ตรวจคำสั่งและแปลง key หลักเป็น 8 bytes
↓
A: อ่านไฟล์ → input buffer + ความยาว
↓
B: สร้าง K1–K16 ลง buffer 96 bytes
↓
C: EncryptBufferECB จัด block และ padding
↓
C: EncryptDESBlock ต่อ block → output buffer
↓
A: ถ้า status สำเร็จ เขียนผลลงไฟล์เส้นทางหนึ่ง DES block
รับ block 8 bytes = 64 bits
↓
ApplyInitialPermutation: IP
↓
ReadBE32: แบ่งเป็น L0 / R0 อย่างละ 32 bits
↓
RunFeistelRounds: ทำ 16 rounds
↓
WriteBE32: เขียน R16 ก่อน L16
↓
ApplyInversePermutation: inverse IP
↓
ได้ผล block 8 bytesภายในฟังก์ชัน f
R เก่า 32 bits
↓
E expansion → 48 bits
↓
XOR subkey ของรอบนั้น → 48 bits
↓
S-box 8 กล่อง: 6 → 4 bits ต่อกล่อง → 32 bits
↓
P permutation → ผล f 32 bitsL ใหม่ = R เก่า
R ใหม่ = L เก่า XOR f(R เก่า, subkey ประจำรอบ)ข้อที่มักสับสน: เลขท้าย L0/L1/L16 คือหมายเลขรอบ ไม่ใช่ตำแหน่ง bit; ทุก block ใช้ครบ K1–K16; K ไม่ใช่ key หลักในสูตรของ round; P ไม่ได้ลด 64 เป็น 32; IP ไม่ใช่การแปลง ASCII และไม่ได้เพิ่มจำนวน bits
2. พื้นฐานที่โค้ด C ใช้จริง
| คำ/คำสั่ง | ความหมายที่ต้องแม่น |
|---|---|
| 1 byte / 1 bit | 1 byte = 8 bits; bit มีค่า 0/1; 8 bytes = 64 bits |
| ASCII กับ binary | A = 65 ฐานสิบ = 41h = 01000001; เป็นข้อมูลเดียวกันคนละวิธีแสดง |
| PTR BYTE / DWORD | PTR BYTE เป็นชนิด pointer ของ parameter ไม่ใช่การจองพื้นที่; pointer x86 32-bit กิน stack slot 4 bytes; DWORD คือค่า 32 bits |
| mov esi,inputPtr | เอาค่า address ใน parameter ใส่ ESI |
| mov al,[esi] | อ่าน byte จาก memory ที่ ESI ชี้ |
| ADDR / OFFSET | ส่ง address ของตัวแปร; ADDR ใช้ใน INVOKE และรองรับตัวแปร local; global ในไฟล์นี้ใช้ส่งตำแหน่ง memory |
| EAX / AL | AL เป็น 8 bits ล่างของ EAX; เขียน AL ไม่ล้าง 24 bits บน |
| movzx | ขยายค่าขนาดเล็กด้วยศูนย์; อ่าน byte เป็น unsigned 0–255 |
| test eax,eax | อัปเดต flags จาก AND โดยไม่แก้ EAX; jz เมื่อศูนย์ |
| cmp / jb / jae | เปรียบเทียบโดยไม่เก็บผลลบ; jb/jae เทียบแบบ unsigned สำหรับ length/index |
| xor eax,eax | ล้าง EAX เป็น 0 |
| shl / shr / CL | เลื่อน bits; shift count แบบ register ใช้ CL ซึ่งเป็นส่วนล่างของ ECX |
| div ecx | หาร unsigned EDX:EAX ด้วย ECX; quotient ใน EAX, remainder ใน EDX; จึงล้าง EDX ก่อนหารค่า 32-bit |
| INVOKE / PROTO | MASM ตรวจ interface และสร้างการส่ง arguments/call ตาม calling convention |
| PROC USES | ให้ MASM รักษา register ที่ระบุและคืนก่อน ret; EAX มักจงใจไม่อยู่ใน USES เพื่อส่งผลกลับ |
| push / pop | เก็บค่าชั่วคราวบน stack และรับกลับ; ต้องสมดุลในทุกเส้นทาง |
| rep stosb / cld | เขียน AL ที่ปลายทาง EDI จำนวน ECX bytes; cld ให้เดินหน้า; ทำให้ EDI เคลื่อน จึงมี push/pop คืนฐาน |
ไฟล์นี้ใช้ .model flat, stdcall: parameters ส่งผ่าน stack ไม่ใช่กติกา x86-64 ที่ใช้ register หลักสำหรับ arguments Procedure ที่มี parameters ให้ MASM สร้าง frame/prologue และ epilogue ตามค่า assembler ปัจจุบัน หากอาจารย์ถาม machine instructions ที่สร้างจริงต้องดู .lst หรือ disassembly ไม่ควรบอกว่าข้อความ PROC เองคือ instruction CPU
3. Interface, status และข้อมูลที่เก็บ
Public มี 4 procedure ส่วน IP/f/S-box/helpers เป็น private ของไฟล์ ฟังก์ชัน block ไม่มี length/capacity parameter caller ต้องรับรองว่า input/output มีอย่างน้อย 8 bytes และ subkeys มี 96 bytes Pointer ไม่เป็น NULL ไม่ได้พิสูจน์ว่าพื้นที่ถูกต้อง
| ฟังก์ชัน | Arguments ตามลำดับ | ผล |
|---|---|---|
| EncryptDESBlock / DecryptDESBlock | inputPtr, outputPtr, subkeysPtr | เขียน 8 bytes; EAX เป็น status |
| EncryptBufferECB / DecryptBufferECB | inputPtr, inputLength, outputPtr, outputCapacity, subkeysPtr, outputLengthPtr | เขียนผลใน outputPtr; เขียนความยาวผ่าน outputLengthPtr; EAX เป็น status |
| Argument ของ buffer API | ค่า vs address | หน้าที่ |
|---|---|---|
| inputPtr | address | ต้นข้อมูล ไม่ใช่ชื่อไฟล์ |
| inputLength | ค่า DWORD | จำนวน bytes ข้อมูลจริง |
| outputPtr | address | ต้นพื้นที่เขียนผล |
| outputCapacity | ค่า DWORD | จำนวน bytes ที่ปลายทางจุได้ |
| subkeysPtr | address | ต้น K1–K16 เรียงกัน 6 bytes ต่อชุด |
| outputLengthPtr | address | ตำแหน่งตัวแปร DWORD ที่ให้ฟังก์ชันเขียนความยาวผลกลับ |
006 .386
007 .model flat, stdcall
008 option casemap:none
009 INCLUDE module_C.inc
010 PUBLIC EncryptBufferECB, DecryptBufferECB, EncryptDESBlock, DecryptDESBlock
011
012 MODE_ENCRYPT EQU 0
013 MODE_DECRYPT EQU 1
014 STATUS_OK EQU 0
015 ERR_BUFFER EQU 13
016 ERR_CIPHER_LENGTH EQU 14
017 ERR_PADDING EQU 15
018 ERR_PARAM EQU 16
019
020 PermuteBits PROTO :PTR BYTE,:PTR BYTE,:PTR BYTE,:DWORD
021 GetFipsBitC PROTO :PTR BYTE,:DWORD
022 ReadBE32 PROTO :PTR BYTE
023 WriteBE32 PROTO :DWORD,:PTR BYTE
024 ApplyInitialPermutation PROTO :PTR BYTE,:PTR BYTE
025 RunFeistelRounds PROTO :PTR BYTE,:PTR BYTE,:DWORD
026 FeistelFunction PROTO :DWORD,:PTR BYTE
027 ExpandRightHalf PROTO :DWORD,:PTR BYTE
028 ApplySBoxes PROTO :PTR BYTE
029 ApplyPPermutation PROTO :DWORD
030 ApplyInversePermutation PROTO :PTR BYTE,:PTR BYTEคำสั่ง .386 เลือก instruction set พื้นฐาน 80386; flat/stdcall เลือก memory model/calling convention; casemap:none รักษาตัวพิมพ์ของ symbol INCLUDE ใช้ interface และ PUBLIC เปิด 4 entry points MODE_ENCRYPT=0 และ MODE_DECRYPT=1 เลือก key order Status 13–16 สอดคล้องรหัสที่ A แปลเป็นข้อความ PROTO หลังจากนั้นเป็นสัญญาของ helpers ภายในไฟล์
032 .data
033 ipTable BYTE 58,50,42,34,26,18,10,2,60,52,44,36,28,20,12,4
034 BYTE 62,54,46,38,30,22,14,6,64,56,48,40,32,24,16,8
035 BYTE 57,49,41,33,25,17,9,1,59,51,43,35,27,19,11,3
036 BYTE 61,53,45,37,29,21,13,5,63,55,47,39,31,23,15,7
037 inverseIpTable BYTE 40,8,48,16,56,24,64,32,39,7,47,15,55,23,63,31
038 BYTE 38,6,46,14,54,22,62,30,37,5,45,13,53,21,61,29
039 BYTE 36,4,44,12,52,20,60,28,35,3,43,11,51,19,59,27
040 BYTE 34,2,42,10,50,18,58,26,33,1,41,9,49,17,57,25
041 expansionTable BYTE 32,1,2,3,4,5,4,5,6,7,8,9,8,9,10,11,12,13
042 BYTE 12,13,14,15,16,17,16,17,18,19,20,21
043 BYTE 20,21,22,23,24,25,24,25,26,27,28,29,28,29,30,31,32,1
044 pTable BYTE 16,7,20,21,29,12,28,17,1,15,23,26,5,18,31,10
045 BYTE 2,8,24,14,32,27,3,9,19,13,30,6,22,11,4,25ตาราง IP 64 ช่อง, inverse IP 64 ช่อง, E 48 ช่อง และ P 32 ช่องเป็นค่าที่กำหนดโดย DES แต่ละช่องหมายถึงตำแหน่ง input ที่จะหยิบมาเป็น output ช่องนั้น เริ่มนับ 1 จาก MSB ของ byte แรก หลายบรรทัด BYTE คือ array เดียวต่อกัน ไม่ใช่ตารางหลายชุด IP/P เป็นการเรียงใหม่; E อนุญาตตำแหน่งซ้ำจึงขยายได้
046 sBoxTable BYTE 14,4,13,1,2,15,11,8,3,10,6,12,5,9,0,7
047 BYTE 0,15,7,4,14,2,13,1,10,6,12,11,9,5,3,8
048 BYTE 4,1,14,8,13,6,2,11,15,12,9,7,3,10,5,0
049 BYTE 15,12,8,2,4,9,1,7,5,11,3,14,10,0,6,13
050 BYTE 15,1,8,14,6,11,3,4,9,7,2,13,12,0,5,10
051 BYTE 3,13,4,7,15,2,8,14,12,0,1,10,6,9,11,5
052 BYTE 0,14,7,11,10,4,13,1,5,8,12,6,9,3,2,15
053 BYTE 13,8,10,1,3,15,4,2,11,6,7,12,0,5,14,9
054 BYTE 10,0,9,14,6,3,15,5,1,13,12,7,11,4,2,8
055 BYTE 13,7,0,9,3,4,6,10,2,8,5,14,12,11,15,1
056 BYTE 13,6,4,9,8,15,3,0,11,1,2,12,5,10,14,7
057 BYTE 1,10,13,0,6,9,8,7,4,15,14,3,11,5,2,12
058 BYTE 7,13,14,3,0,6,9,10,1,2,8,5,11,12,4,15
059 BYTE 13,8,11,5,6,15,0,3,4,7,2,12,1,10,14,9
060 BYTE 10,6,9,0,12,11,7,13,15,1,3,14,5,2,8,4
061 BYTE 3,15,0,6,10,1,13,8,9,4,5,11,12,7,2,14
062 BYTE 2,12,4,1,7,10,11,6,8,5,3,15,13,0,14,9
063 BYTE 14,11,2,12,4,7,13,1,5,0,15,10,3,9,8,6
064 BYTE 4,2,1,11,10,13,7,8,15,9,12,5,6,3,0,14
065 BYTE 11,8,12,7,1,14,2,13,6,15,0,9,10,4,5,3
066 BYTE 12,1,10,15,9,2,6,8,0,13,3,4,14,7,5,11
067 BYTE 10,15,4,2,7,12,9,5,6,1,13,14,0,11,3,8
068 BYTE 9,14,15,5,2,8,12,3,7,0,4,10,1,13,11,6
069 BYTE 4,3,2,12,9,5,15,10,11,14,1,7,6,0,8,13
070 BYTE 4,11,2,14,15,0,8,13,3,12,9,7,5,10,6,1
071 BYTE 13,0,11,7,4,9,1,10,14,3,5,12,2,15,8,6
072 BYTE 1,4,11,13,12,3,7,14,10,15,6,8,0,5,9,2
073 BYTE 6,11,13,8,1,4,10,7,9,5,0,15,14,2,3,12
074 BYTE 13,2,8,4,6,15,11,1,10,9,3,14,5,0,12,7
075 BYTE 1,15,13,8,10,3,7,4,12,5,6,11,0,14,9,2
076 BYTE 7,11,4,1,9,12,14,2,0,6,10,13,15,3,5,8
077 BYTE 2,1,14,7,4,10,8,13,15,12,9,0,3,5,6,11S-box เก็บเป็น byte array เส้นตรง 512 bytes: 8 boxes × 4 rows × 16 columns หนึ่ง box ใช้ 4 บรรทัด บรรทัดละ 16 ค่า ค่าแต่ละช่องอยู่ 0–15 แม้เก็บใน byte แต่มีสาระเพียง 4 bits ไม่ต้องท่องตัวเลขทั้งหมด ต้องรู้ index = box*64 + row*16 + column
079 .data?
080 permutationBlock BYTE 8 DUP(?)
081 rightBytes BYTE 4 DUP(?)
082 expandedBuffer BYTE 6 DUP(?)
083 pInputBytes BYTE 4 DUP(?)
084 pOutputBytes BYTE 4 DUP(?)
085 ecbBlock BYTE 8 DUP(?)
086 roundLeft DWORD ?
087 roundRight DWORD ?
088 sboxCombined DWORD ?
089
090 .codepermutationBlock เป็น scratch block 8 bytes; rightBytes 4 bytes แทน R แบบ big-endian; expandedBuffer 6 bytes; pInputBytes/pOutputBytes อย่างละ 4; ecbBlock 8 bytes; roundLeft/roundRight/sboxCombined เป็น DWORD globals สิ่งเหล่านี้ใช้ซ้ำ ไม่ใช่ตัวแปร local ต่อ call .data? ไม่ระบุ initializer ใน source; Windows loader จัดพื้นที่เริ่มเป็นศูนย์ แต่ code ยังต้อง reset เพื่อกันข้อมูล call ก่อนค้างอยู่
| Return ผ่าน EAX | ฟังก์ชัน |
|---|---|
| Status: 0 สำเร็จ, 13/14/15/16 error | block APIs, buffer APIs, IP wrappers, ExpandRightHalf, PermuteBits, RunFeistelRounds, WriteBE32 |
| ข้อมูล 32 bits | ReadBE32, FeistelFunction, ApplySBoxes, ApplyPPermutation |
| ค่าหนึ่ง bit: 0/1 | GetFipsBitC |
อย่าใช้ test eax,eax เพื่อบอกว่า helpers ที่คืนข้อมูลล้มเหลว: ผล f เป็นศูนย์ได้ตามปกติ ApplySBoxes เมื่อ input NULL คืน 0 ซึ่งแยกจากผลข้อมูล 0 ไม่ได้ นี่เป็นข้อจำกัดของ internal interface ปัจจุบัน
4. EncryptDESBlock — เข้ารหัสหนึ่งก้อน
Contract: รับ input 8 bytes, output อย่างน้อย 8 bytes, subkeys 96 bytes ไม่เติม padding ไม่สร้าง subkeys ไม่เปิดไฟล์ และไม่วนหลาย blocks การคืน status สำเร็จหมายถึง output ถูกเขียนแล้ว
091 EncryptDESBlock PROC USES ebx ecx edx esi edi,inputPtr:PTR BYTE,outputPtr:PTR BYTE,subkeysPtr:PTR BYTE
092 mov esi,inputPtr
093 mov edi,outputPtr
094 test esi,esi
095 jz edb_bad
096 test edi,edi
097 jz edb_bad
098 mov eax,subkeysPtr
099 test eax,eax
100 jz edb_badหัว PROC บอก parameters และ registers ที่รักษาไว้ ESI เป็น input address; EDI เป็น output address test+jz ปฏิเสธ NULL ทั้ง input/output/subkeys แล้วไป edb_bad Pointer check นี้ไม่ตรวจ length หรือ memory access rights
101 INVOKE ApplyInitialPermutation,esi,ADDR permutationBlock
102 test eax,eax
103 jnz edb_done
104 INVOKE RunFeistelRounds,ADDR permutationBlock,subkeysPtr,MODE_ENCRYPT
105 test eax,eax
106 jnz edb_done
107 INVOKE ApplyInversePermutation,ADDR permutationBlock,ediIP เขียนข้อมูลจัดตำแหน่งลง permutationBlock จากนั้น test status ก่อนทำ rounds RunFeistelRounds ใช้ MODE_ENCRYPT และแก้ scratch block ให้เป็น R16|L16 สุดท้าย inverse IP อ่าน scratch แล้วเขียน ciphertext ลง output ของ caller Status จาก inverse IP อยู่ใน EAX สำหรับคืนต่อ
108 edb_done:
109 ret
110 edb_bad:
111 mov eax,ERR_PARAM
112 ret
113 EncryptDESBlock ENDPเส้นทางปกติและ error จาก helper มารวมที่ edb_done แล้ว ret ถ้า pointer NULL ตั้ง EAX=16 ที่ edb_bad แล้ว ret MASM คืน registers ตาม USES ก่อนกลับ caller; ไม่ทำ outputLength เพราะหนึ่ง block ขนาดคงที่
5. DecryptDESBlock — ใช้ core เดิมกับ key order ย้อนกลับ
115 DecryptDESBlock PROC USES ebx ecx edx esi edi,inputPtr:PTR BYTE,outputPtr:PTR BYTE,subkeysPtr:PTR BYTE
116 mov esi,inputPtr
117 mov edi,outputPtr
118 test esi,esi
119 jz ddb_bad
120 test edi,edi
121 jz ddb_bad
122 mov eax,subkeysPtr
123 test eax,eax
124 jz ddb_badตรวจ input/output/subkeys เหมือน encrypt; input ตอนนี้เป็น ciphertext 8 bytes ไม่มีการตรวจ PKCS#7 ที่ระดับ block เพราะ padding มีความหมายเฉพาะท้าย buffer ทั้งหมด
125 INVOKE ApplyInitialPermutation,esi,ADDR permutationBlock
126 test eax,eax
127 jnz ddb_done
128 INVOKE RunFeistelRounds,ADDR permutationBlock,subkeysPtr,MODE_DECRYPT
129 test eax,eax
130 jnz ddb_done
131 INVOKE ApplyInversePermutation,ADDR permutationBlock,ediทำ IP เหมือน encrypt แต่ส่ง MODE_DECRYPT เข้า RunFeistelRounds เพื่อใช้ K16→K1 จากนั้นทำ inverse IP เหมือนเดิม ไม่เรียก inverse S-box และไม่สร้าง subkeys แบบใหม่
132 ddb_done:
133 ret
134 ddb_bad:
135 mov eax,ERR_PARAM
136 ret
137 DecryptDESBlock ENDPคืน status เหมือน EncryptDESBlock; NULL กลับ 16 Caller ระดับ ECB ตรวจ EAX และหยุดเมื่อมี error
เหตุผลคณิตศาสตร์: L_i=R_(i-1), R_i=L_(i-1) XOR f(R_(i-1),K_i) ดังนั้น R_(i-1)=L_i และ L_(i-1)=R_i XOR f(L_i,K_i) เพราะ X XOR Y XOR Y = X เราคำนวณ f เดิมซ้ำ ไม่จำเป็นต้องกลับ f ที่ย่อข้อมูลผ่าน S-box
เริ่ม undo จากคู่ที่สลับแล้ว (R_i, L_i):
Feistel ด้วย K_i → (L_i, R_i XOR f(L_i,K_i))
= (R_(i-1), L_(i-1))
ย้อน K16→K1 ได้คู่ (R0,L0)
การ swap ท้าย core จัดกลับเป็น (L0,R0) แล้ว inverse IP คืน plaintextIP ตอนเริ่ม decrypt ย้อน inverse IP ตอนจบ encrypt ส่วน inverse IP ตอนจบ decrypt ย้อน IP ตอนเริ่ม encrypt ถ้าข้าม inverse IP ท้าย encrypt ต้องข้าม IP ตอนเริ่ม decrypt คู่กัน แต่ไม่ได้แปลว่าข้าม inverse IP ท้าย decrypt ได้ ถ้าตัดทั้งคู่จากทั้งสองฝั่งยังสร้าง cipher ที่ย้อนกลับได้แต่ไม่ใช่ DES มาตรฐาน
6. Core 16 rounds และเครื่อง f แบบเทียบโค้ด
6.1 ApplyInitialPermutation / ApplyInversePermutation
276 ApplyInitialPermutation PROC USES ebx ecx edx esi edi,inputPtr:PTR BYTE,blockPtr:PTR BYTE
277 INVOKE PermuteBits,inputPtr,blockPtr,ADDR ipTable,64
278 ret
279 ApplyInitialPermutation ENDP
280 ApplyInversePermutation PROC USES ebx ecx edx esi edi,blockPtr:PTR BYTE,outputPtr:PTR BYTE
281 INVOKE PermuteBits,blockPtr,outputPtr,ADDR inverseIpTable,64
282 ret
283 ApplyInversePermutation ENDPสอง wrappers ใช้ PermuteBits เครื่องเดียวกัน จำนวน output 64 bits ต่างกันเฉพาะตาราง ipTable หรือ inverseIpTable Input/output ต้องเป็นพื้นที่แยกกันสำหรับ PermuteBits เพราะ helper ล้างปลายทางก่อนอ่าน input; wrappers ที่ถูกเรียกจาก block API ใช้ scratch แยกจึงไม่ล้าง source ระหว่างการ permute
IP: output bit 1=input bit 58 ตามตาราง; inverse IP: output bit 58=input bit 1 จึงย้อนการจัดตำแหน่งได้ IP/FP ไม่ผสม key และไม่ได้เพิ่มความแข็งแรงด้วยตัวมันเอง เหตุผลเชิง hardware ของโครงสร้างเดิมมีหลักฐานในสิทธิบัตร IBM; ไม่ควรกล่าวว่า IP ใช้ต้าน differential cryptanalysis แบบเดียวกับ P
6.2 RunFeistelRounds
285 RunFeistelRounds PROC USES ebx esi edi ecx edx,blockPtr:PTR BYTE,subkeysPtr:PTR BYTE,cipherMode:DWORD
286 mov esi,blockPtr
287 mov edi,subkeysPtr
288 test esi,esi
289 jz rfr_bad
290 test edi,edi
291 jz rfr_bad
292 INVOKE ReadBE32,esi
293 mov roundLeft,eax
294 add esi,4
295 INVOKE ReadBE32,esi
296 mov roundRight,eax
297 xor ebx,ebxESI ชี้ scratch block EDI ชี้ subkeys; ตรวจ NULL ReadBE32 อ่าน 4 bytes แรกเป็น L0 เก็บใน roundLeft แล้วขยับ ESI อีก 4 bytes อ่าน R0 เก็บใน roundRight EBX=0 เป็น round index แบบ zero-based ไม่ใช่จำนวน bits ที่ประมวลผล
298 rfr_loop:
299 cmp ebx,16
300 jae rfr_finish
301 mov eax,cipherMode
302 cmp eax,MODE_DECRYPT
303 je rfr_reverse_key
304 mov eax,ebx
305 jmp rfr_key_ready
306 rfr_reverse_key:
307 mov eax,15
308 sub eax,ebx
309 rfr_key_ready:
310 imul eax,6
311 add eax,ediวนจน EBX>=16 Encrypt key index=EBX: 0..15; decrypt index=15-EBX: 15..0 คูณ 6 แปลง index เป็น byte offset แล้วบวก EDI ให้ได้ pointer ไป key ของรอบนั้น K1 อยู่ offset 0, K16 อยู่ offset 90 ฟังก์ชันไม่ได้ตรวจ mode เป็น 0/1 ครบทุกค่า; ค่าใดที่ไม่เท่ากับ 1 จะเดินเส้น encrypt
312 INVOKE FeistelFunction,roundRight,eax
313 mov edx,roundLeft
314 xor edx,eax
315 mov eax,roundRight
316 mov roundLeft,eax
317 mov roundRight,edx
318 inc ebx
319 jmp rfr_loopEAX ก่อน INVOKE เป็น subkey pointer; หลัง INVOKE เป็นค่า f 32 bits นำ L เก่ามา EDX แล้ว XOR กับ f → R ใหม่ จากนั้นเอา R เก่าใส่ L ใหม่ และเก็บ EDX ใน R ใหม่ ต้องรักษาค่าเก่าก่อนเขียนทับเพื่อไม่ให้สูตรใช้ข้อมูลผิด EBX เพิ่มหนึ่ง แล้ววนรอบต่อ ทั้ง L และ R ยังคง 32 bits ในทุกรอบ
320 rfr_finish:
321 mov esi,blockPtr
322 INVOKE WriteBE32,roundRight,esi
323 add esi,4
324 INVOKE WriteBE32,roundLeft,esi
325 xor eax,eax
326 ret
327 rfr_bad:
328 mov eax,ERR_PARAM
329 ret
330 RunFeistelRounds ENDPเขียน roundRight ลง 4 bytes แรก แล้ว roundLeft ลง 4 bytes หลัง จึงได้ R16|L16; ไม่กลับลำดับ bits ในแต่ละครึ่ง xor eax,eax คืน status สำเร็จ ถ้า NULL คืน 16 การ swap อยู่ตรงนี้ ไม่ได้อยู่ใน ApplyInversePermutation
6.3 FeistelFunction
332 FeistelFunction PROC USES ebx esi ecx edx,rightHalfValue:DWORD,subkeyPtr:PTR BYTE
333 INVOKE ExpandRightHalf,rightHalfValue,ADDR expandedBuffer
334 test eax,eax
335 jnz ff_done
336 mov esi,subkeyPtr
337 xor ecx,ecxรับ R เป็นค่าตัวเลข DWORD และ subkey เป็น pointer ส่ง R ไป ExpandRightHalf ให้เขียน expandedBuffer 6 bytes ตรวจ status แล้วเตรียม ESI ชี้ subkey ECX เป็น byte index Function นี้คืนข้อมูล f ไม่ใช่ status บนเส้นทางปกติ
338 ff_xor:
339 cmp ecx,6
340 jae ff_sboxes
341 mov al,[esi+ecx]
342 xor expandedBuffer[ecx],al
343 inc ecx
344 jmp ff_xorXOR ทีละ byte 6 รอบ mov al,[esi+ecx] อ่านหนึ่ง byte จาก K_i; xor expandedBuffer[ecx],al ผสมกับ byte ตำแหน่งเดียวกัน 6 bytes × 8 bits = 48 bits ผลยังอยู่ใน expandedBuffer ไม่ได้เอาไฟล์ทั้งก้อน XOR กับ key หลัก
345 ff_sboxes:
346 INVOKE ApplySBoxes,ADDR expandedBuffer
347 INVOKE ApplyPPermutation,eax
348 ff_done:
349 ret
350 FeistelFunction ENDPApplySBoxes ย่อผล 48 เป็น DWORD 32 bits ใน EAX แล้ว ApplyPPermutation รับค่านี้และคืนผล f 32 bits เส้นทางสำเร็จ ret ส่งข้อมูลให้ RunFeistelRounds อย่า test EAX แล้วคิดว่าค่า f=0 คือ error ขั้นนี้ไม่ตรวจ subkeyPtr NULL ด้วยตัวเอง แต่ public block entry ตรวจมาก่อน
6.4 ExpandRightHalf
352 ExpandRightHalf PROC USES ebx ecx edx esi edi,rightHalfValue:DWORD,expandedPtr:PTR BYTE
353 INVOKE WriteBE32,rightHalfValue,ADDR rightBytes
354 INVOKE PermuteBits,ADDR rightBytes,expandedPtr,ADDR expansionTable,48
355 ret
356 ExpandRightHalf ENDPWriteBE32 แปลงค่า R ใน register เป็น byte array 4 bytes ที่ rightBytes แล้ว PermuteBits ใช้ expansionTable เลือก 48 bits ลง expandedPtr ตารางเริ่ม 32,1,2,3,4,5,4,5,... จึงเลือก bits บางตำแหน่งซ้ำ ไม่ใช่เติม 16 ศูนย์ ไม่สุ่ม และไม่เปลี่ยนค่า bit ที่หยิบ
6.5 ApplySBoxes — อ่านละเอียดทีละช่วง
48 bits แบ่งเป็น 8 กลุ่ม กลุ่มละ 6 bits กลุ่มที่ 1 ใช้ S1 กลุ่มที่ 2 ใช้ S2 จนครบ S8 แต่ละกลุ่มได้ผล 4 bits จึงรวม 32 bits S-box แต่ละตัวเป็นตารางคนละค่า แม้ input เหมือนกันใช้คนละ box ก็อาจได้ output ต่างกัน
358 ApplySBoxes PROC USES ebx esi edi ecx edx,expandedPtr:PTR BYTE
359 mov esi,expandedPtr
360 test esi,esi
361 jz asb_bad
362 mov sboxCombined,0
363 xor ebx,ebx
364 asb_box:
365 cmp ebx,8
366 jae asb_done
367 xor edx,edx ; six-bit value
368 xor ecx,ecxESI ชี้ expanded buffer ตรวจ NULL แล้ว reset sboxCombined=0 และ EBX=0 เป็น box index แต่ละ box reset EDX=0 สำหรับรวบ 6 bits และ ECX=0 เป็น index ภายในกลุ่ม
369 asb_six_bits:
370 cmp ecx,6
371 jae asb_lookup
372 shl edx,1
373 mov eax,ebx
374 imul eax,6
375 add eax,ecx
376 inc eax
377 INVOKE GetFipsBitC,esi,eax
378 or edx,eax
379 inc ecx
380 jmp asb_six_bitsประกอบ 6 bits ทีละตัว: shl edx,1 เปิดช่องล่างให้ bit ใหม่ ตำแหน่งต้นทาง = boxIndex*6 + bitIndex + 1; +1 เพราะ GetFipsBitC ใช้ตำแหน่งแบบ 1-based helper คืน bit ใน EAX และรักษา EDX/ECX จึง or edx,eax ต่อ bit ได้โดยค่าเดิมไม่หาย ครบแล้ว EDX เป็นค่า 0..63
381 asb_lookup:
382 mov eax,edx
383 and eax,1
384 mov ecx,edx
385 and ecx,20h
386 shr ecx,4
387 or eax,ecx ; row
388 shl eax,4row ใช้ bit แรกกับสุดท้าย: and eax,1 เก็บ bit สุดท้าย; and ecx,20h เลือก bit แรกของ 6-bit value (ค่า 32); shr ecx,4 ย้ายให้เป็นค่า 2; or รวมเป็น row 0..3 แล้ว shl eax,4 คูณ row ด้วย 16 เพื่อหา row offset ไม่ใช่ลบ bits ทิ้งเพื่อลดขนาด
389 mov ecx,edx
390 shr ecx,1
391 and ecx,0Fh ; column
392 add eax,ecx
393 mov ecx,ebx
394 shl ecx,6
395 add eax,ecx
396 movzx eax,BYTE PTR sBoxTable[eax]column ใช้ 4 bits กลาง: เอา EDX >>1 แล้ว mask 0Fh ให้เหลือ 0..15 บวกกับ row*16 ต่อด้วย boxIndex*64 จาก shl ecx,6 ได้ index ทั้งตาราง 0..511 movzx อ่านคำตอบจาก sBoxTable เป็นค่า 0..15
397 mov edx,sboxCombined
398 shl edx,4
399 or edx,eax
400 mov sboxCombined,edx
401 inc ebx
402 jmp asb_box
403 asb_done:
404 mov eax,sboxCombined
405 ret
406 asb_bad:
407 xor eax,eax
408 ret
409 ApplySBoxes ENDPเอาผลเก่าที่รวมแล้วเลื่อนซ้าย 4 bits แล้ว OR คำตอบใหม่ต่อท้าย เช่น 5 แล้ว C ได้ 5Ch ทำครบ 8 กล่องเป็น 32 bits คืนใน EAX ถ้า expandedPtr=NULL คืน 0; internal return นี้ไม่มีช่องแยก error และข้อมูล 0
Input S1 = 011000
row = bits แรกและสุดท้าย = 00₂ = 0
column = 4 bits กลาง = 1100₂ = 12
index = 0*64 + 0*16 + 12 = 12
sBoxTable[12] = 5 = 0101₂
Input 101011: row=11₂=3, column=0101₂=5
สำหรับ S1 index=3*16+5=53; คำตอบ S1 คือ 9 = 1001₂6.6 ApplyPPermutation
411 ApplyPPermutation PROC USES ebx ecx edx esi edi,inputValue:DWORD
412 INVOKE WriteBE32,inputValue,ADDR pInputBytes
413 INVOKE PermuteBits,ADDR pInputBytes,ADDR pOutputBytes,ADDR pTable,32
414 INVOKE ReadBE32,ADDR pOutputBytes
415 ret
416 ApplyPPermutation ENDPS-box คืน DWORD 32 bits WriteBE32 ทำให้เป็น pInputBytes แล้ว PermuteBits ใช้ pTable 32 ช่องไป pOutputBytes ReadBE32 ประกอบกลับเป็นตัวเลขใน EAX เพื่อให้ caller XOR กับ L ได้ P ไม่ลดจำนวน bits ไม่ใช้ key และไม่ใช่ inverse IP
| ผลจาก S1 เดิม | หลัง P อยู่ output ช่อง |
|---|---|
| input bit 1 | 9 |
| input bit 2 | 17 |
| input bit 3 | 23 |
| input bit 4 | 31 |
ตัวอย่าง S1 ให้ 1010: bits ที่เคยติดกันในช่อง 1–4 ไปอยู่ 9,17,23,31 ตามลำดับ หลัง XOR L และ Expansion ในรอบถัดไป bits เหล่านี้ป้อนหลาย S-box ตาราง P ร่วมกับ E ถูกเลือกให้ outputs 4 bits ของ box หนึ่งมีอิทธิพลถึง 6 boxes รอบถัดไป เป็นการกระจาย (diffusion) เพื่อช่วยต้านการติดตามความแตกต่างข้าม rounds ไม่ใช่แค่ทำเพราะมาตรฐานโดยไม่มีเหตุผล
7. EncryptBufferECB — ไฟล์หลาย blocks และ padding
ชั้นนี้รับทั้ง buffer จาก A แล้วจัดข้อมูลครั้งละ 8 bytesให้ block core Key schedule สร้างครั้งเดียวจาก A/B ก่อนเข้า C; ไม่ต้องพิมพ์ KEYGEN ก่อน ENCRYPT และไม่สร้าง subkeys ใหม่ต่อ block Output ciphertext เป็นผลทุก block ต่อกันไม่มี IV/chaining ใน ECB
139 EncryptBufferECB PROC USES ebx esi edi ecx edx,inputPtr:PTR BYTE,inputLength:DWORD,outputPtr:PTR BYTE,outputCapacity:DWORD,subkeysPtr:PTR BYTE,outputLengthPtr:PTR DWORD
140 mov esi,inputPtr
141 mov edi,outputPtr
142 mov edx,outputLengthPtr
143 test esi,esi
144 jz ebe_bad
145 test edi,edi
146 jz ebe_bad
147 test edx,edx
148 jz ebe_bad
149 mov DWORD PTR [edx],0ESI/EDI เป็น input/output base EDX ชี้ตัวแปร outputLength ตรวจ NULL สาม pointer แล้วเขียน *outputLengthPtr=0 ก่อนทำงาน เมื่อเกิด error ภายหลัง length จึงยังเป็น 0 แต่ถ้า outputLengthPtr เป็น pointer ผิดแม้ไม่ใช่ NULL ก็ยังเขียนผิด memory ได้ ไม่ได้ตรวจ subkeysPtr ตรงนี้ แต่ block entry จะตรวจ
150 mov eax,inputLength
151 xor edx,edx
152 mov ecx,8
153 div ecx
154 inc eax
155 shl eax,3
156 mov ecx,eax ; padded length
157 cmp outputCapacity,ecx
158 jb ebe_small
159 mov ebx,ecx
160 sub ebx,inputLength ; padding byteสูตรความยาว = (floor(inputLength/8)+1)*8: div ecx ให้จำนวนเต็ม blocks; inc เพิ่มหนึ่ง; shl 3 คูณ 8 แม้ input ลงตัว 8 ก็เติม block เต็ม ตรวจ unsigned outputCapacity >= paddedLength แล้ว EBX=paddedLength-inputLength เป็นค่า padding 1..8 ภายใต้ขอบเขตไฟล์ที่ A จำกัด
161 xor edx,edx ; absolute offset
162 ebe_block_loop:
163 cmp edx,ecx
164 jae ebe_done_blocks
165 push ecx
166 xor ecx,ecxEDX=absolute byte offset ของ block เริ่ม 0; ECX=paddedLength ลูปจบเมื่อ offset ถึง paddedLength push ECX เก็บความยาวทั้งหมดไว้แล้วใช้ ECX ใหม่เป็นตำแหน่งภายใน block 0..7
167 ebe_fill:
168 cmp ecx,8
169 jae ebe_encrypt
170 mov eax,edx
171 add eax,ecx
172 cmp eax,inputLength
173 jae ebe_pad
174 mov al,[esi+eax]
175 jmp ebe_store
176 ebe_pad:
177 mov eax,ebx
178 ebe_store:
179 mov ecbBlock[ecx],al
180 inc ecx
181 jmp ebe_fillตำแหน่งจริง = EDX+ECX ถ้ายังน้อยกว่า inputLength อ่าน byte เดิมจาก ESI ถ้าถึง/เกินแล้วใช้ค่า padding จาก EBX เขียนหนึ่ง byte ลง ecbBlock[ECX] แล้วเพิ่ม ECX เป็นการประกอบ block เต็มใน scratch ไม่ได้เติมข้อมูลลง input buffer และไม่แก้ไฟล์ต้นฉบับ
182 ebe_encrypt:
183 push edx
184 add edi,edx
185 INVOKE EncryptDESBlock,ADDR ecbBlock,edi,subkeysPtr
186 sub edi,edx
187 pop edx
188 pop ecx
189 test eax,eax
190 jnz ebe_return
191 add edx,8
192 jmp ebe_block_loopเก็บ EDX แล้วเลื่อน EDI จาก output base ไปตำแหน่ง block นี้ เรียก EncryptDESBlock ด้วย ecbBlock และ output address นั้น คืน EDI ด้วย sub edi,edx (block callee รักษา EDX ตาม USES) จากนั้น pop offset และ paddedLength ตรวจ status; ถ้าสำเร็จ offset +=8 แล้ววน ถ้ามี error คืนโดยความยาวผลยัง 0 แต่อาจมีบาง blocks ถูกเขียนแล้ว
193 ebe_done_blocks:
194 mov eax,outputLengthPtr
195 mov [eax],ecx
196 xor eax,eax
197 ebe_return:
198 ret
199 ebe_small:
200 mov eax,ERR_BUFFER
201 ret
202 ebe_bad:
203 mov eax,ERR_PARAM
204 ret
205 EncryptBufferECB ENDPเมื่อทุก block สำเร็จเขียน paddedLength ผ่าน outputLengthPtr และคืน EAX=0 ถ้า output จุไม่พอคืน 13 ถ้า parameter NULL ที่ตรวจไว้คืน 16 ไม่เขียนไฟล์เองและไม่พิมพ์ข้อความเอง
| inputLength | ค่า padding | จำนวน padding bytes | outputLength | blocks |
|---|---|---|---|---|
| 0 | 08 | 8 | 8 | 1 |
| 1 | 07 | 7 | 8 | 1 |
| 5 | 03 | 3 | 8 | 1 |
| 6 | 02 | 2 | 8 | 1 |
| 8 | 08 | 8 | 16 | 2 |
| 16 | 08 | 8 | 24 | 3 |
| 24 | 08 | 8 | 32 | 4 |
| 25 | 07 | 7 | 32 | 4 |
| 1048576 | 08 | 8 | 1048584 | 131073 |
ข้อมูลลงตัวต้องเติม block เต็มเพื่อให้ท้ายข้อมูลจริงไม่ถูกเข้าใจเป็น padding เช่น input 05 05 05 05 05 03 03 03 เป็นข้อมูลจริง 8 bytes จะเติม 08 แปดตัวอีก block; ตอน decrypt ตัดเฉพาะ block 08 นั้น ส่วน input 08 ตัวเดียวเติม 07 เจ็ดตัวแล้วตัดเพียงเจ็ดตัว ไม่ตรวจและตัดซ้ำจาก 08 ที่เหลือ
8. DecryptBufferECB — คืนข้อมูลและตรวจ padding
207 DecryptBufferECB PROC USES ebx esi edi ecx edx,inputPtr:PTR BYTE,inputLength:DWORD,outputPtr:PTR BYTE,outputCapacity:DWORD,subkeysPtr:PTR BYTE,outputLengthPtr:PTR DWORD
208 mov esi,inputPtr
209 mov edi,outputPtr
210 mov edx,outputLengthPtr
211 test esi,esi
212 jz dbe_bad
213 test edi,edi
214 jz dbe_bad
215 test edx,edx
216 jz dbe_bad
217 mov DWORD PTR [edx],0เตรียม input/output/length pointer และ reset outputLength เป็น 0 เช่น encrypt ขนาด ciphertext ต้องถูกตรวจเพิ่มเติม ไม่ใช่แค่มี pointer
218 mov eax,inputLength
219 test eax,eax
220 jz dbe_length
221 test eax,7
222 jnz dbe_length
223 cmp outputCapacity,eax
224 jb dbe_small
225 xor ebx,ebxปฏิเสธ inputLength=0 และ test eax,7 ตรวจ 3 bits ล่าง: หากไม่เป็นศูนย์แปลว่าไม่ลงตัว 8 เช่น 7→111₂, 8→1000₂ ตรวจ capacity ต้องจุ ciphertext ทั้งหมด ไม่ใช่แค่ plaintext หลังตัด padding เพราะถอดทุก bytes ก่อนตัด
226 dbe_loop:
227 cmp ebx,inputLength
228 jae dbe_padding
229 mov eax,esi
230 add eax,ebx
231 mov edx,edi
232 add edx,ebx
233 INVOKE DecryptDESBlock,eax,edx,subkeysPtr
234 test eax,eax
235 jnz dbe_return
236 add ebx,8
237 jmp dbe_loopEBX เป็น offset เริ่ม 0 สร้าง input address = ESI+offset และ output address = EDI+offset ส่งให้ DecryptDESBlock ซึ่งทำ core ย้อน key order ตรวจ status แล้ว offset +=8; ไม่เติม padding ใหม่ ไม่เชื่อม ciphertext ระหว่าง blocks
238 dbe_padding:
239 mov ecx,inputLength
240 movzx eax,BYTE PTR [edi+ecx-1]
241 cmp eax,1
242 jb dbe_pad_error
243 cmp eax,8
244 ja dbe_pad_error
245 mov edx,eax ; padding count
246 mov ebx,ecx
247 sub ebx,edx ; plaintext lengthหลังถอดครบ อ่าน byte ท้าย output ที่ [edi+inputLength-1] ด้วย movzx ตรวจ pad ในช่วง 1..8 EDX=padCount และ EBX=inputLength-padCount เป็น plaintextLength ที่คาด ยังไม่ประกาศสำเร็จจนตรวจ bytes ท้ายทั้งหมด
248 dbe_pad_loop:
249 cmp edx,0
250 je dbe_pad_ok
251 dec ecx
252 cmp BYTE PTR [edi+ecx],al
253 jne dbe_pad_error
254 dec edx
255 jmp dbe_pad_loopวนจากท้ายย้อนหลัง padCount ครั้ง: dec ecx ไป byte ที่จะตรวจ cmp กับ AL ซึ่งเก็บ pad value ทุกตัวต้องเท่ากัน ถ้า byte ใดผิดคืน ERR_PADDING ถ้าถูกลด EDX จนครบ การตรวจครั้งเดียวที่ท้ายทั้ง buffer ไม่ใช่ตรวจท้ายทุก block
256 dbe_pad_ok:
257 mov eax,outputLengthPtr
258 mov [eax],ebx
259 xor eax,eax
260 dbe_return:
261 ret
262 dbe_length:
263 mov eax,ERR_CIPHER_LENGTH
264 ret
265 dbe_small:
266 mov eax,ERR_BUFFER
267 ret
268 dbe_pad_error:
269 mov eax,ERR_PADDING
270 ret
271 dbe_bad:
272 mov eax,ERR_PARAM
273 ret
274 DecryptBufferECB ENDPเมื่อ padding ผ่านเขียน plaintextLength ผ่าน outputLengthPtr และคืน 0 รหัส 14 คือ ciphertextLength ผิด, 13 capacity ไม่พอ, 15 padding ผิด, 16 NULL ที่ตรวจไว้ การตัด padding คือเปลี่ยนความยาวที่รายงาน ไม่ได้ลบ/zero bytes ท้าย output buffer A เขียนเพียง outputLength จึงไม่มี padding ในไฟล์ผล
ตัวอย่าง 41 42 43 44 45 46 02 02: pad=2 ตรวจสองตัวท้ายเป็น 02 ทั้งคู่แล้วรายงาน length=6 ส่วนท้าย 01 02: pad=2 แต่สองตัวไม่เท่ากันจึง reject Key ผิดอาจบังเอิญให้ padding ที่ถูกต้องได้; padding validation ไม่ใช่ authentication และไม่รับประกันว่าข้อมูลจริงถูกต้อง
9. Helpers ระดับ bit และ endian
9.1 PermuteBits — เครื่องเดียวใช้ IP, inverse IP, E และ P
418 PermuteBits PROC USES ebx esi edi ecx edx,sourcePtr:PTR BYTE,destPtr:PTR BYTE,tablePtr:PTR BYTE,bitCount:DWORD
419 mov esi,sourcePtr
420 mov edi,destPtr
421 test esi,esi
422 jz pb_bad
423 test edi,edi
424 jz pb_bad
425 mov eax,tablePtr
426 test eax,eax
427 jz pb_badรับ source/dest/table pointers และจำนวน output bits ตรวจ NULL ของ pointers ทั้งสาม อาศัย caller ส่ง table และความยาวถูกต้อง helper ไม่รู้ source capacity หรือ dest capacity และไม่ validate แต่ละตำแหน่งใน table
428 mov ecx,bitCount
429 add ecx,7
430 shr ecx,3
431 push edi
432 xor eax,eax
433 cld
434 rep stosb
435 pop edi
436 xor ebx,ebxจำนวน bytes ปลายทาง = ceil(bitCount/8) = (bitCount+7)>>3; เช่น 48→6 bytes เก็บฐาน EDI แล้ว cld/rep stosb เขียน AL=0 ทุกช่อง ก่อนคืนฐานและเริ่ม EBX=0 ต้องล้างเพราะ loop ถัดไป OR เฉพาะ bits ที่เป็น 1 ถ้าไม่ล้าง bits 1 จาก call ก่อนจะค้าง Input/dest ซ้อนกันจึงไม่ปลอดภัยสำหรับ helper นี้
437 pb_loop:
438 cmp ebx,bitCount
439 jae pb_ok
440 mov edx,tablePtr
441 movzx eax,BYTE PTR [edx+ebx]
442 INVOKE GetFipsBitC,esi,eax
443 test eax,eax
444 jz pb_nextEBX เป็น output bit index 0..bitCount-1 อ่าน table[EBX] เป็นตำแหน่ง source แบบ 1-based แล้ว GetFipsBitC คืนค่า bit ถ้าเป็น 0 ข้ามการ OR เพราะ dest ถูกล้างเป็น 0 ไว้แล้ว การเปลี่ยนตารางทำให้เครื่องเดียวใช้กับงาน 4 แบบได้
445 mov edx,ebx
446 shr edx,3
447 mov ecx,ebx
448 and ecx,7
449 mov al,80h
450 shr al,cl
451 or BYTE PTR [edi+edx],al
452 pb_next:
453 inc ebx
454 jmp pb_loopเมื่อ source bit=1 คำนวณ output byte index = EBX>>3 และตำแหน่งภายใน byte = EBX&7 สร้าง mask 80h>>ตำแหน่ง เช่น output bit index 0 ใช้ 80h, index 7 ใช้ 01h แล้ว OR byte ปลายทาง การเก็บแบบ MSB-first ตรงกับ FIPS EBX เพิ่มหนึ่งไม่ว่า bit=0 หรือ 1
455 pb_ok:
456 xor eax,eax
457 ret
458 pb_bad:
459 mov eax,ERR_PARAM
460 ret
461 PermuteBits ENDPจบครบ bitCount คืน 0; NULL คืน 16 ไม่มีการสุ่มค่า ไม่มี XOR key และไม่มีการกลับ bits จาก 0 เป็น 1 เว้นแต่ค่าที่หยิบจาก source เป็น 1 อยู่แล้ว
9.2 GetFipsBitC — ดึง bit จาก byte array
463 GetFipsBitC PROC USES ebx ecx edx esi,sourcePtr:PTR BYTE,bitPosition:DWORD
464 mov esi,sourcePtr
465 mov eax,bitPosition
466 dec eax
467 xor edx,edx
468 mov ebx,8
469 div ebx
470 movzx eax,BYTE PTR [esi+eax]
471 mov ecx,7
472 sub ecx,edx
473 shr eax,cl
474 and eax,1
475 ret
476 GetFipsBitC ENDPแปลงตำแหน่ง p ที่เริ่ม 1 เป็น p-1; หาร 8 ให้ quotient=byte index และ remainder=ตำแหน่งจากซ้ายใน byte อ่าน byte นั้นแล้วเลื่อนขวา 7-remainder ก่อน AND 1 คืน 0/1 ตัวอย่าง p=58: 57/8=7 เศษ1 จึงอ่าน byte7 แล้ว >>6 การล้าง EDX ก่อน div จำเป็น ไม่มี guard p=0/source NULL ใน helper นี้ caller ภายในต้องส่งค่าที่ valid
ตัวอย่าง input byte แรก = 41h = 01000001
p=1 → byteIndex=0, remainder=0 → shift=7 → bit=0
p=2 → byteIndex=0, remainder=1 → shift=6 → bit=1
p=8 → byteIndex=0, remainder=7 → shift=0 → bit=1
ตัวอย่าง key bit 57: key byte7=F1h=11110001
(57-1)/8 = quotient7 remainder0 → >>7 → 19.3 ReadBE32
478 ReadBE32 PROC USES ebx ecx edx esi edi,sourcePtr:PTR BYTE
479 mov esi,sourcePtr
480 movzx eax,BYTE PTR [esi]
481 shl eax,8
482 mov al,[esi+1]
483 shl eax,8
484 mov al,[esi+2]
485 shl eax,8
486 mov al,[esi+3]
487 ret
488 ReadBE32 ENDPอ่าน byte แรกด้วย movzx ให้บนเป็นศูนย์ แล้วเลื่อน EAX ซ้าย 8 ก่อนใส่ byte ถัดไปใน AL ทำสามครั้งครบ 4 bytes ตัวอย่าง 01 23 45 67: EAX 00000001→00000123→00012345→01234567 คืนค่าตัวเลข ไม่ใช่ status ถ้าใช้ mov eax,[esi] ตรงบน x86 little-endian จะได้ 67452301 จึงต้องแยก byte order จากค่าตัวเลขใน register
9.4 WriteBE32
490 WriteBE32 PROC USES ebx ecx edx esi edi,value:DWORD,destPtr:PTR BYTE
491 mov edi,destPtr
492 mov eax,value
493 mov [edi+3],al
494 shr eax,8
495 mov [edi+2],al
496 shr eax,8
497 mov [edi+1],al
498 shr eax,8
499 mov [edi],al
500 xor eax,eax
501 ret
502 WriteBE32 ENDP
503 ENDรับ DWORD value ใน EAX แล้วเขียน AL ที่ byte3 ก่อนเลื่อนขวา 8 เขียน byte2, byte1, byte0 ตามลำดับ ตัวอย่าง 01234567h ได้ memory 01 23 45 67 จบคืน status0 ไม่ได้คืนค่าที่เขียน Source value ไม่จำเป็นต้องอยู่ใน EAX ก่อน caller INVOKE เพราะ value เป็น stack parameter; mov eax,value ใน body จึงโหลดเอง END ปิด translation unit ไม่ใช่ runtime exit
10. จุดเชื่อมกับ Module A แบบโค้ดจริง
1128 HandleEncrypt PROC USES ebx ecx edx esi edi,commandPtr:PTR BYTE
1129 mov esi,commandPtr
1130 INVOKE PathsEqualCI,[esi].PARSED_COMMAND.inputPathPtr,[esi].PARSED_COMMAND.inputPathLength,[esi].PARSED_COMMAND.outputPathPtr,[esi].PARSED_COMMAND.outputPathLength
1131 cmp eax,1
1132 je he_same
1133 INVOKE ReadWholeFile,[esi].PARSED_COMMAND.inputPathPtr,ADDR fileInputBuffer,FILE_BUFFER_CAPACITY,ADDR moduleInputLength
1134 test eax,eax
1135 jnz he_doneHandleEncrypt ตรวจ path input/output แบบ case-insensitive ถ้าเท่ากันปฏิเสธ จากนั้นอ่าน input สูงสุด 1 MiB การเทียบชื่อ string ไม่รับรองว่า path ต่างรูปหมายถึงคนละไฟล์ เช่น relative/absolute หรือ hard links; จึงไม่ควรอ้างว่าป้องกัน alias ได้ทั้งหมด
1148 lea eax,[esi].PARSED_COMMAND.keyBytes
1149 INVOKE GenerateKeySchedule,eax,ADDR moduleSubkeys
1150 test eax,eax
1151 jnz he_done
1152 mov edx,OFFSET processingText
1153 call WriteString
1154 mov eax,moduleInputLength
1155 shr eax,3
1156 inc eax
1157 call WriteDec
1158 mov edx,OFFSET blocksText
1159 call WriteString
1160 INVOKE EncryptBufferECB,ADDR fileInputBuffer,moduleInputLength,ADDR fileOutputBuffer,(FILE_BUFFER_CAPACITY+8),ADDR moduleSubkeys,ADDR moduleOutputLength
1161 test eax,eax
1162 jnz he_done
1163 INVOKE WriteWholeFile,[esi].PARSED_COMMAND.outputPathPtr,ADDR fileOutputBuffer,moduleOutputLength
1164 test eax,eax
1165 jnz he_donelea คำนวณ address ของ keyBytes ใน PARSED_COMMAND ส่งให้ B สร้าง subkeys ใน moduleSubkeys A พิมพ์จำนวน blocks แล้วส่ง arguments 6 ตัวให้ EncryptBufferECB หาก status ไม่สำเร็จจะไม่ถึง WriteWholeFile ถ้าสำเร็จเขียนจำนวน moduleOutputLength bytes โค้ดช่วงที่ไม่ได้ยกเป็นข้อความแสดง progress ไม่ใช่ส่วน cipher
1200 lea eax,[esi].PARSED_COMMAND.keyBytes
1201 INVOKE GenerateKeySchedule,eax,ADDR moduleSubkeys
1202 test eax,eax
1203 jnz hd_done
1204 mov edx,OFFSET processingText
1205 call WriteString
1206 mov eax,moduleInputLength
1207 shr eax,3
1208 call WriteDec
1209 mov edx,OFFSET blocksText
1210 call WriteString
1211 INVOKE DecryptBufferECB,ADDR fileInputBuffer,moduleInputLength,ADDR fileOutputBuffer,(FILE_BUFFER_CAPACITY+8),ADDR moduleSubkeys,ADDR moduleOutputLength
1212 test eax,eax
1213 jnz hd_done
1214 INVOKE WriteWholeFile,[esi].PARSED_COMMAND.outputPathPtr,ADDR fileOutputBuffer,moduleOutputLength
1215 test eax,eax
1216 jnz hd_doneDecrypt ก็สร้าง subkeys จาก key หลักก่อนเรียก C โดย C เลือกย้อนลำดับเอง เมื่อ C คืน padding error A กระโดด hd_done ก่อนสร้าง/เขียน output แต่หากไฟล์ output เก่ามีอยู่ก่อนก็ไม่ได้ถูกลบเพียงเพราะคำสั่งนี้ fail
ขอบเขตจริง: ENCRYPT input ≤1,048,576 bytes; DECRYPT/DUMP/STATS อ่านได้ ≤1,048,584 bytes ตาม capacities ที่ A ส่ง ข้อจำกัดนี้เป็นของโปรแกรม ไม่ใช่ DES และไม่ใช่ 24 bytes ที่เห็นใน demo ฟังก์ชัน C public ไม่บังคับ 1 MiB เอง จึงต้องแยก contract ภายใน shell จากความปลอดภัยของ API ถ้าเรียกจาก caller ใหม่
11. Trace ที่ตรวจคำนวณแล้ว
ค่าต่อไปนี้คำนวณแยกจากตารางใน source ปัจจุบัน เพื่อตรวจเลขในเอกสาร ไม่ใช่การรัน Assembly ภายใต้ debugger Known-answer bytes ใน test_runner.asm ตรงกับผลคำนวณ และ demo ASCII ตรงกับ output ที่ผู้ใช้ส่งในแชต
Binary plaintext bytes: 01 23 45 67 89 AB CD EF
Key bytes: 13 34 57 79 9B BC DF F1
IP output: CC00CCFF F0AAF0AA
L0: CC00CCFF R0: F0AAF0AA
K1: 1B02EFFC7072
E(R0): 7A15557A1555
E(R0) XOR K1: 6117BA866527
S-box output: 5C82B597
P output / f: 234AA9BB
R1 = CC00CCFF XOR 234AA9BB = EF4A6544
L1 = F0AAF0AA
Final ciphertext: 85E813540F0AB40511.1 S-box รอบแรกครบแปดกล่อง
| Box | 6-bit input | row | column | ค่า | 4-bit output |
|---|---|---|---|---|---|
| S1 | 011000 | 0 | 12 | 5 | 0101 |
| S2 | 010001 | 1 | 8 | 12 | 1100 |
| S3 | 011110 | 0 | 15 | 8 | 1000 |
| S4 | 111010 | 2 | 13 | 2 | 0010 |
| S5 | 100001 | 3 | 0 | 11 | 1011 |
| S6 | 100110 | 2 | 3 | 5 | 0101 |
| S7 | 010100 | 0 | 10 | 9 | 1001 |
| S8 | 100111 | 3 | 3 | 7 | 0111 |
11.2 ทุก round ของ block binary ตัวอย่าง
| Round | L เก่า | R เก่า | Ki | f | L ใหม่ | R ใหม่ |
|---|---|---|---|---|---|---|
| 1 | CC00CCFF | F0AAF0AA | 1B02EFFC7072 | 234AA9BB | F0AAF0AA | EF4A6544 |
| 2 | F0AAF0AA | EF4A6544 | 79AED9DBC9E5 | 3CAB87A3 | EF4A6544 | CC017709 |
| 3 | EF4A6544 | CC017709 | 55FC8A42CF99 | 4D166EB0 | CC017709 | A25C0BF4 |
| 4 | CC017709 | A25C0BF4 | 72ADD6DB351D | BB23774C | A25C0BF4 | 77220045 |
| 5 | A25C0BF4 | 77220045 | 7CEC07EB53A8 | 2813ADC3 | 77220045 | 8A4FA637 |
| 6 | 77220045 | 8A4FA637 | 63A53E507B2F | 9E45CD2C | 8A4FA637 | E967CD69 |
| 7 | 8A4FA637 | E967CD69 | EC84B7F618BC | 8C051C27 | E967CD69 | 064ABA10 |
| 8 | E967CD69 | 064ABA10 | F78A3AC13BFB | 3C0E86F9 | 064ABA10 | D5694B90 |
| 9 | 064ABA10 | D5694B90 | E0DBEBEDE781 | 22367C6A | D5694B90 | 247CC67A |
| 10 | D5694B90 | 247CC67A | B1F347BA464F | 62BC9C22 | 247CC67A | B7D5D7B2 |
| 11 | 247CC67A | B7D5D7B2 | 215FD3DED386 | E104FA02 | B7D5D7B2 | C5783C78 |
| 12 | B7D5D7B2 | C5783C78 | 7571F59467E9 | C268CFEA | C5783C78 | 75BD1858 |
| 13 | C5783C78 | 75BD1858 | 97C5D1FABA41 | DDBB2922 | 75BD1858 | 18C3155A |
| 14 | 75BD1858 | 18C3155A | 5F43B7F2E73A | B7318E55 | 18C3155A | C28C960D |
| 15 | 18C3155A | C28C960D | BF918D3D3F0A | 5B81276E | C28C960D | 43423234 |
| 16 | C28C960D | 43423234 | CB3D8B0E17F5 | C8C04F98 | 43423234 | 0A4CD995 |
จบได้ L16=43423234 และ R16=0A4CD995 เขียน preoutput=0A4CD99543423234 แล้ว inverse IP ได้ 85E813540F0AB405 ถ้าทดสอบ block API ผลยาว 8 bytes แต่ buffer API กับ input8 จะเติม block padding อีก 8 bytes จึงให้ ciphertext16
11.3 Subkeys ที่ใช้ในตัวอย่าง
| Round | หมุน | C ของ key | D ของ key | Subkey |
|---|---|---|---|---|
| 1 | 1 | E19955F | AACCF1E | 1B02EFFC7072 |
| 2 | 1 | C332ABF | 5599E3D | 79AED9DBC9E5 |
| 3 | 2 | 0CCAAFF | 56678F5 | 55FC8A42CF99 |
| 4 | 2 | 332ABFC | 599E3D5 | 72ADD6DB351D |
| 5 | 2 | CCAAFF0 | 6678F55 | 7CEC07EB53A8 |
| 6 | 2 | 32ABFC3 | 99E3D55 | 63A53E507B2F |
| 7 | 2 | CAAFF0C | 678F556 | EC84B7F618BC |
| 8 | 2 | 2ABFC33 | 9E3D559 | F78A3AC13BFB |
| 9 | 1 | 557F866 | 3C7AAB3 | E0DBEBEDE781 |
| 10 | 2 | 55FE199 | F1EAACC | B1F347BA464F |
| 11 | 2 | 57F8665 | C7AAB33 | 215FD3DED386 |
| 12 | 2 | 5FE1995 | 1EAACCF | 7571F59467E9 |
| 13 | 2 | 7F86655 | 7AAB33C | 97C5D1FABA41 |
| 14 | 2 | FE19955 | EAACCF1 | 5F43B7F2E73A |
| 15 | 2 | F866557 | AAB33C7 | BF918D3D3F0A |
| 16 | 1 | F0CCAAF | 556678F | CB3D8B0E17F5 |
C/D ในตารางนี้เป็น halves ของ key schedule ใน B ไม่ใช่ชื่อ Module C/D การหมุนรวม 28 bits ทำให้ C16=C0 และ D16=D0 แต่ round subkeys ต่างกันตามสถานะที่เลือกในแต่ละรอบ
11.4 Demo ที่คุณรันจริงเป็น ASCII
ไฟล์ต้นฉบับ: ข้อความ 0123456789ABCDEF (16 bytes)
bytes: 30 31 32 33 34 35 36 37 38 39 41 42 43 44 45 46
Block1 ASCII 01234567 → 6C BD 22 85 8B CE DB 79
Block2 ASCII 89ABCDEF → AB A1 A7 BE 22 14 C5 42
Block3 padding 08×8 → FD F2 E1 74 49 29 22 F8
รวม ciphertext 24 bytes
ถอดแล้วรายงาน plaintextLength16 และได้ bytes ต้นฉบับครบอย่าสับสนข้อความ ASCII "0123456789ABCDEF" กับ binary bytes 01 23 45 67 89 AB CD EF: แบบแรก16 bytes แบบหลัง8 bytes จึงไม่ได้ ciphertextเดียวกัน ตัวอย่าง binary สอง blocks ซ้ำให้ ciphertextสอง blocksซ้ำใน ECB และเติม block padding ที่สาม
12. หลักฐานทดสอบและข้อจำกัดที่ต้องตอบตามจริง
| รายการ | สถานะ/ขอบเขต |
|---|---|
| Manual demo ในแชต | ผู้ใช้ build สำเร็จ; HELP/KEYGEN/DUMP/ENCRYPT/DECRYPT/STATS/CLEAR/EXIT; round trip ASCII16 bytes; quoted path/file missing/keyสั้น/wrong-key padding rejection |
| คำนวณแยกในรอบจัดทำ | ตรวจความยาวตาราง, IP/FP inverse ของ basis64 bits, K1/K16, known-answer block encryption/decryption, trace16 rounds, ASCII demo ciphertext |
| test.bat | มีอยู่ใน repo แต่ยังไม่รันในรอบจัดทำเอกสารนี้ |
| TestMaxFile ใน source | ตรวจ file I/O ที่ 1MiB และเกิน1 byte ไม่ใช่การเข้ารหัสทั้งไฟล์1MiB |
| TestRegisterContract ใน source | ทดสอบ ComputeBufferStats ของ D ไม่ใช่ public functions ของ C ทั้งหมด |
| TestInvalidPadding | ทดสอบกรณีถอดได้ท้าย0 ไม่ได้ครอบคลุม paddingผิดทุกแบบ |
| ผ่าน demo แปลว่าไม่มีบั๊กหรือไม่ | ไม่; ยืนยันได้เฉพาะกรณีที่ทดสอบ |
ข้อจำกัดจากการอ่าน source: globals scratch ทำให้ C ไม่ reentrant/thread-safe หากสอง calls ทำงานพร้อมกัน; helpers หลายตัวเชื่อ caller และไม่ validate capacity/position; NULL check ไม่รับรอง memory valid; padding error อาจเกิดหลัง output ถูกเขียนบางส่วนและไม่ได้ zeroize; subkeys/scratch ไม่ถูกล้างหลังใช้งาน; caller ใหม่ต้องไม่สมมติว่า public API รับ pointers/lengths ทุกค่าอย่างปลอดภัย
EncryptBufferECB คำนวณ (n/8+1)*8 ใน DWORD โดยไม่มี overflow guard ถ้า caller ใหม่ส่งความยาวใกล้ 2^32 ผลอาจ wrap เช่น n=FFFFFFF8h ทำ paddedLength กลายเป็น0 ขอบเขต1MiB ของ shell ปัจจุบันป้องกันไม่ให้ inputปกติไปถึงกรณีนี้ แต่ไม่ใช่หลักฐานว่าฟังก์ชันทั่วไปปลอดภัยทุก n
PermuteBits ใช้ source/dest ซ้อนกันไม่ได้เพราะล้าง dest ก่อนอ่าน; buffer-level ECB ไม่มีสัญญารองรับ overlap ทุกกรณี อย่าสรุปว่าทุกระดับใช้ in-place ได้จากการที่ block entry อ่านผ่าน scratch เท่านั้น DES 56-bit และ ECB เป็นการบ้านเพื่อศึกษา ไม่ควรอ้างว่าเหมาะกับระบบป้องกันข้อมูลใหม่ Padding ไม่ใช่ MAC/authentication
สิ่งที่ไม่ควรอ้างในสอบ: รับรองไม่มีบั๊ก; ตรวจ key ผิดได้เสมอ; STATS พิสูจน์ cryptographic entropy; ผ่าน test register แล้ว C รักษา registers ถูกทุกเส้นทาง; ผ่าน test boundary แล้วเข้ารหัส1MiB runtime-tested ทั้งหมด
13. คำถามอาจารย์พร้อมคำตอบ
เป็นคำถามที่มีเหตุผลว่าจะใช้ตรวจความเข้าใจ ไม่ใช่การรับประกันว่าอาจารย์จะถามแบบนี้ ฝึกตอบเองก่อนเปิดคำตอบ และชี้ code range ที่เกี่ยวข้องได้ HTML เปิด/ปิดคำตอบได้ ส่วน Markdown แสดงคำตอบครบ
1) Module C รับผิดชอบอะไร?
รับ buffer และ subkeys ทำ DES block encryption/decryption, ECB และ PKCS#7 คืน status กับความยาวผล ไม่เปิดไฟล์ ไม่ parse คำสั่ง ไม่สร้าง key schedule; A เป็นผู้เรียก
2) ใช้ DES รุ่นไหน?
Single DES ตาม FIPS46-3, block64 bits, effective key56 bits,16 Feistel rounds จัดไฟล์ด้วย ECB/PKCS#7 ไม่ใช่3DES เลข -3 เป็นฉบับเอกสาร
3) ต้องพิมพ์ KEYGEN ก่อน ENCRYPT หรือไม่?
ไม่ A เรียก GenerateKeySchedule ทุกคำสั่ง ENCRYPT/DECRYPT ส่วน KEYGEN ใช้สร้างและแสดง subkeys เพื่อดู
4) Key หลักกับ subkey ต่างกันอย่างไร?
keyหลักรับ8 bytes จาก hex16หลัก มี56 effective bits ส่วน subkeyแต่ละรอบ48 bits B สร้าง16ชุด รวม96 bytes C ใช้ทีละชุดในแต่ละround
5) ทุก block ใช้ key ชุดไหน?
ทุกblockใช้K1ถึงK16ชุดเดียวกันในการencrypt ไม่ใช่block1ใช้K1อย่างเดียว เมื่อdecryptกลับลำดับK16ถึงK1
6) ทำไม block8 bytes?
DES นิยาม block64 bits ซึ่ง8bytes×8bits=64bits ไม่ใช่เพราะไฟล์ยาว24bytes อัลกอริทึมอื่นมีขนาดอื่นได้
7) L0/R0 และ L16/R16 คืออะไร?
ครึ่งซ้ายและขวาอย่างละ32bits เลขท้ายเป็นสถานะหลังroundนั้น 0คือหลังIPก่อนเริ่ม ไม่ใช่ตำแหน่งbit
8) IP ทำอะไร?
เลือกinput bitตามตาราง64ช่องมาเป็นoutput64bits เช่น output1=input58 แล้วแบ่งผลเป็นL0/R0 ไม่ใช้key ไม่เพิ่มbits ไม่ใช่แปลงASCII
9) Inverse IP ย้อนการเข้ารหัสหรือไม่?
ไม่ ย้อนเฉพาะการจัดตำแหน่งIP หากไม่มีroundsคั่น IP⁻¹(IP(x))=x แต่ในDESมีkeyed coreคั่นจึงยังเป็นciphertext
10) ทำไมมี IP/FP?
เป็นการจัดรูปแบบ bits ที่เกี่ยวข้องกับhardwareเดิม ไม่เพิ่มความแข็งแรงโดยตัวมันเอง ต้องทำเพื่อให้ได้ผลตามDES ไม่ควรแต่งเหตุผลว่าIPเพิ่มdiffusionด้านความปลอดภัยแบบP
11) ถ้าเอา IP/FP ออกจะ decrypt ได้ไหม?
ได้ถ้าจัดทั้งสองฝั่งให้ย้อนกัน แต่ไม่ใช่DESมาตรฐาน ถ้าตัดเฉพาะFPท้ายencryptต้องตัดIPต้นdecrypt ไม่ใช่ตัดFPท้ายdecrypt
12) สูตร Feistel หนึ่งรอบคืออะไร?
Lใหม่=Rเก่า; Rใหม่=Lเก่า XOR f(Rเก่า,Ki) ใช้ค่ารอบก่อนทั้งคู่แล้วจึงอัปเดต มีxorกับkeyในf และxorกับLหลังf เป็นคนละจุด
13) ทำไมไม่เอาทั้ง block XOR16keys ตรง ๆ?
XORหลายค่าติดต่อกันรวมเป็นXORค่าเดียวได้ DESใช้S-boxและการสลับ/กระจายระหว่างroundsทำให้โครงสร้างซับซ้อนกว่านั้น
14) ทำไมต้อง16rounds?
เป็นจำนวนroundsที่กำหนดโดยDESและเป็นส่วนของการออกแบบความแข็งแรง ไม่เกี่ยวกับL/Rมี32bits ไม่ใช่ทำทีละbit16ตัว
15) f ทำอะไรทีละขั้น?
R32→E48→XORKi48→S-box32→P32 คืนค่า32bitsให้callerเอาไปXORL
16) E ขยายโดยเติม0หรือไม่?
ไม่ ใช้ตารางเลือกบางbitsซ้ำเพื่อให้ครบ48bits เช่น32,1,2,3,4,5แล้ว4,5,6,7,8,9
17) XOR bit1กับbit1ได้อะไร?
0 เหมือนกันได้0ต่างกันได้1 คุณสมบัติ X XOR Y XOR Y = X ช่วยให้ย้อนFeistelได้
18) S-box ย่อ6เป็น4โดยตัด2bitsหรือไม่?
ไม่ ใช้6bitsหาพิกัด: bitแรก+สุดท้ายเป็นrow0..3;4bitsกลางเป็นcolumn0..15 แล้วอ่านคำตอบ0..15จากboxที่กำหนด
19) Input011000เข้าS1ได้อะไร?
row00=0 column1100=12 S1[0][12]=5 binary4bits0101 เป็นค่าตาราง ไม่ใช่row+column
20) Input101011มีพิกัดอะไร?
row11=3 column0101=5 ถ้าใช้S1คำตอบ9=1001; boxอื่นต้องอ่านตารางของboxนั้น
21) หา index ใน sBoxTable อย่างไร?
boxIndex*64+row*16+column โดยboxIndex0..7 โค้ดshl6คูณ64และshl4คูณ16 indexสูงสุด511
22) ทำไม sBoxTable512bytes?
8boxes×4rows×16columns แต่ละคำตอบเก็บในBYTE แม้ค่า0..15ใช้เพียง4bits
23) ทำไม S-box สำคัญ?
เป็นส่วนnonlinearของf ตารางแทนค่าต่างจากpermutation/XORเชิงเส้น ช่วยไม่ให้ทั้งcipherกลายเป็นระบบเชิงเส้นที่แก้ง่าย
24) ทำไม P ไม่เหมือน IP ในบทบาท?
P32bitsอยู่ในfทุกround เพื่อกระจายผลS-boxให้มีอิทธิพลหลายboxesรอบถัดไป IP64bitsอยู่ก่อนroundsเพื่อจัดรูปแบบเข้า ตารางคนละชุด
25) P กระจายอย่างไร?
เช่น4bitsจากS1ช่อง1–4ไปoutput9,17,23,31 ค่าของbitไม่เปลี่ยน หลังEรอบถัดไปผลไปถึงหลายboxes
26) ทำไมมาตรฐานเลือก P แบบนั้น?
ร่วมกับEทำให้4outputbitsจากboxหนึ่งมีอิทธิพลต่อ6boxesถัดไป เกณฑ์นี้ช่วยให้ความแตกต่างแพร่ข้ามroundsและต้านdifferential cryptanalysis อ้างงานCoppersmith ไม่อ้างว่าPอย่างเดียวรับประกันความปลอดภัย
27) ทำไมสลับ R16/L16?
เพื่อให้round coreเดียวกันย้อนกลับด้วยkeyorderย้อนได้: เมื่อป้อน(Ri,Li)ด้วยKiได้(Rก่อน,Lก่อน) สลับท้ายจัดกลับ แล้วinverseIPคืนข้อมูล มีโค้ดWriteBE32 roundRightก่อนroundLeft
28) Decrypt ต้อง inverse S-box ไหม?
ไม่ ใช้fเดิมคำนวณซ้ำจากRเดิมที่กู้ได้ผ่านLของรอบถัดไป แล้วXORหักล้าง ไม่ต้องย้อนfที่6→4bits
29) ทำไม reverse key index=15-EBX?
EBXเริ่ม0..15 index15คือK16 และindex0คือK1 จากนั้นคูณ6หาbyteoffsetในbuffer96bytes
30) ECB คืออะไรและจุดอ่อน?
เข้ารหัสblocksแยกกันไม่มีchainingด้วยkeyเดียวกัน plaintextblocksเหมือนกันให้ciphertextเหมือนกัน จึงเผยรูปแบบซ้ำ ไม่ใช่modeที่ทำให้ข้อมูลเหมือนกันออกมาต่างกัน
31) Padding ใครทำและทำเมื่อไร?
EncryptBufferECBทำตอนประกอบecbBlockท้ายbuffer ไม่ทำในEncryptDESBlock DecryptBufferECBตรวจท้ายทั้งbufferหลังถอดทุกblock
32) ไฟล์6bytesเติมอะไร?
เติม02สองตัวเพราะค่าbytepaddingเท่ากับจำนวนที่เติม ทำให้รวม8bytes
33) ไฟล์8bytesทำไมกลาย16?
ต้องเติม08แปดตัวอีกblockเพื่อให้ท้ายข้อมูลจริงที่เหมือนpaddingไม่ถูกตัดผิด PKCS#7เติมอย่างน้อยหนึ่งbyteเสมอ
34) ไฟล์ว่าง encrypt/decrypt อย่างไร?
encryptlength0พร้อมvalidpointerเติม08แปดตัวได้ciphertext8bytes ถอดciphertextนั้นคืนplaintextlength0 แต่decryptinputlength0ตรง ๆ reject14
35) input08ตัวเดียวถูกตัดหมดไหม?
ไม่ encryptเติม07เจ็ดตัว decryptดู07ท้ายและตรวจเจ็ดตัวแล้วลดlengthเหลือ1 ไม่ตรวจตัดซ้ำจาก08ที่เหลือ
36) รู้จำนวน padding อย่างไร?
หลังdecryptอ่านbyteท้ายpad ตรวจ1..8แล้วตรวจทุกpadbytesเท่ากัน ถ้าผ่าน outputLength=inputLength-pad
37) ตัด padding คือ zero memory ทิ้งไหม?
ไม่ โค้ดลดความยาวที่รายงาน พื้นที่outputยังมีpaddingเดิม Aเขียนเฉพาะlengthที่รายงาน
38) ใส่ key ผิดจะ detect เสมอไหม?
ไม่ Wrongkeyอาจบังเอิญให้paddingถูก Paddingไม่ยืนยันkeyหรือintegrity ตัวอย่างในแชตrejectได้เฉพาะกรณีนั้น
39) ทำไม decrypt capacity ต้องเท่า ciphertext?
ต้องถอดทุกblockลงoutputก่อนตรวจและลดlength จึงต้องจุinputLengthทั้งหมด แม้plaintextสุดท้ายสั้นกว่า
40) test eax,7 ตรวจอะไร?
ต่ำสุด3bitsคือremainderเมื่อหาร8 ถ้าไม่ศูนย์lengthไม่ลงตัว8 ต้องตรวจpositiveด้วยเพราะ0ก็ลงตัวแต่ไม่ใช่PKCS#7ciphertext valid
41) ทำไม div ต้อง xor edx,edx ก่อน?
unsigneddivใช้ตัวตั้ง64bitsEDX:EAX ถ้าEDXค้างผลหารผิดหรือoverflow ล้างEDXเพื่อหารค่าinputLength32bits
42) ทำไม ReadBE32/WriteBE32 ต้องมี?
DESใช้bytearrayMSB-first แต่x86memoryDWORDlittle-endian helpersประกอบ/แยกbytesให้ค่าของL/Rสอดคล้องมาตรฐาน ไม่ใช่แปลงASCII
43) GetFipsBitC หาตำแหน่งอย่างไร?
q=(p-1)/8เป็นbyteindex r=(p-1)%8 ตำแหน่งshift=7-r แล้ว(value>>shift)&1 เพราะFIPSเริ่ม1จากซ้าย
44) PermuteBits ทำไมล้าง output ก่อน?
loop ORเฉพาะsourcebitsที่เป็น1 ถ้าไม่ล้างbits1จากการเรียกก่อนจะค้างทำให้ผลผิด repstosbเขียน0ทั้งพื้นที่
45) USES ช่วยอะไร?
MASMบันทึกและคืนregisterที่ระบุให้caller ค่าEAXจงใจคืนผล แต่USESไม่ได้รักษาflagsและไม่ทำglobalbuffersเป็นlocal/thread-safe
46) EAX=0หมายถึงสำเร็จทุกฟังก์ชันไหม?
ไม่ APIsที่คืนstatusใช่ แต่f/ReadBE32/S-box/Pคืนข้อมูล0ได้ปกติ GetFipsBitCคืนbit0ได้ ต้องอ่านcontractก่อน
47) ผลลัพธ์กลับ caller ทางไหน?
bytesผ่านoutputPtr ความยาวผ่านoutputLengthPtr และstatusผ่านEAX จึงคืนหลายอย่างได้โดยเขียนmemoryที่callerส่งaddressมา
48) ความยาวกับความจุต่างกันอย่างไร?
lengthคือbytesข้อมูลจริง capacityคือพื้นที่ที่เขียนได้ การเช็คcapacityป้องกันเขียนเกินในขอบเขตที่คำนวณlengthถูก แต่NULLcheckไม่ยืนยันพื้นที่จริง
49) รับไฟล์สูงสุดเท่าไร?
ENCRYPTplaintext1MiB=1048576bytes;ciphertextสูงสุด1048584bytes เผื่อpadding8 ขีดจำกัดอยู่Aไม่ใช่DES
50) ไฟล์ ASCII16bytesทำกี่roundsรวม?
paddingทำเป็น3blocks แต่ละblock16rounds รวม48round executions ใช้subkeys16ชุดเดิม สร้างscheduleครั้งเดียวในคำสั่งนั้น
51) ใช้หลาย threads ได้ไหม?
ปัจจุบันไม่ได้ปลอดภัย: permutationBlock,expandedBuffer,roundLeft/Rightและscratchอื่นเป็นglobals callsพร้อมกันอาจเขียนทับกัน shellปัจจุบันเรียกทีละคำสั่ง
52) errorแล้วoutputไม่ถูกแตะเลยไหม?
ไม่เสมอ บางerrorsเกิดหลังเขียนบางblocksแล้ว outputLengthยัง0 Callerต้องไม่ใช้outputเมื่อstatusผิด Aไม่เขียนไฟล์เมื่อCfail แต่ไม่ได้erasebufferหรือไฟล์เก่าที่มีอยู่
53) input/outputใช้addressเดียวกันได้ทุกfunctionไหม?
ไม่รับรอง PermuteBitsล้างdestก่อนอ่านจึงห้ามoverlap direct helper Public bufferAPIsก็ไม่มีสัญญารองรับpartialoverlapทุกแบบ ใช้buffersแยกตามAปลอดภัยกว่าการเดา
54) ตรวจทั้งโปรแกรมแล้วไม่มีบั๊กใช่ไหม?
ยังยืนยันไม่ได้ มีmanualcasesและstaticreviewกับคำนวณแยก ชุดtestบอกcoverageจำกัดและยังไม่ได้รันใหม่ในเอกสาร ต้องรายงานขอบเขตจริง
55) Round tripพอพิสูจน์DESถูกไหม?
ไม่ ทั้งencrypt/decryptอาจผิดแบบคู่กัน จึงต้องknown-answerciphertextด้วย เช่นbinary0123456789ABCDEF,key133457799BBCDFF1→85E813540F0AB405
56) ทำไมตัวอย่างASCIIได้ciphertextไม่เหมือนtestvector?
ข้อความ16ตัวเก็บbytes30 31...46 ต่างจากbinary8bytes01 23...EF ป้อนข้อมูลต่างกันจึงได้ผลต่างกัน อย่าเทียบจากหน้าตาhexstringอย่างเดียว
57) privatehelpersเติม/ลบpaddingอยู่ไหน?
เวอร์ชันนี้ไม่มีAddPKCS7PaddingหรือRemovePKCS7Paddingแยกเป็นPROC Logicอยู่ในEncryptBufferECBและDecryptBufferECBโดยตรง แม้READMEมีชื่อconceptualhelpers
58) ทำไมไม่ใช้ DES กับระบบจริงใหม่?
effectivekey56bitsเล็กและECBเผยblocksซ้ำ โปรเจกต์นี้ใช้เพื่อศึกษา การออกแบบใช้งานจริงต้องเลือกprimitive/modeที่เหมาะสมและตรวจauthentication ไม่ใช่ดูSTATSว่าผ่าน
59) จุดเสี่ยงถ้า callerใหม่ส่ง lengthใหญ่ผิดปกติ?
สูตรpaddedLength32bitอาจoverflowโดยไม่มีguard เช่นFFFFFFF8h wrapเป็น0 Shellจำกัด1MiBจึงไม่ถึงกรณีนี้ แต่publicAPIไม่ควรอ้างrobustทุกDWORD
60) ถ้าอาจารย์ให้ชี้3ฟังก์ชันสำคัญที่สุด?
EncryptBufferECBจัดblocks/padding;EncryptDESBlockเชื่อมIP-rounds-FP;RunFeistelRoundsเลือกkeyและอัปเดตL/R แล้วชี้FeistelFunctionเพื่อขยายรายละเอียดE-XOR-S-P
14. ลำดับ demo และวิธีตอบโดยชี้โค้ด
HELP
KEYGEN 133457799BBCDFF1
DUMP "secret.txt"
ENCRYPT "secret.txt" "demo.enc" 133457799BBCDFF1
DUMP "demo.enc"
DECRYPT "demo.enc" "demo.dec" 133457799BBCDFF1
DUMP "demo.dec"
STATS "demo.enc"
EXITนี่คือตัวอย่างจากsessionที่รันแล้ว หากจะรันซ้ำ outputชื่อเดิมอาจถูกเขียนทับ ให้เลือกชื่อที่ตั้งใจใช้ ก่อนอธิบายตัวเลขต้องดูDUMPไฟล์ต้นฉบับจริงว่าเป็นASCIIหรือbinary เตรียมตอบเป็น3ชั้น: หน้าที่→input/output→ชี้instructions อย่าอ่านชื่อฟังก์ชันอย่างเดียว
| คำถามเชื่อมโค้ด | จุดชี้ |
|---|---|
| หนึ่งblockเริ่มตรงไหน | EncryptDESBlock91–113 |
| keyประจำroundเลือกยังไง | RunFeistelRounds301–311 |
| L/Rอัปเดตยังไง | 312–317 |
| XORkeyเกิดตรงไหน | FeistelFunction338–344 |
| row/columnS-box | 381–396 |
| paddingเติมและตรวจ | Encrypt150–181; Decrypt238–258 |
| สลับครึ่งตรงไหน | 322–324 |
| ดึงbitอย่างไร | GetFipsBitC463–476 |
15. แหล่งอ้างอิงและวิธีตรวจเลข
Don Coppersmith, 1994: DES and its strength against attacks — เกณฑ์ S-box/P
IBM patent US3962539A — การจัด bits เข้า hardware
โค้ดที่ยกมามาจากโปรเจกต์ DCS-CLS และเก็บถ้อยคำตาม source ส่วนคำอธิบายเป็นภาษาไทยเรียบเรียงใหม่ ตาราง trace คำนวณจาก source; ไม่คัดลอกเนื้อหาเอกสารอ้างอิงทั้งฉบับ