โหมดมืด
บทที่ 4 — Cryptography for Devs
← บทที่ 3 | สารบัญ | บทที่ 5 →
📒 มีศัพท์ที่ไม่คุ้น? ดู ตารางคำศัพท์ + คำย่อ ในบทที่ 0 ข้อ 18 หรือ glossary หลัก
หลังจบบท คุณจะ:
- เข้าใจ Hash, HMAC, Encryption (symmetric / asymmetric), Signature
- เลือก algorithm + library ถูก
- เข้าใจ TLS ทำงานยังไง
- ใช้ key management (generate, rotate, store)
- เลี่ยง pitfall ที่พบบ่อย
กฎทอง: "อย่าเขียน crypto เอง — ใช้ library ที่คนอื่นทดสอบมาแล้ว" (ฝรั่งเรียก "Don't roll your own crypto" — "roll" ที่นี่หมายถึง "ทำขึ้นเอง" ไม่ใช่ "กลิ้ง") — ใช้ library มาตรฐาน ไม่เขียนเอง
🗺️ บทนี้อ่านยังไง — ถ้าไม่เข้าใจ ข้ามได้
บทนี้เป็นบทที่ "ทฤษฎีหนักสุด" ในเล่ม มีคณิตศาสตร์และศัพท์เฉพาะเยอะ — ไม่ต้องเข้าใจทุกอย่างก็ได้
ถ้าจำได้แค่ประโยคเดียวจากบทนี้ ให้จำว่า: "ใช้ library มาตรฐาน อย่าเขียน crypto เอง" เท่านี้ก็ปลอดภัยกว่า 90% ของคนทั่วไปแล้ว
ระดับ อ่านยังไง มือใหม่ อ่านเอา "ความรู้สึก" ว่าแต่ละอย่าง (hash, encrypt, sign) ใช้ทำอะไร + จำกฎทองข้างบน · ส่วนที่มี ⚠️ pitfall (ข้อ 18) อ่านให้รู้ว่า "อย่าทำอะไร" · รายละเอียด algorithm/โค้ด ข้ามได้ พอเขียนโค้ดเป็น ทำตามตัวอย่าง AES-GCM (ข้อ 5) + HMAC (ข้อ 4) + SecureRandom (ข้อ 8) ได้ · ที่เหลืออ่านเอาหลักการ ทำงานจริง/มี cloud ลงลึก envelope encryption (ข้อ 15) + KMS + key rotation ได้เลย 👉 ตัวอย่างโค้ดในบทนี้เป็น Java เป็นหลัก — ใช้ภาษาอื่นให้อ่านเอาหลักการแล้วหา crypto library ของภาษาตัวเอง
1. ทำไม Cryptography ยาก
"นี่ algorithm ปลอดภัย" — แต่ implement ผิด → ไม่ปลอดภัย
"นี่ library ดี" — แต่ใช้ผิด mode → ไม่ปลอดภัย
"ทำงานได้" — แต่ side channel leak → ไม่ปลอดภัย📒 ศัพท์ที่จะเจอในย่อหน้าถัดไป (อ่านก่อนเข้าตัวอย่าง):
- ECB (อ่าน อี-ซี-บี) = Electronic Codebook — โหมดที่ encrypt แต่ละก้อนแยกกันโดยไม่ผูกกัน ทำให้ก้อนเหมือนกัน → ผลลัพธ์เหมือนกัน (เห็น pattern)
- IV (อ่าน ไอ-วี) = Initialization Vector — ค่าสุ่มที่ใส่เข้าไปเพื่อให้ผลลัพธ์ของการ encrypt ข้อความเดียวกันต่างกันทุกครั้ง
- padding = การเติมไบต์ท้ายข้อมูลให้ครบขนาด block (เช่น 16 bytes สำหรับ AES)
- side channel = ช่องโหว่ที่ไม่ใช่ตัว algorithm — แต่รั่วผ่าน "ผลข้างเคียง" เช่น เวลา, พลังงาน, เสียงที่ใช้
ตัวอย่างจริง:
- ECB mode ของ AES → encrypted แต่ยังเห็น pattern
- ใช้ random IV ซ้ำ → ทำลายความปลอดภัย
- Implement RSA เอง → padding error → key recovery
→ ใช้ library + standard mode. Cryptographer เก่ง ๆ ยังพลาด
2. ประเภทของ Cryptography
"cryptography" ไม่ได้มีแค่ "เข้ารหัส" — มันเป็นกลุ่มเครื่องมือหลายแบบที่ใช้คนละงาน: hash (ตรวจ integrity), HMAC, symmetric/asymmetric encryption, digital signature, key exchange และ secure random เข้าใจภาพรวมนี้ก่อนแล้วทั้งบทจะต่อกันติด:
1. Hash function — text → digest (ลายเซ็นย่อ, one-way = ทำกลับไม่ได้)
2. HMAC — hash + key (verify integrity + authenticity)
3. Symmetric encryption — 1 key, encrypt + decrypt
4. Asymmetric encryption — public/private key pair
5. Digital signature — sign with private, verify with public
6. Key exchange — ตกลง shared key ผ่าน public channel
7. Random number generation — secure randomness3. Hash Function
input (any size) → hash function → digest (fixed size)
"hello" → SHA-256 → 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824คุณสมบัติของ Hash ที่ดี
- One-way (ทำกลับไม่ได้) — reverse ไม่ได้
- Deterministic (ผลลัพธ์คงเดิม) — input เดิม = output เดิมเสมอ
- Avalanche (ผลกระเพื่อม) — เปลี่ยน input 1 bit → output เปลี่ยนเกือบหมด
- Collision resistance (กันชน) — หา 2 input ที่ hash เท่ากันยาก
Algorithm ที่ใช้
MD5 128-bit ❌ broken (collisions found)
SHA-1 160-bit ⚠️ broken สำหรับ collision (SHAttered, Google 2017)
— ยังถือว่าปลอดภัยสำหรับ HMAC แต่ห้ามใช้กับ signature/cert ใหม่
SHA-256 256-bit ✅ standard
SHA-512 512-bit ✅ standard (faster on 64-bit CPU)
SHA-3 variable ✅ modern alternative (different design)
SHA-3 SHAKE-128/256 ✅ XOF (extendable-output) — variable length output, ใช้แทน HKDF บางกรณี
BLAKE2b up to 512-bit ✅ modern, fast — ใช้ภายใน Argon2, WireGuard
BLAKE3 256-bit ✅ modern, very fast (อ่าน "เบลค-ทรี")ใช้สำหรับ
✅ File integrity (SHA-256 checksum)
✅ Detect change (Git uses SHA-1 — for non-security purpose)
✅ Index (hash table)
❌ Password — ใช้ slow hash แทน (BCrypt, Argon2)
❌ Symmetric encryption — hash ไม่ใช่ encryptionตัวอย่าง
java
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HexFormat;
public class HashExample {
public static String sha256Hex(String text) throws NoSuchAlgorithmException {
byte[] data = text.getBytes(StandardCharsets.UTF_8);
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hash = md.digest(data);
return HexFormat.of().formatHex(hash);
// "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
}
}📌 Snippet ต่อๆ ไปในบทนี้จะ ไม่ใส่ import statement ซ้ำ เพื่อให้อ่านโค้ดสำคัญง่ายขึ้น แต่ทุกตัวอย่าง compile ได้ถ้ามี import ตามมาตรฐาน Java (
java.security.*,javax.crypto.*,java.nio.charset.StandardCharsets,java.util.*)
4. HMAC — Hash-based Message Authentication
HMAC = hash(secret + message) → ผู้ที่มี secret เท่านั้นที่สร้างได้ใช้สำหรับ
- Webhook signature
- JWT (HS256)
- API request signing
- Cookie integrity
ตัวอย่าง
java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
String message = "amount=100&to=alice";
String secret = "shared-secret-key";
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(), "HmacSHA256"));
byte[] sig = mac.doFinal(message.getBytes());
String sigHex = HexFormat.of().formatHex(sig);Webhook ตัวอย่าง (Stripe-style)
Server ส่ง webhook:
POST https://client.com/webhook
X-Signature: t=1747562400,v1=abc123...
body: {"event":"payment.success"}
Client verify:
computed = HMAC-SHA256(secret, "1747562400." + body)
ถ้า computed != provided signature → reject (อาจเป็น forge)⚠️ Constant-Time Comparison
java
// ❌ Time-leaking comparison (early exit)
if (sig1.equals(sig2)) { ... }
// ✅ Constant time — `MessageDigest.isEqual` คือ API ที่ JDK แนะนำสำหรับเทียบ byte[] แบบ constant-time
if (MessageDigest.isEqual(sig1.getBytes(), sig2.getBytes())) { ... }→ ป้องกัน timing attack — attacker วัดเวลา response เพื่อเดา sig ทีละ byte
📌 เวอร์ชันสำคัญ (timeline ที่จริง):
- JDK 6 update 17 (2009) เริ่มเปลี่ยน
MessageDigest.isEqualเป็น constant-time (CVE-2009-3875)- JDK 7+ การันตี constant-time implementation อย่างเป็นทางการ
- ถ้ายัง maintain โค้ด Java เก่ามาก (ก่อน 6u17) → implement comparison เอง (loop ที่ XOR ทุก byte แล้วเช็คท้ายสุด)
⚠️ caveat สำคัญ:
MessageDigest.isEqualเป็น constant-time เฉพาะเมื่อ length เท่ากัน — ถ้า length ต่างจะ return false ทันที (early-return) ซึ่ง leak length ได้ ถ้ากลัว length leak ให้ pad ให้ยาวเท่ากันหรือ check length เท่ากันก่อนเรียก แล้วเปรียบเทียบ length ผ่าน constant-time function
- Python:
hmac.compare_digest()- Node.js:
crypto.timingSafeEqual()- Go:
subtle.ConstantTimeCompare()
5. Symmetric Encryption
1 key — ใช้ทั้ง encrypt และ decrypt
text + key → encrypt → cipher
cipher + key → decrypt → textAlgorithm
DES ❌ broken (56-bit key, breakable)
3DES ❌ deprecated (slow + 64-bit block)
AES-128 (เอ-อี-เอส 128) ⚠️ acceptable สำหรับ short-lived data
— Grover's algorithm (quantum) ลด strength เหลือ 64-bit ~ marginal
AES-256 ✅ ⭐ recommended baseline (PQ-resistant via Grover)
ChaCha20 (อ่าน "ชา-ชา-ทเวนตี้") + Poly1305 ✅ modern AEAD,
เร็วบน ARM/mobile ที่ไม่มี AES hardware
XChaCha20-Poly1305 ✅ extended nonce (192-bit) — ทน nonce-misuse ดีกว่าMode of Operation — สำคัญมาก
📒 mental model:
- block cipher = อัลกอริทึมที่ encrypt ทีละก้อน 128-bit (16 bytes) ก้อนเดียวเท่านั้น (เช่น AES คือ "ฟังก์ชัน" รับ key + 16 bytes → 16 bytes)
- mode (โหมด) = วิธี "จัดเรียง" หลายก้อนต่อ ๆ ไปให้ดูเหมือนสุ่ม (เพราะถ้าใช้ block cipher ตรง ๆ ทุกก้อนจะ encrypt แยกกัน → ก้อนเหมือนกัน = cipher เหมือนกัน = leak pattern)
แปลคร่าว ๆ: block cipher = "เครื่องเข้ารหัสทีละกล่อง", mode = "วิธีลำเลียงกล่องต่อ ๆ ไป"
| Mode | ความปลอดภัย | ใช้เมื่อ |
|---|---|---|
| ECB | ❌ ห้าม | (insecure — เห็น pattern) |
| CBC | ❌ avoid for new code | legacy only — ต้อง encrypt-then-MAC + random IV + ป้องกัน padding oracle ถูก ถึงจะปลอดภัย |
| GCM ⭐ | ✅ มาตรฐาน | สมัยใหม่ — AEAD มี authentication ในตัว |
| CTR | ⚠️ ดี | streaming — แต่ไม่มี authentication, ต้องคู่กับ MAC |
⚠️ ECB Mode — ห้ามใช้
text
ECB encrypt block ตรงๆ → block เหมือนกัน → cipher เหมือนกัน
→ pattern ใน image ยังเห็นชัดเจน🖼️ ภาพประกอบ classic — "Tux penguin ECB": ลองเข้าหน้านี้ของ Wikipedia เพื่อดูภาพเทียบ ECB vs other mode (public domain): https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Electronic_codebook_(ECB)
ภาพเพนกวินที่ encrypt ด้วย ECB ยังเห็นเค้าโครงเพนกวินครบ — ส่วน mode อื่น (CBC/CTR/GCM) จะกลายเป็น noise ไร้รูปร่าง นี่คือ "visual proof" ของทำไม ECB ห้ามใช้กับ data ที่มี pattern
AES-GCM ใน Java
ก่อนดูโค้ด — mental model ของ AEAD/AAD/tag/IV:
- AEAD (อ่าน เอ-อี-เอ-ดี) = Authenticated Encryption with Associated Data — "เข้ารหัส + ตรวจความถูกต้อง ในก้อนเดียว" (ไม่ต้องเรียก HMAC แยก)
- IV / nonce = ค่าสุ่ม 12 bytes ที่ใส่เข้าไปคู่กับ key — ทำให้ encrypt ข้อความเดิม 2 ครั้งได้ ciphertext ต่างกัน · ห้ามซ้ำต่อ key เดียวกัน เด็ดขาด
- AAD = ข้อมูลแถม (associated data) ที่จะ "ตรวจ integrity" แต่ "ไม่ encrypt" — เช่น user_id, version, file_id (อยู่ใน metadata แบบ public แต่ห้ามถูกสลับ)
- tag = ลายเซ็น 16 bytes ที่ติดท้าย ciphertext — ตอน decrypt ถ้า tag ผิดแสดงว่า ciphertext/AAD ถูกแก้
- GCM (อ่าน จี-ซี-เอ็ม) = Galois/Counter Mode — โหมดที่ implement AEAD
ทีนี้ดูโค้ด:
java
import javax.crypto.*;
import javax.crypto.spec.*;
import java.security.SecureRandom;
// 1. Generate key (256-bit = 32 bytes)
KeyGenerator kg = KeyGenerator.getInstance("AES");
kg.init(256);
SecretKey key = kg.generateKey();
// 2. Generate IV (12 bytes for GCM)
byte[] iv = new byte[12];
new SecureRandom().nextBytes(iv);
// 3. Encrypt
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(128, iv));
// 128 = tag length (bits) = 16 bytes ต่อท้าย ciphertext; ห้ามใช้ < 96
// 3.1 ผูก context กับ ciphertext ผ่าน AAD
byte[] aad = ("user_id=42;version=1").getBytes(StandardCharsets.UTF_8);
cipher.updateAAD(aad);
byte[] cipherText = cipher.doFinal("secret message".getBytes(StandardCharsets.UTF_8));
// 4. Decrypt — ต้อง updateAAD ด้วย AAD ตัวเดียวกันก่อน doFinal
cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv));
cipher.updateAAD(aad);
byte[] plainText = cipher.doFinal(cipherText);ทำไม tag 128-bit: ถ้าใช้ tag สั้นกว่า 96-bit attacker สุ่มถูกได้ง่าย — NIST แนะนำ 128-bit สำหรับ general use
ทำไม IV ซ้ำ = หายนะ: ถ้า attacker เห็น ciphertext 2 ตัวที่ใช้ (key, IV) เดียวกัน → XOR กันแล้วได้ "plaintext1 XOR plaintext2" (key หายไป) + recover authentication key → forge ciphertext ใด ๆ ก็ได้ (ไม่ใช่แค่ decrypt ที่เคยเห็น) นี่คือเหตุผลที่ GCM IV reuse ถือว่า "catastrophic"
AAD จริง ๆ ใช้ยังไง: ถ้า attacker copy ciphertext ของ user A ไปแปะเป็นของ user B (โดยที่ user_id อยู่นอก ciphertext) — ตอน decrypt ฝั่ง B ใส่ AAD="user_id=B" → tag ไม่ตรง → fail ทันที กัน "ciphertext substitution attack"
⚠️ Cipher object ไม่ thread-safe: ต้อง
init()ใหม่ทุกครั้งก่อนใช้ — ห้าม cache Cipher object แล้วเอาไปใช้ข้าม request/thread จะเจอ data race แบบ silent (ciphertext เพี้ยน) ถ้าต้องการ pool ให้ใช้ThreadLocal<Cipher>หรือสร้างใหม่ทุกครั้ง (getInstanceเร็วพอ)
⚠️ GCM IV birthday bound — สำคัญสำหรับ high-throughput (⚠️ ขั้นสูง — มือใหม่ข้ามได้):
"Birthday bound" (birthday paradox) = ปรากฏการณ์ทาง probability ที่ collision เกิดเร็วกว่าที่คนทั่วไปคิด · ตัวอย่างคลาสสิก: ในห้องที่มีคน 23 คน โอกาสที่จะมี 2 คนเกิดวันเดียวกันคือ ~50% (ไม่ใช่ 365/23) — เพราะเราเทียบทุกคู่ ไม่ใช่เทียบกับวันใดวันหนึ่ง · กับ random 96-bit IV หลังจาก ~2¹⁶ messages ก็เริ่มมีโอกาส collision แล้ว
NIST SP 800-38D กำหนด 2³² invocations per key เป็น hard limit (ไม่ใช่แค่ "ระวัง collision" — เป็น mandate) เพื่อรักษา collision probability < 2⁻³² · ต้อง rotate key เมื่อใกล้ถึง
Random 96-bit IV ปลอดภัยถึงประมาณ 2³² messages (~4 พันล้าน) ต่อ key
- High-volume system (เช่น log encryption, message queue) → พิจารณา AES-GCM-SIV (RFC 8452) ที่ทน nonce-reuse ได้
- หรือใช้ counter-based IV (deterministic, track ใน DB) — ไม่ใช่ random
- ภาษาอื่น: XChaCha20-Poly1305 (อ่าน เอ็กซ์-ชา-ชา-ทเวนตี้ โพ-ลี-วัน-เธอเรอะ-โอ-ไฟว์) มี 192-bit nonce → random ปลอดภัยมากกว่ามาก
อันตรายของ GCM IV reuse: ไม่ใช่แค่ leak — ถ้า IV ซ้ำกับ key เดียวกัน attacker จะ recover authentication key และ forge ciphertext + decrypt ของเก่าทั้งหมดที่ใช้ key นั้น (catastrophic ไม่ใช่ degraded)
⚠️ IV (Initialization Vector) — กฎ
- ต้องสุ่มใหม่ทุกครั้ง (สำหรับ GCM/CBC) — ถ้าซ้ำ → ความปลอดภัยพัง
- เก็บคู่กับ cipher text (ไม่ลับ — แต่ห้ามซ้ำ)
- GCM IV = 12 bytes (96-bit)
java
// Store format: IV (12 bytes) + cipher + tag
ByteBuffer buf = ByteBuffer.allocate(iv.length + cipherText.length);
buf.put(iv);
buf.put(cipherText);
byte[] combined = buf.array();6. Asymmetric Encryption (Public-Key)
มี 2 key:
- Public key — แจกให้ใครก็ได้ (encrypt + verify signature)
- Private key — เก็บเป็นความลับ (decrypt + sign)
Alice → ขอ public key ของ Bob
Alice → encrypt ข้อความ ด้วย Bob's public key
Bob → decrypt ด้วย private keyAlgorithm (ปี 2026 — เรียงตามที่ "ควรเลือกใช้ก่อน")
Ed25519 (อ่าน "อีดี-ทเวนตี้-ไฟว์-ไฟว์-วัน-ไนน์")
✅ ⭐ recommended สำหรับ signing ในระบบใหม่
— key เล็ก (32 bytes), เร็ว, ไม่มี side-channel pitfall
X25519 / Curve25519 (อ่าน "เคิร์ฟ-ทเวนตี้-ไฟว์-ไฟว์-วัน-ไนน์")
✅ ⭐ recommended สำหรับ key agreement (TLS 1.3, Signal, WireGuard)
ECDSA P-256 / P-384
✅ interop (JWT ES256, smart card, FIPS)
RSA-2048+
⚠️ legacy interop only — slow, key ใหญ่, ยังจำเป็นเพราะ ecosystem (TLS cert ส่วนใหญ่, JWT RS256)
— minimum 2048-bit สำหรับใช้งานปกติ, 4096-bit สำหรับ new high-security
— encryption ใช้ RSA-OAEP, signature ใช้ RSA-PSS (ไม่ใช่ PKCS#1 v1.5)Use Cases
- Encrypt small data (e.g., AES key) — RSA-OAEP (legacy) หรือ ML-KEM (post-quantum, §20)
- Digital signature — Ed25519 (ใหม่), ECDSA (interop), RSA-PSS (legacy)
- TLS handshake — exchange symmetric key ผ่าน X25519 (TLS 1.3 default)
⚠️ RSA ช้า + จำกัด size
RSA encrypt ได้แค่ data ขนาดเล็ก — สูตรขีดจำกัด RSA-OAEP-SHA256:
| Key size | Max plaintext |
|---|---|
| 2048-bit | 190 bytes |
| 3072-bit | 318 bytes |
| 4096-bit | 446 bytes |
สูตร: max_bytes = (key_size / 8) - 2 * hash_len - 2 (4096-bit: 512 - 2*32 - 2 = 446)
📌 ที่มาของสูตร: มาจาก RSA-OAEP padding spec (PKCS#1 v2.2) — ต้องเหลือพื้นที่สำหรับ 2 hash + 2 byte overhead · ไม่ต้องจำสูตร ใช้ตารางพอ ถ้าจะ encrypt ของใหญ่กว่าให้ใช้ hybrid (ดูข้างล่าง)
❌ อย่า encrypt data ใหญ่กว่านี้ตรง ๆ — จะ fail (
IllegalBlockSizeExceptionใน Java) ✅ ใช้ hybrid encryption / envelope encryption (RSA encrypt AES key, AES encrypt data) — ดู §15
→ Pattern จริง: hybrid encryption
1. Generate random AES key
2. Encrypt message ด้วย AES (fast, no size limit)
3. Encrypt AES key ด้วย RSA public key
4. Send: { encrypted_aes_key, encrypted_message }🔍 ภาพรวม hybrid encryption:
ทำไมต้อง 2 step: RSA encrypt data ใหญ่ไม่ได้ + ช้ามาก · AES encrypt data ใหญ่ได้เร็ว แต่ทั้ง 2 ฝ่ายต้อง share key ก่อน — ปัญหาคือจะ share ยังไงปลอดภัย · hybrid = ใช้ RSA แค่ share AES key (เล็ก) + AES ทำ heavy lifting
ตัวอย่าง
⚠️ อ่านก่อน — "RSA/ECB/..." ตรงนี้ "ไม่ใช่" ECB mode ที่ห้ามใช้:
RSA ไม่ได้ใช้ block cipher mode (ECB/CBC/GCM ที่เราพูดถึงใน §5) — RSA เข้ารหัสเป็น "1 block ใหญ่" (modular exponentiation บนเลขทั้งก้อน) ไม่มี chaining ระหว่าง block เลย
ที่ JCA API ใส่คำว่า "ECB" ใน transformation string
RSA/ECB/...เป็น quirk ของ Java เท่านั้น — แปลว่า "single-block RSA operation" ไม่ได้แปลว่าใช้ ECB mode แบบ AES · padding ที่ใช้จริงคือ OAEP กับ SHA-256 + MGF1 ซึ่งปลอดภัยสรุป: เห็น "RSA/ECB/OAEPWith..." ใน Java = OK · เห็น "AES/ECB/..." = ห้ามใช้
java
// Generate key pair
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair pair = kpg.generateKeyPair();
PublicKey pub = pair.getPublic();
PrivateKey priv = pair.getPrivate();
// Encrypt with public (padding = OAEP-SHA256-MGF1 — ปลอดภัย)
Cipher c = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
c.init(Cipher.ENCRYPT_MODE, pub);
byte[] cipherText = c.doFinal("secret".getBytes());
// Decrypt with private
c.init(Cipher.DECRYPT_MODE, priv);
byte[] plainText = c.doFinal(cipherText);7. Digital Signature
digital signature พิสูจน์ 2 อย่างพร้อมกัน: "ข้อความมาจากคนนี้จริง" (authenticity) และ "ไม่ถูกแก้ระหว่างทาง" (integrity) — โดยใช้ private key เซ็น และให้ใครก็ได้ verify ด้วย public key มันคือรากฐานของ JWT, code signing และ cryptocurrency:
Alice ต้องการพิสูจน์ว่า "ข้อความนี้มาจากฉัน + ไม่ถูกแก้":
1. Alice → hash ข้อความ
2. Alice → sign hash ด้วย private key
3. แนบ signature ไปกับข้อความ
Bob verify:
1. hash ข้อความ
2. ใช้ Alice's public key verify signature
3. ถ้าผ่าน → ของ Alice แน่นอน + ไม่ถูกแก้ใช้สำหรับ
- JWT (RS256, ES256)
- Code signing (verify download)
- Email signing (S/MIME, PGP)
- Document signing (PDF)
- Cryptocurrency (Bitcoin tx)
ตัวอย่าง
java
// Sign
Signature signer = Signature.getInstance("SHA256withRSA");
signer.initSign(privateKey);
signer.update(message.getBytes());
byte[] signature = signer.sign();
// Verify
Signature verifier = Signature.getInstance("SHA256withRSA");
verifier.initVerify(publicKey);
verifier.update(message.getBytes());
boolean valid = verifier.verify(signature);8. SecureRandom — Random ที่ปลอดภัย
java
import java.security.SecureRandom;
SecureRandom rnd = new SecureRandom();
// ไม่ต้อง seed เอง — JVM seed อัตโนมัติจาก OS entropy
// (/dev/urandom บน Linux/macOS, CryptGenRandom บน Windows)
byte[] token = new byte[32];
rnd.nextBytes(token);
String tokenStr = Base64.getUrlEncoder().withoutPadding().encodeToString(token);
// "X1B5KqV4yzC..."⚠️ ห้ามใช้ Math.random() หรือ new Random() สำหรับ security
java
// ❌ Predictable
Random r = new Random();
String token = String.valueOf(r.nextLong());
// ✅ Secure
SecureRandom sr = new SecureRandom();
byte[] tokenBytes = new byte[32];
sr.nextBytes(tokenBytes);ทำไม? — Random ใช้ seed → predict ได้ถ้าเดา seed เจอ
Token Length
Session token: 128-bit (16 bytes)
API key: 128-bit
Reset token: 256-bit (32 bytes) — high security
Encryption key: 128 / 256-bit
UUID v4: 128-bit9. Key Derivation
"ทำ key จาก password" — ห้ามใช้ password ตรง ๆ เป็น encryption key
Password → KDF (Key Derivation Function) → encryption key
↑
slow + memory-hard📒 คำอ่าน: KDF = "เค-ดี-เอฟ", PBKDF2 = "พี-บี-เค-ดี-เอฟ-ทู", Argon2id = "อาร์-กอน-ทู-ไอ-ดี", bcrypt = "บี-คริปต์", scrypt = "เอส-คริปต์"
Algorithm (เรียงตามที่ "ควรเลือกใช้ก่อน" ปี 2026 — OWASP 2024)
- ✅ Argon2id ⭐ — modern, memory-hard, OWASP 2024 primary recommendation สำหรับ password hashing
- ✅ scrypt — memory-hard, acceptable alternative
- ✅ bcrypt cost 12+ — ยังใช้ได้ (timing target 250-500ms บน CPU 2026, ไม่ใช่ 30-100ms แบบสมัยก่อน)
- ⚠️ PBKDF2 — legacy / FIPS-mandated only สำหรับ system ใหม่ที่ไม่ผูกกับ FIPS ใช้ Argon2id แทน
java
// PBKDF2 — ใช้เฉพาะเมื่อต้อง compat กับระบบเก่า/FIPS
// ตัวอย่างนี้ไว้ดู API ของ JCA เป็น reference
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(
password.toCharArray(),
salt,
600_000, // iterations — OWASP Password Storage Cheat Sheet (2024)
256 // key length (bits)
);
SecretKey key = factory.generateSecret(spec);📒 ทำไม class เยอะ: Java JCA API design มาแบบ "factory + spec" —
SecretKeyFactoryคือ "โรงงานสร้าง key",KeySpecคือ "สูตรการสร้าง" (params),PBEKeySpec= "password-based encryption" spec · จำ pattern นี้: factory.generateSecret(spec) → SecretKey
⚠️ iteration count (last reviewed: 2026-06): OWASP Password Storage Cheat Sheet (2024) แนะนำ:
- Argon2id — m=19MB, t=2, p=1 (default ของ libsodium ใหม่)
- PBKDF2-HMAC-SHA256: ขั้นต่ำ 600,000 iterations
- PBKDF2-HMAC-SHA512: ขั้นต่ำ 210,000 iterations
- bcrypt: cost factor 12+ (timing target ~250-500ms บน 2026 server CPU — ไม่ใช่ 30-100ms ที่เคยใช้ปี 2010)
- source: OWASP Password Storage Cheat Sheet — เช็คค่าล่าสุดก่อน deploy เพราะ recommended count เพิ่มขึ้นทุก ๆ 2-3 ปี
ใช้สำหรับ: password manager, encryption with password
Part 2: TLS — Transport Layer Security
10. TLS ทำงานยังไง (พื้นฐาน)
อธิบาย step-by-step ภาษามนุษย์:
- ClientHello — เบราว์เซอร์บอก server ว่า "ฉันรองรับ cipher/version พวกนี้ + ส่ง random number มาด้วย"
- ServerHello — server ตอบกลับ "งั้นเลือก cipher นี้ + ส่ง random number ของฉัน"
- Certificate — server ส่ง certificate (ใบรับรอง) ของตัวเองมา → client เช็คว่า CA ที่ออก cert เชื่อถือได้ไหม
- ServerKeyExchange — server ส่ง "กุญแจ ECDHE ชั่วคราว" (public key สำหรับ session นี้เท่านั้น) มา
- ClientKeyExchange — client ส่ง "กุญแจ ECDHE ชั่วคราว" ของตัวเองกลับไป
- ทั้งสองฝั่งใช้ "private ของตัวเอง + public ของคู่" คำนวณ shared secret ออกมา (Diffie-Hellman magic — ดักได้แต่ถอดไม่ได้)
- Finished — encrypted ด้วย shared secret → จากนี้ทุกอย่าง encrypted
📌 ใน TLS 1.3 steps จะรวบเหลือ 1 round-trip (0-RTT ในบางกรณี) เพราะ client ส่ง keyshare ตั้งแต่ ClientHello เลย
TLS Version
📅 ตารางนี้ last reviewed: 2026-06 — ตรวจ Mozilla SSL Config Generator ก่อน deploy production: https://ssl-config.mozilla.org/
| Version | สถานะ (as of 2026-Q2) |
|---|---|
| TLS 1.3 | ✅ mandatory สำหรับระบบใหม่ — NIST SP 800-52 Rev 3 (Aug 2024) แนะนำ; HSTS preload list บังคับ |
| TLS 1.2 | ⚠️ legacy compat only — formally deprecated สำหรับ deployment ใหม่ (RFC 8996, March 2021); IETF discussion ลด support ตั้งแต่ปี 2024 · เก็บไว้เฉพาะ backward compat กับ client เก่า (older browsers, IoT) · เฉพาะกับ AEAD cipher (AES-GCM, ChaCha20-Poly1305) ห้าม CBC mode |
| TLS 1.1 / 1.0 | ❌ disable เด็ดขาด — browsers dropped 2020 (Chrome 84, Firefox 78), PCI-DSS deprecated since 2018 |
| SSL 3.0 / SSL 2.0 | ❌ broken (POODLE 2014, DROWN 2016) — ห้ามมีในระบบ |
📌 ระวัง overstate: PCI-DSS 4.0.1 (มีผล 2024-06) บังคับ "strong cryptography" ไม่ได้บังคับ TLS 1.3 specifically — TLS 1.2 ยังผ่าน PCI ถ้า config cipher suite ถูก
AEAD = Authenticated Encryption with Associated Data — เข้ารหัส + ตรวจความถูกต้องในก้อนเดียว (เช่น AES-GCM, ChaCha20-Poly1305)
ตัวอย่าง nginx config (แนะนำเริ่มต้นจาก Mozilla SSL Config Generator — "intermediate" profile, reviewed 2026-06):
nginx
# nginx 1.19.4+ (Aug 2020) — รองรับ ssl_conf_command ที่จำเป็นสำหรับ TLS 1.3 cipher selection
ssl_protocols TLSv1.3 TLSv1.2;
# ssl_ciphers = ใช้สำหรับ TLS 1.2 เท่านั้น (TLS 1.3 ignore directive นี้)
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
# ssl_conf_command Ciphersuites = ใช้สำหรับ TLS 1.3 (OpenSSL 1.1.1+)
# default ของ OpenSSL ก็ปลอดภัยอยู่แล้ว — ระบุชัดเจนเพื่อ explicit
ssl_conf_command Ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256;
ssl_prefer_server_ciphers off; # TLS 1.3 ignore directive นี้อยู่แล้ว⚠️ bug ที่พบบ่อย: ใส่
TLS_AES_256_GCM_SHA384(cipher ของ TLS 1.3) ลงssl_ciphersdirectly — nginx จะ warn/ignore เพราะ directive นั้นเป็นของ TLS 1.2 เท่านั้น สำหรับ TLS 1.3 ต้องใช้ssl_conf_command Ciphersuites
Cipher Suite
ชื่อ cipher suite แบบ TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ตอนแรกอ่านแล้วเหมือนรหัสรถถัง — จริง ๆ มันคือ "ส่วนผสม 5 ขั้น" เขียนต่อกัน:
TLS 1.2 format — 5 ส่วน:
🏷️ TLS | 🔑 ECDHE | 🪪 RSA | 🔒 AES_256_GCM | #️⃣ SHA384
prefix | key exchange | authentication | symmetric (AEAD) | hash for MAC/PRF
| (forward | (cert sig algo)| |
| secrecy!) | | |
อ่านเป็นประโยค: "TLS ใช้ ECDHE ตกลง key, RSA เซ็น cert, AES-256 GCM เข้ารหัสข้อมูล, SHA384 ทำ MAC"
TLS 1.3 format — เหลือ 2 ส่วน (สั้นกว่ามาก):
🏷️ TLS | 🔒 AES_256_GCM | #️⃣ SHA384
prefix | symmetric (AEAD) | hash for PRF / HKDF📌 ทำไม TLS 1.3 cipher suite สั้นกว่า:TLS 1.3 ตัด key exchange (ECDHE บังคับ) และ authentication (กำหนดผ่าน
signature_algorithmsextension แยก) ออกจาก cipher suite — เหลือแค่ AEAD + hash function เท่านั้น ทำให้ negotiate ง่ายและลด combination ลงเหลือไม่กี่ตัว
Forward Secrecy (ความลับย้อนหลังปลอดภัย)
Forward secrecy = แม้ private key ของ server หลุดในอนาคต ข้อมูลที่ดักเก็บไว้ก่อนหน้านี้ก็ยัง decrypt ไม่ได้ — เพราะแต่ละ session ใช้กุญแจชั่วคราวคนละตัว
ใช้ ECDHE (อ่าน "อี-ซี-ดี-เอช-อี" — Elliptic-Curve Diffie–Hellman Ephemeral — สร้าง key ชั่วคราวบน elliptic curve) หรือ DHE (อ่าน "ดี-เอช-อี" — Diffie–Hellman Ephemeral — เวอร์ชันไม่ใช้ EC; ช้ากว่าและต้องใช้ key ใหญ่กว่ามาก) → ถ้า private key ของ server ถูกขโมย → session เก่า ๆ ก็ยัง decrypt ไม่ได้
ห้ามใช้ static RSA (RSA แบบกุญแจตายตัว) — ไม่มี forward secrecy (กุญแจหลุดทีเดียวถอดได้หมด)
📖 คำย่อ + คำอ่าน:
- DH = Diffie–Hellman (อ่าน "ดิฟ-ฟี่-เฮลล์-แมน" — ชื่อนักคณิตศาสตร์ 2 คน) วิธีตกลง shared key ผ่าน channel เปิด
- E = Ephemeral (อ่าน "เอ-เฟ-เมอ-รัล" = ชั่วคราว — สร้างใหม่ทุก session)
- EC = Elliptic Curve (ใช้คณิตศาสตร์ของ elliptic curve → key สั้นกว่าและเร็วกว่า)
11. Certificate
X.509 Format
Certificate:
Subject: CN=api.example.com
Issuer: CN=Let's Encrypt R3
Valid: 2026-01-01 to 2026-04-01 (90 days — see note below)
Public Key: <key>
Signature: <signed by issuer>
SAN (Subject Alternative Names):
DNS:api.example.com
DNS:www.example.comChain of Trust
Root CA (trusted, in browser)
↓ signs
Intermediate CA
↓ signs
Your server certificateBrowser มี root CA list — verify chain ขึ้นไป
📌 Certificate validity ปี 2026 — อย่าจำ "90 วัน" เป็นค่าตายตัว:
- Let's Encrypt: 90 วัน default + 6 วัน short-lived certs (beta 2025)
- CA/B Forum SC-081 (passed 2025) บังคับลด max cert validity เหลือ 47 วัน ภายในมีนาคม 2029
- Let's Encrypt ประกาศจะ phase out 90-day certs โดย ~Sep 2026 → ใช้ ACME v2 automation ตั้งแต่ตอนนี้
CT logs (Certificate Transparency): browser ทุกตัวบังคับแล้ว — cert ที่ issue ทุกใบต้องเข้า public log (helps detect rogue CA)
Get Certificate
bash
# certbot = client ของ Let's Encrypt — รันบรรทัดเดียวก็ได้ cert ฟรี + auto renew
# -w (webroot) = path ที่จะวาง challenge file ให้ Let's Encrypt มา verify domain ownership
# -d (domain) = domain ที่จะออก cert ให้
certbot certonly --webroot -w /var/www/html -d api.example.com
# ทางเลือกอื่น (ACME v2 ecosystem):
# - cert-manager (Kubernetes)
# - Smallstep CA (private CA + ACME ภายในองค์กร)
# - acme.sh (lightweight shell client)
# Internal CA สำหรับ internal service
openssl req ...Pinning
App ตรวจว่า certificate มาจาก specific CA / public key:
Public key pinning (mobile app)
→ ป้องกัน rogue CA + MITM⚠️ Risky — ถ้า pin หาย → app ทุกตัวที่ deploy แล้วใช้ไม่ได้
12. mTLS — Mutual TLS
TLS ปกติ client ตรวจ server ฝ่ายเดียว (เช่นเบราว์เซอร์ตรวจว่าเว็บคือของจริง) — mTLS เพิ่มให้ server ตรวจ client ด้วย ทั้งสองฝั่งจึงพิสูจน์ตัวตนกันและกัน เหมาะกับ service-to-service ภายใน, ระบบ high-security และ zero-trust network:
ปกติ TLS = client verify server
mTLS = ทั้ง client + server verify กัน
Server cert ← client verify
Client cert → server verifyใช้กับ:
- Service-to-service (internal microservices)
- Banking / high security
- Zero-trust network
yaml
# Spring Boot
server:
ssl:
key-store: server.p12
key-store-password: ...
trust-store: clients-ca.p12 # CA ที่ออก cert ให้ client
trust-store-password: ... # optional — truststore เก็บ public cert ของ CA เท่านั้น
# ไม่จำเป็นต้องใส่ password (เว้นแต่ truststore เก็บ private key)
client-auth: need # บังคับ client cert (ทางเลือก: want = optional, none = ปิด)Part 3: Key Management
13. Key Lifecycle
key ทาง crypto ไม่ใช่ของที่สร้างทิ้งไว้ตลอดกาล — มันมี "วงจรชีวิต" ตั้งแต่ generate → distribute → use → rotate → revoke → destroy การจัดการแต่ละขั้นให้ปลอดภัยสำคัญพอ ๆ กับ algorithm เพราะ key ที่หลุดทำให้ crypto ทั้งหมดไร้ความหมาย:
1. Generate
2. Distribute (secure)
3. Use
4. Rotate (periodically)
5. Backup (encrypted)
6. Revoke (when compromise)
7. Destroy (secure wipe)14. Key Storage
❌ ห้าม
ใส่ใน code:
private static final String KEY = "abc123";
ใน config file ที่ commit:
key: abc123
ใน environment variable plain:
KEY=abc123 (ดีกว่า in-code แต่ยังเสี่ยง)✅ Storage ที่ถูก
1. Cloud Secret Manager
- AWS Secrets Manager
- GCP Secret Manager
- Azure Key Vault
2. HashiCorp Vault
- Self-hosted
- Most flexible
3. HSM (Hardware Security Module)
- Physical hardware
- High-security (banking, gov)
- AWS CloudHSM, etc.
4. KMS (Key Management Service)
- Key เก็บใน HSM ของ cloud
- App ใช้ผ่าน API (sign/encrypt/decrypt)
- Key ไม่เคยออกจาก KMSดูบทที่ 5 (Secret Management) สำหรับรายละเอียด
15. Envelope Encryption Pattern (เข้ารหัสแบบซองจดหมาย)
Envelope encryption = "เข้ารหัสซ้อนชั้นแบบซองจดหมาย": ใช้กุญแจตัวเล็ก (DEK) เข้ารหัสข้อมูลจริง แล้วเอากุญแจใหญ่ (Master Key ที่อยู่ใน KMS ออกมาไม่ได้) มาเข้ารหัสกุญแจเล็กอีกที — เหมือนใส่กุญแจไว้ในซองที่ล็อกด้วยกุญแจใหญ่ · KMS = Key Management Service (บริการจัดการกุญแจของ cloud) · DEK = Data Encryption Key (กุญแจที่ใช้เข้ารหัสข้อมูลจริง)
ขั้นตอน encrypt:
1. App ขอ DEK ใหม่จาก KMS
2. KMS return DEK + encrypted DEK
3. App ใช้ DEK encrypt data
4. App store: encrypted DEK + encrypted data
5. App ทิ้ง DEK ออกจาก memoryขั้นตอน decrypt:
1. App ดึง encrypted DEK + data
2. ส่ง encrypted DEK ไป KMS
3. KMS return DEK
4. App ใช้ DEK decrypt dataทำไม?
- ✅ Master key อยู่ใน KMS เสมอ (ไม่หลุด)
- ✅ Rotate master key ง่าย (re-encrypt แค่ DEK ทั้งหมด, ไม่ใช่ data)
- ✅ Performance — KMS call แค่ DEK เล็ก ๆ ไม่ใช่ data ใหญ่
- ✅ Each data has unique DEK
📎 ทำไมต้อง
byte[]ไม่ใช่Stringสำหรับ key/secret ใน Java:
Stringใน Java immutable — เก็บใน String pool/heap, garbage collect เมื่อไหร่ก็ได้, ระหว่างนั้น memory dump อ่านได้Arrays.fill(bytes, (byte) 0)zero ออกได้ทันที = secret หาย จาก memory หลังใช้เสร็จString.replace()ไม่ overwrite — สร้าง String ใหม่ ตัวเก่ายังลอยใน heap- กฎทอง: secret/password ใช้
char[]หรือbyte[]เสมอ + zeroize หลังใช้⚠️ JVM modern caveat: GC (ZGC, G1, Shenandoah) อาจ copy bytes ระหว่าง region ระหว่าง compact — zeroize 1 จุดอาจไม่ลบ copy เก่าใน old region ทันที สำหรับ high-security (banking, HSM-class) ใช้:
ByteBuffer.allocateDirect()(off-heap, GC ไม่ย้าย) — แต่ต้อง zeroize ด้วยตัวเองและ free ผ่าน cleanerMemorySegmentของ Foreign Memory API (Java 22+) แทนsun.misc.Unsafe- Hardware-backed key store (HSM, AWS KMS, Cloud HSM) ที่ key ไม่ลงมาใน JVM memory เลย
⚠️
SecretKeySpecก็ COPY bytes: ตอนสร้างnew SecretKeySpec(plaintextDek, "AES")constructor จะ copyplaintextDekลง field ภายใน · zeroizeplaintextDekตัวนอกไม่ลบ copy ภายในของ SecretKeySpec · ถ้าต้องการลบจริง ๆ ต้องเรียก.destroy()(ถ้า provider implement — ไม่ใช่ทุก provider implement)
16. Key Rotation
key ที่ใช้นาน ๆ เสี่ยงหลุดและถูกเจาะมากขึ้นเรื่อย ๆ จึงต้อง "หมุนเปลี่ยน" (rotate) ตามรอบ — แต่เปลี่ยนทื่อ ๆ จะทำให้ข้อมูลเก่าถอดไม่ออก เคล็ดลับคือรองรับหลาย key พร้อมกัน (เก่า verify ได้, ใหม่ใช้เขียน) แล้วค่อย ๆ re-encrypt ส่วนนี้สอน rotation แบบไม่มี downtime:
Schedule:
- KMS master key: 1 year
- Application encryption key: 90 days
- API key: 90 days
- JWT signing key: 30-90 days
- Database password: 90 days
- TLS certificate: Let's Encrypt = 90 วัน (auto renew)Rotation Without Downtime
1. Generate new key (key_v2)
2. App รองรับ ทั้ง key_v1 + key_v2
3. Re-encrypt ข้อมูลด้วย v2 (background)
4. Switch new write → v2
5. หลังจาก verify ทั้งหมด → remove v1JWT Rotation
java
// Multiple keys ที่ active
@Bean
public JWKSource<SecurityContext> jwkSource() {
return (selector, context) ->
keyRepo.findActive().stream()
.map(JWK::parse)
.collect(Collectors.toList());
}→ Token เก่า verify ได้ + Token ใหม่ใช้ key ใหม่
17. Encryption at Rest vs in Transit
ข้อมูลต้องถูกปกป้องในทุก "สถานะ" — ระหว่างส่งผ่าน network (in transit, ใช้ TLS), ตอนเก็บใน disk (at rest, ใช้ TDE/disk encryption) และล่าสุดแม้ตอน CPU ประมวลผล (in use, confidential computing) ส่วนนี้แยกแต่ละสถานะและเครื่องมือที่ใช้:
In Transit
ระหว่างส่งผ่าน network → ใช้ TLS
- HTTPS for API
- TLS for DB connection
- VPN for service-to-serviceAt Rest
เก็บใน disk → encrypted
- Database TDE (Transparent Data Encryption)
- Disk encryption (LUKS, BitLocker)
- S3 encryption (SSE-S3, SSE-KMS)
- Application-level encryption (very sensitive)In Use (newer)
ตอน CPU process ก็ encrypted
- Intel SGX, AMD SEV
- Confidential Computing18. ⚠️ Crypto Pitfalls
crypto เป็นเรื่องที่ "พลาดนิดเดียวพังหมด" — ความผิดพลาดที่เจอบ่อยคือเขียน algorithm เอง (อย่าทำ!), ใช้ ECB mode, reuse IV/nonce, หรือใช้ Math.random() แทน secure random ส่วนนี้รวมกับดักพร้อมโค้ดที่ถูก/ผิด ให้เลี่ยงตั้งแต่ต้น:
1. Roll Your Own Crypto
java
// ❌
public String encrypt(String text, String key) {
char[] result = new char[text.length()];
for (int i = 0; i < text.length(); i++) {
result[i] = (char) (text.charAt(i) ^ key.charAt(i % key.length()));
}
return new String(result);
}
// ✅ ใช้ AES-GCM จาก JDK2. ECB Mode
java
// ❌
Cipher.getInstance("AES/ECB/PKCS5Padding");
// ✅
Cipher.getInstance("AES/GCM/NoPadding");3. IV ซ้ำ
java
// ❌
byte[] iv = "1234567890123456".getBytes(); // ทุก call ใช้ IV เดียวกัน
// ✅
byte[] iv = new byte[12];
new SecureRandom().nextBytes(iv);4. Math.random for Token
java
// ❌
String token = String.valueOf(Math.random()).substring(2);
// ✅
SecureRandom sr = new SecureRandom();
byte[] tokenBytes = new byte[32];
sr.nextBytes(tokenBytes);5. Hardcoded Key
java
// ❌
private static final String SECRET = "my-secret-2026";
// ⚠️ ดีขึ้นนิดหน่อย แต่ยังไม่ ideal
private static final String SECRET = System.getenv("SECRET");
// env var ใน JVM ยัง leak ได้ผ่าน /proc/<pid>/environ, heap dump, error message
// ✅ ดีกว่า: inject ตอน runtime จาก Vault / AWS Secrets Manager / KMS
// + cache ใน char[]/byte[] + zeroize หลังใช้6. Timing Attack
java
// ❌
if (provided.equals(stored)) { ... } // early exit by character
// ✅
if (MessageDigest.isEqual(provided.getBytes(), stored.getBytes())) { ... }7. Padding Oracle (การโจมตีผ่าน error ของ padding)
Padding = ไบต์เติมท้ายข้อมูลให้ครบขนาด block (เช่น AES block = 16 bytes ถ้าข้อมูลเหลือ 13 bytes ต้องเติม 3 bytes ให้ครบ)
Padding oracle เปรียบเทียบเหมือน แม่กุญแจที่ส่งเสียง "คลิ๊ก" บอกว่ารหัสล็อกถูก/ผิด — ขโมยไม่ต้องรู้รหัสจริง แค่ลองหมุนทีละหลัก ฟังเสียง "คลิ๊ก" เดาทีละหลักได้
ในโลก crypto: ถ้าระบบตอบ attacker ได้ว่า "padding ถูกหรือผิด" (ผ่าน error message, HTTP status, response time) → attacker ส่ง ciphertext ที่แก้ทีละ byte แล้วฟัง "เสียงคลิ๊ก" → ค่อย ๆ เดารหัสได้ทีละ byte จนได้ plaintext ทั้งหมด — โดยไม่ต้องรู้ key เลย
ใช้ CBC mode ที่ leak (เผยให้รู้) padding error → attacker decrypt ได้ทีละ byte
→ ใช้ GCM (authenticated encryption — เข้ารหัสที่ตรวจความถูกต้องในตัว) แทน ปลอดภัยกว่า
🛠️ ถ้ายังต้อง support CBC (legacy / interop):
- ใช้ encrypt-then-MAC pattern: คำนวณ
HMAC(key_mac, IV || ciphertext)ครอบ ciphertext แล้ว verify MAC ก่อน decrypt- ถ้า MAC verify fail → reject ทันที ก่อน ทำ padding check (ไม่ leak padding error)
- ใช้ constant-time padding verification + return error เดียวกันสำหรับ padding error และ MAC error
- key สำหรับ encrypt และ MAC ต้องคนละตัว (derive ผ่าน HKDF)
8. Weak Random Seed
java
// ❌
Random r = new Random(System.currentTimeMillis()); // attacker เดา seed ได้
// ✅
SecureRandom r = new SecureRandom(); // seed จาก /dev/urandom9. Key Reuse Across Purpose
❌ ใช้ key เดียวกัน encrypt + sign
✅ Generate key ต่างสำหรับแต่ละ purpose (encryption key, signing key, ...)10. Long Key Lifetime
❌ JWT signing key อายุ 5 ปี
✅ Rotate 90 วัน19. Modern Cryptography ที่ควรรู้
Argon2id — Password Hashing
ดูบทที่ 2
Ed25519 — Signing
(อ่าน "อีดี-ทเวนตี้-ไฟว์-ไฟว์-วัน-ไนน์" — "Edwards curve 25519")
- เร็ว, ปลอดภัย, key เล็ก (32 bytes)
- ใช้แทน RSA / ECDSA
X25519 / Curve25519 — Key Exchange
(อ่าน "เอ็กซ์-ทเวนตี้-ไฟว์-ไฟว์-วัน-ไนน์" / "เคิร์ฟ-ทเวนตี้-ไฟว์-ไฟว์-วัน-ไนน์")
- ใช้ใน TLS 1.3, Signal, WireGuard
ChaCha20-Poly1305
(อ่าน "ชา-ชา-ทเวนตี้ โพ-ลี-วัน-เธอเรอะ-โอ-ไฟว์")
- Symmetric encryption ทางเลือกของ AES-GCM
- เร็วบน mobile/ARM (ไม่ต้องการ hardware AES instruction)
libsodium / NaCl
(อ่าน "ลิบ-โซ-เดียม" / "แนกเคิล" — ตั้งใจเล่นคำกับ NaCl = เกลือทางเคมี · อ่านเป็น "salt" ก็ได้)
- High-level crypto library
- Hard to misuse — แนะนำใช้ในภาษาที่มี wrapper
- Java:
lazysodium-java
java
// libsodium-style
byte[] encrypted = SecretBox.encrypt(message, key);
// → no choice of mode / IV — library handle20. Post-Quantum Cryptography ⭐ (Update 2024-2026)
🟡 ขั้นสูง — section นี้สำหรับคนที่เข้าใจ RSA + ECDH แล้ว
Post-Quantum cryptography (PQC) = crypto ที่ทนต่อ quantum computer ในอนาคต basic ที่ยังไม่คุ้น RSA/ECDH: อ่านเอาความรู้สึก — NIST finalize standards แล้ว (FIPS 203/204/205, ส.ค. 2024) + Chrome/Cloudflare deploy hybrid TLS ตั้งแต่ 2024
สิ่งสำคัญที่ basic ต้องรู้: "เก็บกักไว้ก่อน-ถอดทีหลัง" (Harvest now, decrypt later) — attacker เก็บ data ที่ encrypt ไว้ตอนนี้ รอ quantum computer พัฒนาแล้ว decrypt ทีหลัง · data ที่ sensitive ระยะยาว (medical record 50 ปี, รหัสลับรัฐ, แผนธุรกิจ) เริ่ม migrate ตั้งแต่ตอนนี้
Threat: Quantum Computer Will Break Current Crypto
ปัญหาที่ใกล้:
- Quantum computer (ขนาดพอ — ~4,000 logical qubits) จะ break:
- RSA (Shor's algorithm)
- ECDSA, ECDH
- Diffie-Hellman
- ยังไม่มี quantum computer ที่ใหญ่พอตอนนี้
- คาดการณ์ Q-Day: ~2030-2040📒 qubit (อ่าน "คิว-บิต") = "quantum bit" = หน่วยข้อมูลของ quantum computer · ต่างจาก bit ทั่วไปที่เป็น 0 หรือ 1 อย่างเดียว — qubit อยู่ในสถานะ superposition (เป็น 0 และ 1 พร้อมกัน) จนกว่าจะวัด · "logical qubit" = qubit ที่ผ่าน error correction แล้ว ใช้ physical qubits จำนวนมาก (~1000:1) ทำให้ใช้งานได้จริง
NIST Standards — FIPS 203, 204, 205 (สิงหาคม 2024)
NIST ออก post-quantum standards อย่างเป็นทางการในเดือนสิงหาคม 2024:
| FIPS | Algorithm (เคยชื่อ) | จุดประสงค์ |
|---|---|---|
| FIPS 203 | ML-KEM (เคยชื่อ Kyber — อ่าน "ไค-เบอร์") | Key encapsulation (KEM) — แทน ECDH key exchange (RSA-OAEP encryption ก็ถูกทยอย phase out คนละทาง) |
| FIPS 204 | ML-DSA (เคยชื่อ Dilithium — อ่าน "ดิ-ลิ-เธียม") | Digital signature — แทน RSA-PSS, ECDSA |
| FIPS 205 | SLH-DSA (เคยชื่อ SPHINCS+ — อ่าน "สฟิงซ์-พลัส") | Stateless hash-based signature (slow + secure backup) |
📒 คำอ่าน + ความหมายชื่อย่อ:
- ML-KEM = "Module-Lattice-based Key Encapsulation Mechanism"
- ML-DSA = "Module-Lattice-based Digital Signature Algorithm"
- SLH-DSA = "Stateless Hash-based Digital Signature Algorithm"
- KEM (Key Encapsulation Mechanism) ≠ encryption โดยตรง — KEM คือ "ห่อ shared key ส่ง" คล้าย Diffie-Hellman มากกว่าคล้าย RSA-OAEP
"เก็บกักไว้ก่อน-ถอดทีหลัง" (Harvest Now, Decrypt Later) Attack
สถานการณ์อันตราย:
1. ปี 2026 — attacker เก็บ TLS traffic ที่ encrypted ของคุณ
2. รอ ~10 ปี ให้ quantum computer พร้อม
3. ปี 2035 — decrypt traffic ที่เก็บไว้
4. → ข้อมูลที่ confidential ตอนนี้ → หลุดในอนาคต
→ ข้อมูลที่ต้อง confidential > 10 ปี ต้องเริ่มเปลี่ยนแล้ว!Migration Strategy (ปี 2026)
1. ระบุข้อมูลที่ต้องเก็บลับ > 10 ปี:
- Medical records
- Government secrets
- Long-term legal documents
- Strategic business plans
2. Hybrid mode (interim):
- ใช้ classic (ECDH/RSA) + PQ (ML-KEM) ผสมกัน
- กัน both quantum AND classical attack
- Cloudflare, Google Chrome ใช้แล้ว (X25519+Kyber768)
3. Library support:
- OpenSSL 3.5+ — ML-KEM (ปี 2025)
- BoringSSL — รองรับ
- liboqs (Open Quantum Safe) — research lib
- AWS/Cloudflare — already hybrid TLS
4. Code update:
- ปกติ apps ใช้ TLS library — update lib = ได้ PQ ฟรี ๆ
- ระบบที่ใช้ crypto โดยตรง — ต้องเปลี่ยน APIปี 2026 — Status
✅ Big tech adopt:
- Apple iMessage PQ3 (2024)
- Signal — PQXDH key agreement
- Cloudflare — hybrid PQ TLS by default
- Google Chrome — X25519MLKEM768
⚠️ Library ยังกำลังพัฒนา:
- JCE / BouncyCastle — เพิ่ม ML-KEM, ML-DSA แล้ว
- libsodium — กำลังประเมิน
🔴 Avoid:
- PQ algorithm ที่ "ไม่ใช่ NIST standard"
- Premature deployment (ก่อน 2024 — ใช้ algo ที่ถูก ถอนออก)Practical takeaway ปี 2026 — ต้องเริ่มแล้ว ไม่ใช่ "ยังไม่ต้องรีบ":
- Cloudflare + Chrome > 50% ของ TLS handshake บนอินเทอร์เน็ตเป็น X25519MLKEM768 hybrid แล้ว (ตั้งแต่ปลาย 2024)
- NIST CSRC: deprecate RSA-2048 / ECC-P256 สำหรับ federal use ภายในปี 2030 · disallow หลัง 2035
- ของใหม่: เริ่มใช้ hybrid TLS (X25519+ML-KEM-768) ทันที + วางแผน migrate signature เป็น ML-DSA
- ของเก่า: ระบุ data ที่ confidential > 10 ปี + ทยอย re-encrypt
📌 X25519MLKEM768 = hybrid TLS 1.3 key share ที่ Chrome/Cloudflare ใช้ default ปี 2024+ — ผสม classic X25519 + post-quantum ML-KEM-768 ผ่าน HKDF
ลองเล่นด้วย BouncyCastle ML-KEM (Kyber)
(BouncyCastle อ่าน "เบาน์-ซี่-คาส-เซิ่ล" — แปลตรงตัว "ปราสาทเด้งดึ๋ง")
⚠️ API ยังไม่ stable — bcprov-jdk18on 1.78+ ใส่ ML-KEM/ML-DSA แล้ว แต่ namespace และ class signature อาจเปลี่ยนใน minor release ตรวจ BC release note ก่อนใช้ production (last reviewed: 2026-06)
java
// ⚠️ ตัวอย่างนี้ optional — ต้อง Java 21+ เพราะ javax.crypto.KEM API ถูกเพิ่มใน JEP 452 (Java 21)
// Java 17 (LTS เก่า) จะ fail compile · ต้อง upgrade JDK หรือใช้ low-level API ของ BC ตรง ๆ
//
// dependency: org.bouncycastle:bcprov-jdk18on:1.78+
// Security.addProvider(new BouncyCastleProvider());
import org.bouncycastle.jcajce.spec.MLKEMParameterSpec;
import javax.crypto.KeyGenerator;
import javax.crypto.KEM;
import javax.crypto.SecretKey;
import java.security.KeyPairGenerator;
import java.security.KeyPair;
// 1. Generate ML-KEM-768 key pair (security level ~AES-192)
KeyPairGenerator kpg = KeyPairGenerator.getInstance("ML-KEM", "BC");
kpg.initialize(MLKEMParameterSpec.ml_kem_768);
KeyPair kp = kpg.generateKeyPair();
// 2. Sender: encapsulate → shared secret + ciphertext
KEM kemSender = KEM.getInstance("ML-KEM", "BC");
KEM.Encapsulator enc = kemSender.newEncapsulator(kp.getPublic());
KEM.Encapsulated encapsulated = enc.encapsulate();
SecretKey sharedSecretSender = encapsulated.key();
byte[] ciphertext = encapsulated.encapsulation(); // ส่งไปฝั่ง receiver
// 3. Receiver: decapsulate ด้วย private key → shared secret เดียวกัน
KEM kemReceiver = KEM.getInstance("ML-KEM", "BC");
KEM.Decapsulator dec = kemReceiver.newDecapsulator(kp.getPrivate());
SecretKey sharedSecretReceiver = dec.decapsulate(ciphertext);
// 4. ใช้ sharedSecret เป็น key สำหรับ AES-GCM symmetric encryption
// (hybrid: ผสมกับ ECDH shared secret ผ่าน HKDF — อย่าใช้ ML-KEM เดี่ยวๆ)📌 แนวทาง hybrid (recommended 2026): ผสม shared secret ของ ECDH (X25519) + ML-KEM-768 ผ่าน HKDF — ถ้า PQC algorithm ตัวใดตัวหนึ่งโดน break ในอนาคต อีกตัวยัง protect อยู่ Cloudflare/Apple/Signal ใช้ pattern นี้
ตัวอย่าง hybrid (pseudo-code — combine ใน practice):
java// 1. X25519 classic KEX byte[] classicSecret = x25519Agreement(myEcPriv, peerEcPub); // 32 bytes // 2. ML-KEM-768 PQ KEM (ตัวอย่างจากบล็อกข้างบน) byte[] pqSecret = sharedSecretSender.getEncoded(); // 32 bytes // 3. รวมผ่าน HKDF — ถ้าทั้ง 2 ตัวยังปลอดภัย → final key ปลอดภัย // ถ้าตัวใดตัวหนึ่งโดน break (เช่น X25519 พังจาก quantum) → อีกตัวยังกัน byte[] combined = concat(classicSecret, pqSecret); byte[] finalKey = HKDF.expand(HKDF.extract(salt, combined), info, 32); // → finalKey ใช้กับ AES-GCM session
21. ตัวอย่างจริง — File Encryption Service
ปิดท้ายด้วยการรวมทุกแนวคิดเป็นโค้ดจริง — service เข้ารหัสไฟล์ที่ใช้ envelope encryption (ขอ DEK จาก KMS, เข้ารหัส data ด้วย DEK, แล้วเก็บ DEK ที่ถูกเข้ารหัสไว้กับไฟล์) ตัวอย่างนี้แสดง pattern ที่ production จริงใช้ ไม่ใช่ของเล่น:
🟡 prerequisite: ตัวอย่างนี้ใช้ AWS SDK for Java v2 (Builder pattern,
SdkBytes) — ถ้าไม่เคยใช้ AWS ให้อ่านเอาหลักการ envelope encryption แทน · API ของ GCP KMS / Azure Key Vault โครงสร้างคล้ายกัน (request → response → asByteArray)
java
// ใช้ AWS SDK for Java v2 — software.amazon.awssdk:kms
// dependency: software.amazon.awssdk:kms:2.25.x+
import software.amazon.awssdk.services.kms.KmsClient;
import software.amazon.awssdk.services.kms.model.GenerateDataKeyRequest;
import software.amazon.awssdk.services.kms.model.GenerateDataKeyResponse;
import software.amazon.awssdk.services.kms.model.DecryptRequest;
import software.amazon.awssdk.services.kms.model.DecryptResponse;
import software.amazon.awssdk.services.kms.model.DataKeySpec;
import software.amazon.awssdk.core.SdkBytes;
@Service
public class FileEncryptionService {
private final KmsClient kms;
private final String kmsKeyId; // "alias/my-app" หรือ ARN
public FileEncryptionService(KmsClient kms, String kmsKeyId) {
this.kms = kms;
this.kmsKeyId = kmsKeyId;
}
public EncryptedFile encrypt(byte[] data, String contextId) throws Exception {
// 1. ขอ DEK จาก KMS (AWS SDK v2 — Builder pattern)
GenerateDataKeyResponse dataKey = kms.generateDataKey(
GenerateDataKeyRequest.builder()
.keyId(kmsKeyId)
.keySpec(DataKeySpec.AES_256)
.build()
);
byte[] plaintextDek = dataKey.plaintext().asByteArray();
SecretKey dek = new SecretKeySpec(plaintextDek, "AES");
try {
// 2. Random IV
byte[] iv = new byte[12];
new SecureRandom().nextBytes(iv);
// 3. Encrypt data ด้วย DEK — ผูก context ผ่าน AAD
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, dek, new GCMParameterSpec(128, iv));
cipher.updateAAD(contextId.getBytes(StandardCharsets.UTF_8));
byte[] cipherText = cipher.doFinal(data);
// 4. Return: IV + encrypted DEK + cipher + context
return new EncryptedFile(
iv,
dataKey.ciphertextBlob().asByteArray(), // encrypted DEK
cipherText,
contextId
);
} finally {
// 5. ทิ้ง DEK plaintext ออกจาก memory ทุกกรณี (รวม exception path)
Arrays.fill(plaintextDek, (byte) 0);
}
}
public byte[] decrypt(EncryptedFile encrypted) throws Exception {
// 1. Decrypt DEK ผ่าน KMS
DecryptResponse decryptRes = kms.decrypt(
DecryptRequest.builder()
.ciphertextBlob(SdkBytes.fromByteArray(encrypted.encryptedDek()))
.build()
);
byte[] dekBytes = decryptRes.plaintext().asByteArray();
SecretKey dek = new SecretKeySpec(dekBytes, "AES");
try {
// 2. Decrypt data — ต้อง updateAAD ตัวเดียวกับตอน encrypt
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, dek, new GCMParameterSpec(128, encrypted.iv()));
cipher.updateAAD(encrypted.contextId().getBytes(StandardCharsets.UTF_8));
return cipher.doFinal(encrypted.cipherText());
} finally {
Arrays.fill(dekBytes, (byte) 0);
}
}
}📌 API ที่ใช้จริงใน AWS SDK v2:
KmsClient.generateDataKey(GenerateDataKeyRequest)→GenerateDataKeyResponse.plaintext()คืนSdkBytes— เรียก.asByteArray()เพื่อ getbyte[].ciphertextBlob()= encrypted DEK ที่ store คู่กับ data- อย่า log
plaintextDekหรือdekBytes— เป็นความลับ- SDK v1 (
com.amazonaws:aws-java-sdk-kms) deprecated — ใช้ v2 เท่านั้นใน project ใหม่
22. Library ใน Java
java
// JCA (Java Cryptography Architecture)
java.security.* // Hash, signature, KeyGenerator
javax.crypto.* // Cipher, Mac, KeyAgreement
// Bouncy Castle — ทางเลือก (มี algorithm ใหม่ ๆ)
org.bouncycastle:bcprov-jdk18on
// Google Tink — high-level (Google design — hard to misuse)
com.google.crypto.tink:tink
// Lazysodium — libsodium wrapper
com.goterl:lazysodium-javaลองใช้ Tink หรือ libsodium — interface ที่ design มาให้ใช้ผิดยาก
23. ⚠️ ข้อผิดพลาดที่พบบ่อยสรุป
| ❌ | ✅ |
|---|---|
| Hash password ด้วย SHA256 | BCrypt/Argon2 |
| Roll own crypto | ใช้ JCA / Tink / libsodium |
| ECB mode | GCM mode |
| IV ซ้ำ | random ใหม่ทุกครั้ง |
| Math.random for token | SecureRandom |
| Hardcoded key | Secrets Manager / Vault |
.equals() compare signature | MessageDigest.isEqual |
| RSA encrypt big data | hybrid (RSA + AES) |
| Long-lived key | rotate 90 วัน |
| Single key for all purposes | key ต่างสำหรับแต่ละ purpose |
24. Checkpoint
🛠️ Checkpoint 4.1 — Hash + HMAC
- SHA-256 hash ของ string
- HMAC-SHA256 สำหรับ webhook signature
- Verify ด้วย constant-time compare
🛠️ Checkpoint 4.2 — AES-GCM
- Generate AES-256 key
- Encrypt string ด้วย random IV
- Store: IV + cipher
- Decrypt กลับมา
🛠️ Checkpoint 4.3 — RSA Signature
- Generate RSA 2048-bit key pair
- Sign string ด้วย private
- Verify ด้วย public
- ลองแก้ string 1 ตัว → verify fail
🛠️ Checkpoint 4.4 — TLS Test
- Run server ด้วย Let's Encrypt cert
- ทดสอบที่ ssllabs.com — ตั้งเป้าให้ได้ A+
- ดูว่า cipher suite ที่ใช้คืออะไร
🛠️ Checkpoint 4.5 — Envelope Encryption
- ทำ encryption service ที่ใช้ AWS KMS / GCP KMS
- Encrypt file ใหญ่ ๆ
- Verify rotate master key ไม่กระทบ data ที่ encrypt แล้ว
25. สรุปบท
✅ Don't roll your own crypto — ใช้ library มาตรฐาน (JCA, Tink, libsodium)
✅ Hash ≠ encrypt — SHA-256 OK สำหรับ integrity, BCrypt/Argon2 สำหรับ password
✅ HMAC สำหรับ message authentication — constant-time compare
✅ AES-256-GCM = standard symmetric encryption — random IV ทุกครั้ง
✅ RSA/ECDSA/Ed25519 = asymmetric — encrypt small / sign
✅ Hybrid encryption สำหรับ data ใหญ่ (AES + RSA)
✅ SecureRandom ไม่ใช่ Math.random
✅ TLS 1.2+ + forward secrecy + Let's Encrypt
✅ mTLS สำหรับ service-to-service / zero-trust
✅ Envelope encryption + KMS = best practice ของ key management
✅ Rotate keys + at-rest / in-transit / in-use encryption
← บทที่ 3 | บทที่ 5 → Secret Management
Glossary: ../glossary.md · Style guide: ../CONTRIBUTING.md last_verified: 2026-06-03 · review report: ../REVIEW-2026-06-03.md