DCS-CLS / MODULE C

จากข้อมูล 8 bytes ถึง 16 Feistel rounds — อ่านโค้ดคู่คำอธิบาย และฝึกตอบด้วยเหตุผล

คู่มือเข้าใจ Module C — DCS-CLS

อธิบายภาษาไทยแบบเทียบโค้ดจริงทีละช่วง พร้อมตัวอย่างคำนวณและคำถามสอบปากเปล่า • จัดทำ 12 ตุลาคม 2026

ขอบเขตหลักคือทุก procedure ใน module_C.asm รวมจุดเชื่อมกับ A/B และพื้นฐานที่จำเป็น เอกสารนี้อธิบาย implementation ที่มีอยู่ ไม่ได้แก้ source หรือเขียนฟังก์ชันเฉลยใหม่ให้ ตัวอย่างที่เป็นโค้ดคัดจากไฟล์จริงโดยคงคำสั่งและลำดับไว้ เลขบรรทัดเป็นเลขของ snapshot นี้ ถ้า source เปลี่ยนต้องตรวจใหม่

รายการหลักฐาน
Repositoryhttps://github.com/Applelyyyy/DCS-CLS
Commit363ba58d884ba699c40fd7a6539be729f36eb4f7
SourceC:\Users\iponm\DCS-CLS\module_C.asm
SHA-256 ของ module_C.asm797b14c2fa8c57db97afd26e68b2fadb40d3931caef5c92c7fec87dd186811f7
หลักฐานการรันผู้ใช้ส่ง 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 เปิดอ่านและเขียนไฟล์ เรียกโมดูลอื่นทุกคำสั่ง
BGenerateKeySchedule สร้าง subkeys และ DisplayKeySchedule แสดงผลKEYGEN; A เรียกสร้างอีกครั้งใน ENCRYPT/DECRYPT
CDES 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 สำเร็จ เขียนผลลงไฟล์
A: ตรวจคำสั่งและแปลง key หลักเป็น 8 bytesA: อ่านไฟล์ → input buffer + ความยาวB: สร้าง K1–K16 ลง buffer 96 bytesC: EncryptBufferECB จัด block และ paddingC: EncryptDESBlock ต่อ block → output bufferA: ถ้า 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
รับ block 8 bytes = 64 bitsApplyInitialPermutation: IPReadBE32: แบ่งเป็น L0 / R0 อย่างละ 32 bitsRunFeistelRounds: ทำ 16 roundsWriteBE32: เขียน R16 ก่อน L16ApplyInversePermutation: 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 bits
R เก่า 32 bitsE expansion → 48 bitsXOR subkey ของรอบนั้น → 48 bitsS-box 8 กล่อง: 6 → 4 bits ต่อกล่อง → 32 bitsP permutation → ผล f 32 bits
L ใหม่ = 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 bit1 byte = 8 bits; bit มีค่า 0/1; 8 bytes = 64 bits
ASCII กับ binaryA = 65 ฐานสิบ = 41h = 01000001; เป็นข้อมูลเดียวกันคนละวิธีแสดง
PTR BYTE / DWORDPTR 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 / ALAL เป็น 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 / PROTOMASM ตรวจ 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 / DecryptDESBlockinputPtr, outputPtr, subkeysPtrเขียน 8 bytes; EAX เป็น status
EncryptBufferECB / DecryptBufferECBinputPtr, inputLength, outputPtr, outputCapacity, subkeysPtr, outputLengthPtrเขียนผลใน outputPtr; เขียนความยาวผ่าน outputLengthPtr; EAX เป็น status
Argument ของ buffer APIค่า vs addressหน้าที่
inputPtraddressต้นข้อมูล ไม่ใช่ชื่อไฟล์
inputLengthค่า DWORDจำนวน bytes ข้อมูลจริง
outputPtraddressต้นพื้นที่เขียนผล
outputCapacityค่า DWORDจำนวน bytes ที่ปลายทางจุได้
subkeysPtraddressต้น K1–K16 เรียงกัน 6 bytes ต่อชุด
outputLengthPtraddressตำแหน่งตัวแปร DWORD ที่ให้ฟังก์ชันเขียนความยาวผลกลับ
module_C.asm · บรรทัด 6–30
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 ภายในไฟล์

module_C.asm · บรรทัด 32–45
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 อนุญาตตำแหน่งซ้ำจึงขยายได้

module_C.asm · บรรทัด 46–77
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,11

S-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

module_C.asm · บรรทัด 79–90
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 .code

permutationBlock เป็น 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 errorblock APIs, buffer APIs, IP wrappers, ExpandRightHalf, PermuteBits, RunFeistelRounds, WriteBE32
ข้อมูล 32 bitsReadBE32, FeistelFunction, ApplySBoxes, ApplyPPermutation
ค่าหนึ่ง bit: 0/1GetFipsBitC

อย่าใช้ 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 ถูกเขียนแล้ว

module_C.asm · บรรทัด 91–100
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

module_C.asm · บรรทัด 101–107
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,edi

IP เขียนข้อมูลจัดตำแหน่งลง permutationBlock จากนั้น test status ก่อนทำ rounds RunFeistelRounds ใช้ MODE_ENCRYPT และแก้ scratch block ให้เป็น R16|L16 สุดท้าย inverse IP อ่าน scratch แล้วเขียน ciphertext ลง output ของ caller Status จาก inverse IP อยู่ใน EAX สำหรับคืนต่อ

module_C.asm · บรรทัด 108–113
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 ย้อนกลับ

module_C.asm · บรรทัด 115–124
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 ทั้งหมด

module_C.asm · บรรทัด 125–131
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 แบบใหม่

module_C.asm · บรรทัด 132–137
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 คืน plaintext

IP ตอนเริ่ม 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

module_C.asm · บรรทัด 276–283
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

module_C.asm · บรรทัด 285–297
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,ebx

ESI ชี้ scratch block EDI ชี้ subkeys; ตรวจ NULL ReadBE32 อ่าน 4 bytes แรกเป็น L0 เก็บใน roundLeft แล้วขยับ ESI อีก 4 bytes อ่าน R0 เก็บใน roundRight EBX=0 เป็น round index แบบ zero-based ไม่ใช่จำนวน bits ที่ประมวลผล

module_C.asm · บรรทัด 298–311
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

module_C.asm · บรรทัด 312–319
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_loop

EAX ก่อน INVOKE เป็น subkey pointer; หลัง INVOKE เป็นค่า f 32 bits นำ L เก่ามา EDX แล้ว XOR กับ f → R ใหม่ จากนั้นเอา R เก่าใส่ L ใหม่ และเก็บ EDX ใน R ใหม่ ต้องรักษาค่าเก่าก่อนเขียนทับเพื่อไม่ให้สูตรใช้ข้อมูลผิด EBX เพิ่มหนึ่ง แล้ววนรอบต่อ ทั้ง L และ R ยังคง 32 bits ในทุกรอบ

module_C.asm · บรรทัด 320–330
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

module_C.asm · บรรทัด 332–337
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 บนเส้นทางปกติ

module_C.asm · บรรทัด 338–344
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_xor

XOR ทีละ byte 6 รอบ mov al,[esi+ecx] อ่านหนึ่ง byte จาก K_i; xor expandedBuffer[ecx],al ผสมกับ byte ตำแหน่งเดียวกัน 6 bytes × 8 bits = 48 bits ผลยังอยู่ใน expandedBuffer ไม่ได้เอาไฟล์ทั้งก้อน XOR กับ key หลัก

module_C.asm · บรรทัด 345–350
345 ff_sboxes:
346     INVOKE ApplySBoxes,ADDR expandedBuffer
347     INVOKE ApplyPPermutation,eax
348 ff_done:
349     ret
350 FeistelFunction ENDP

ApplySBoxes ย่อผล 48 เป็น DWORD 32 bits ใน EAX แล้ว ApplyPPermutation รับค่านี้และคืนผล f 32 bits เส้นทางสำเร็จ ret ส่งข้อมูลให้ RunFeistelRounds อย่า test EAX แล้วคิดว่าค่า f=0 คือ error ขั้นนี้ไม่ตรวจ subkeyPtr NULL ด้วยตัวเอง แต่ public block entry ตรวจมาก่อน

6.4 ExpandRightHalf

module_C.asm · บรรทัด 352–356
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 ENDP

WriteBE32 แปลงค่า 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 ต่างกัน

module_C.asm · บรรทัด 358–368
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,ecx

ESI ชี้ expanded buffer ตรวจ NULL แล้ว reset sboxCombined=0 และ EBX=0 เป็น box index แต่ละ box reset EDX=0 สำหรับรวบ 6 bits และ ECX=0 เป็น index ภายในกลุ่ม

module_C.asm · บรรทัด 369–380
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

module_C.asm · บรรทัด 381–388
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,4

row ใช้ 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 ทิ้งเพื่อลดขนาด

module_C.asm · บรรทัด 389–396
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

module_C.asm · บรรทัด 397–409
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

module_C.asm · บรรทัด 411–416
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 ENDP

S-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 19
input bit 217
input bit 323
input bit 431

ตัวอย่าง 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

module_C.asm · บรรทัด 139–149
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],0

ESI/EDI เป็น input/output base EDX ชี้ตัวแปร outputLength ตรวจ NULL สาม pointer แล้วเขียน *outputLengthPtr=0 ก่อนทำงาน เมื่อเกิด error ภายหลัง length จึงยังเป็น 0 แต่ถ้า outputLengthPtr เป็น pointer ผิดแม้ไม่ใช่ NULL ก็ยังเขียนผิด memory ได้ ไม่ได้ตรวจ subkeysPtr ตรงนี้ แต่ block entry จะตรวจ

module_C.asm · บรรทัด 150–160
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 จำกัด

module_C.asm · บรรทัด 161–166
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,ecx

EDX=absolute byte offset ของ block เริ่ม 0; ECX=paddedLength ลูปจบเมื่อ offset ถึง paddedLength push ECX เก็บความยาวทั้งหมดไว้แล้วใช้ ECX ใหม่เป็นตำแหน่งภายใน block 0..7

module_C.asm · บรรทัด 167–181
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 และไม่แก้ไฟล์ต้นฉบับ

module_C.asm · บรรทัด 182–192
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 ถูกเขียนแล้ว

module_C.asm · บรรทัด 193–205
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 bytesoutputLengthblocks
008881
107781
503381
602281
8088162
16088243
24088324
25077324
10485760881048584131073

ข้อมูลลงตัวต้องเติม 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

module_C.asm · บรรทัด 207–217
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

module_C.asm · บรรทัด 218–225
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 ก่อนตัด

module_C.asm · บรรทัด 226–237
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_loop

EBX เป็น offset เริ่ม 0 สร้าง input address = ESI+offset และ output address = EDI+offset ส่งให้ DecryptDESBlock ซึ่งทำ core ย้อน key order ตรวจ status แล้ว offset +=8; ไม่เติม padding ใหม่ ไม่เชื่อม ciphertext ระหว่าง blocks

module_C.asm · บรรทัด 238–247
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 ท้ายทั้งหมด

module_C.asm · บรรทัด 248–255
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

module_C.asm · บรรทัด 256–274
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

module_C.asm · บรรทัด 418–427
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

module_C.asm · บรรทัด 428–436
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 นี้

module_C.asm · บรรทัด 437–444
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_next

EBX เป็น output bit index 0..bitCount-1 อ่าน table[EBX] เป็นตำแหน่ง source แบบ 1-based แล้ว GetFipsBitC คืนค่า bit ถ้าเป็น 0 ข้ามการ OR เพราะ dest ถูกล้างเป็น 0 ไว้แล้ว การเปลี่ยนตารางทำให้เครื่องเดียวใช้กับงาน 4 แบบได้

module_C.asm · บรรทัด 445–454
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

module_C.asm · บรรทัด 455–461
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

module_C.asm · บรรทัด 463–476
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 → 1

9.3 ReadBE32

module_C.asm · บรรทัด 478–488
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

module_C.asm · บรรทัด 490–503
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 แบบโค้ดจริง

module_A.asm · บรรทัด 1128–1135
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_done

HandleEncrypt ตรวจ path input/output แบบ case-insensitive ถ้าเท่ากันปฏิเสธ จากนั้นอ่าน input สูงสุด 1 MiB การเทียบชื่อ string ไม่รับรองว่า path ต่างรูปหมายถึงคนละไฟล์ เช่น relative/absolute หรือ hard links; จึงไม่ควรอ้างว่าป้องกัน alias ได้ทั้งหมด

module_A.asm · บรรทัด 1148–1165
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_done

lea คำนวณ address ของ keyBytes ใน PARSED_COMMAND ส่งให้ B สร้าง subkeys ใน moduleSubkeys A พิมพ์จำนวน blocks แล้วส่ง arguments 6 ตัวให้ EncryptBufferECB หาก status ไม่สำเร็จจะไม่ถึง WriteWholeFile ถ้าสำเร็จเขียนจำนวน moduleOutputLength bytes โค้ดช่วงที่ไม่ได้ยกเป็นข้อความแสดง progress ไม่ใช่ส่วน cipher

module_A.asm · บรรทัด 1200–1216
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_done

Decrypt ก็สร้าง 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: 85E813540F0AB405

11.1 S-box รอบแรกครบแปดกล่อง

Box6-bit inputrowcolumnค่า4-bit output
S101100001250101
S201000118121100
S301111001581000
S411101021320010
S510000130111011
S61001102350101
S701010001091001
S81001113370111

11.2 ทุก round ของ block binary ตัวอย่าง

RoundL เก่าR เก่าKifL ใหม่R ใหม่
1CC00CCFFF0AAF0AA1B02EFFC7072234AA9BBF0AAF0AAEF4A6544
2F0AAF0AAEF4A654479AED9DBC9E53CAB87A3EF4A6544CC017709
3EF4A6544CC01770955FC8A42CF994D166EB0CC017709A25C0BF4
4CC017709A25C0BF472ADD6DB351DBB23774CA25C0BF477220045
5A25C0BF4772200457CEC07EB53A82813ADC3772200458A4FA637
6772200458A4FA63763A53E507B2F9E45CD2C8A4FA637E967CD69
78A4FA637E967CD69EC84B7F618BC8C051C27E967CD69064ABA10
8E967CD69064ABA10F78A3AC13BFB3C0E86F9064ABA10D5694B90
9064ABA10D5694B90E0DBEBEDE78122367C6AD5694B90247CC67A
10D5694B90247CC67AB1F347BA464F62BC9C22247CC67AB7D5D7B2
11247CC67AB7D5D7B2215FD3DED386E104FA02B7D5D7B2C5783C78
12B7D5D7B2C5783C787571F59467E9C268CFEAC5783C7875BD1858
13C5783C7875BD185897C5D1FABA41DDBB292275BD185818C3155A
1475BD185818C3155A5F43B7F2E73AB7318E5518C3155AC28C960D
1518C3155AC28C960DBF918D3D3F0A5B81276EC28C960D43423234
16C28C960D43423234CB3D8B0E17F5C8C04F98434232340A4CD995

จบได้ 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 ของ keyD ของ keySubkey
11E19955FAACCF1E1B02EFFC7072
21C332ABF5599E3D79AED9DBC9E5
320CCAAFF56678F555FC8A42CF99
42332ABFC599E3D572ADD6DB351D
52CCAAFF06678F557CEC07EB53A8
6232ABFC399E3D5563A53E507B2F
72CAAFF0C678F556EC84B7F618BC
822ABFC339E3D559F78A3AC13BFB
91557F8663C7AAB3E0DBEBEDE781
10255FE199F1EAACCB1F347BA464F
11257F8665C7AAB33215FD3DED386
1225FE19951EAACCF7571F59467E9
1327F866557AAB33C97C5D1FABA41
142FE19955EAACCF15F43B7F2E73A
152F866557AAB33C7BF918D3D3F0A
161F0CCAAF556678FCB3D8B0E17F5

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-box381–396
paddingเติมและตรวจEncrypt150–181; Decrypt238–258
สลับครึ่งตรงไหน322–324
ดึงbitอย่างไรGetFipsBitC463–476

15. แหล่งอ้างอิงและวิธีตรวจเลข

โค้ดโปรเจกต์ ณ commit ที่ระบุ

NIST FIPS 46-3: DES

Don Coppersmith, 1994: DES and its strength against attacks — เกณฑ์ S-box/P

IBM patent US3962539A — การจัด bits เข้า hardware

โค้ดที่ยกมามาจากโปรเจกต์ DCS-CLS และเก็บถ้อยคำตาม source ส่วนคำอธิบายเป็นภาษาไทยเรียบเรียงใหม่ ตาราง trace คำนวณจาก source; ไม่คัดลอกเนื้อหาเอกสารอ้างอิงทั้งฉบับ