SSL Pinning คืออะไร ใช้กับ Mobile App

SSL Pinning (หรือ Certificate Pinning) คือเทคนิคความปลอดภัยที่ทำให้ Mobile App ตรวจสอบว่า SSL Certificate ของ server ตรงกับที่ฝังไว้ใน app หรือไม่ แทนที่จะเชื่อถือ CA ทุกตัวในระบบ วิธีนี้ช่วยป้องกัน Man-in-the-Middle (MITM) Attack ได้อย่างมีประสิทธิภาพ

SSL Pinning ทำงานอย่างไร

ปกติเมื่อ app เชื่อมต่อกับ server ระบบ OS จะตรวจสอบว่า Certificate ออกโดย CA ที่น่าเชื่อถือหรือไม่ ซึ่งหากผู้โจมตีมี Certificate ปลอมจาก CA ที่ถูกต้อง ก็สามารถดักข้อมูลได้

SSL Pinning แก้ปัญหานี้โดยให้ app เปรียบเทียบ Certificate หรือ Public Key กับค่าที่ฝังไว้ล่วงหน้า ถ้าไม่ตรงกัน การเชื่อมต่อจะถูกปฏิเสธทันที

ประเภทของ SSL Pinning

1. Certificate Pinning

ฝัง Certificate ทั้งใบ (DER format) ไว้ใน app การ renew certificate ต้องอัพเดต app ด้วย เหมาะสำหรับ app ที่ certificate เปลี่ยนไม่บ่อย

2. Public Key Pinning (แนะนำ)

ฝังเฉพาะ Public Key Hash ของ certificate ได้รับแรงบันดาลใจจาก HPKP ข้อดีคือสามารถ renew certificate ได้โดยไม่ต้องอัพเดต app ตราบใดที่ใช้ private key เดิม

3. CA Pinning

ฝัง Public Key ของ CA แทน ทำให้ยืดหยุ่นกว่า แต่ลด security เพราะ CA อาจออก certificate ให้คนอื่นได้

ข้อดีและข้อเสียของ SSL Pinning

ตัวอย่าง Implementation บน iOS (Swift)

// URLSessionDelegate
func urlSession(_ session: URLSession,
    didReceive challenge: URLAuthenticationChallenge,
    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
    
    guard let serverTrust = challenge.protectionSpace.serverTrust,
          let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0) else {
        completionHandler(.cancelAuthenticationChallenge, nil)
        return
    }
    // เปรียบเทียบกับ pinned certificate
    let pinnedCertData = // โหลด cert ที่ฝังไว้ใน app bundle
    let serverCertData = SecCertificateCopyData(certificate) as Data
    if serverCertData == pinnedCertData {
        completionHandler(.useCredential, URLCredential(trust: serverTrust))
    } else {
        completionHandler(.cancelAuthenticationChallenge, nil)
    }
}

ตัวอย่าง Implementation บน Android (OkHttp)

val certificatePinner = CertificatePinner.Builder()
    .add("api.yourdomain.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
    .build()

val client = OkHttpClient.Builder()
    .certificatePinner(certificatePinner)
    .build()

แนะนำ: ใช้ Public Key Pinning แทน Certificate Pinning เพื่อหลีกเลี่ยงการต้องอัพเดต app ทุกครั้งที่ renew certificate และควร pin อย่างน้อย 2 keys (1 primary + 1 backup)

SSL Pinning เทียบกับ HSTS และ Certificate Transparency

หลายคนมักสับสนระหว่าง SSL Pinning กับกลไกความปลอดภัยอื่นๆ เช่น HSTS (HTTP Strict Transport Security) และ Certificate Transparency ซึ่งทั้งสามเป็นคนละ Layer ของการป้องกัน

HSTS บังคับให้ Browser ใช้ HTTPS เสมอ ป้องกันการ Downgrade Attack แต่ยังคงเชื่อถือ CA ทุกตัวในระบบ ดังนั้น HSTS ไม่ได้ป้องกัน MITM ที่ใช้ Certificate จาก CA ที่ถูกต้อง

Certificate Transparency (CT) คือ Log สาธารณะที่ CA ต้องบันทึก Certificate ทุกใบที่ออก ทำให้ตรวจสอบได้ว่ามี Certificate ปลอมถูกออกหรือไม่ แต่ CT ทำงานหลังเกิดเหตุการณ์ ไม่ได้ป้องกัน Real-time MITM

SSL Pinning จึงเป็นการป้องกัน Real-time ที่ App ตรวจสอบเองก่อนส่งข้อมูล โดยไม่ต้องพึ่งระบบภายนอก ทั้งสามทำงานร่วมกันได้ดีและควรใช้ทั้งหมดพร้อมกันในระบบที่ต้องการความปลอดภัยสูง เช่น Banking App หรือ Healthcare Platform

วิธีดึง Public Key Hash สำหรับ Pinning

ก่อนเริ่ม implement SSL Pinning ต้องดึงค่า Public Key Hash ของ Certificate ที่จะ Pin ออกมาก่อน วิธีที่นิยมใช้คือ OpenSSL command line ซึ่งสามารถรันบน macOS หรือ Linux ได้ทันที

# ดึง Public Key Hash จาก Certificate โดยตรง
openssl s_client -connect api.yourdomain.com:443 -servername api.yourdomain.com 2>/dev/null \
  | openssl x509 -pubkey -noout \
  | openssl pkey -pubin -outform der \
  | openssl dgst -sha256 -binary \
  | base64

# ดึงจากไฟล์ .pem ที่มีอยู่แล้ว
openssl x509 -in certificate.pem -pubkey -noout \
  | openssl pkey -pubin -outform der \
  | openssl dgst -sha256 -binary \
  | base64

ค่า Base64 ที่ได้คือ Public Key Hash ที่นำไปใส่ใน App ได้เลย ควรเก็บ Hash ของ Certificate ปัจจุบัน และ Backup Certificate อีกอย่างน้อย 1 ใบ เพื่อป้องกันกรณีที่ต้องเปลี่ยน Certificate ฉุกเฉิน

การทดสอบ SSL Pinning ด้วย Proxy Tools

เมื่อ implement SSL Pinning แล้ว ขั้นตอนสำคัญคือการทดสอบว่า Pinning ทำงานได้จริงหรือไม่ นักพัฒนาและ Security Tester นิยมใช้ Proxy Tools เพื่อ verify การทำงาน

Charles Proxy และ Burp Suite

Tools เหล่านี้จะพยายาม Intercept HTTPS traffic โดยแทรก Certificate ของตัวเองเข้าไป ถ้า SSL Pinning ทำงานถูกต้อง App ควรปฏิเสธการเชื่อมต่อทันทีและไม่ส่งข้อมูลใดๆ ผ่าน Proxy ได้

Frida Framework (Bypass Testing)

สำหรับทดสอบความแข็งแกร่งของ Pinning นักทดสอบมักใช้ Frida ซึ่งเป็น Dynamic Instrumentation Framework บน Device ที่ถูก Root หรือ Jailbreak Frida สามารถ Hook เข้าไปใน Runtime และ Bypass การตรวจสอบ Certificate ได้ ซึ่งช่วยให้รู้ว่า Implementation มีช่องโหว่ตรงไหน

สถานการณ์ ผลลัพธ์ที่ควรได้ ความเสี่ยง
Charles Proxy บน Device ปกติ Connection ถูก reject ทันที ต่ำ
Frida บน Device ที่ Root แล้ว อาจ Bypass ได้หาก Implementation ไม่รัดกุม กลาง-สูง
Network ที่มี Rogue CA Connection ถูก reject เพราะ Hash ไม่ตรง ต่ำมาก

SSL Pinning กับการจัดการ Certificate Rotation

ปัญหาที่พบบ่อยที่สุดของ SSL Pinning คือเมื่อถึงเวลาต้อง Renew หรือเปลี่ยน Certificate ถ้าไม่วางแผนไว้ล่วงหน้า อาจทำให้ App ของผู้ใช้ทุกคนเชื่อมต่อ Server ไม่ได้ทันที ซึ่งเป็นปัญหา Critical สำหรับ Production App

แนวทางป้องกัน Certificate Expiry

สำหรับ App ที่ใช้ SSL Certificate จาก AsiaGB ควร Renew Certificate อย่างน้อย 30 วันก่อนหมดอายุ และอัพเดต Backup Pin ใน App ก่อนนั้นเสมอ

SSL Pinning กับ Network Security Configuration (Android)

ตั้งแต่ Android 7.0 (Nougat) ขึ้นไป Google เพิ่มระบบ Network Security Configuration ซึ่งช่วยให้ตั้งค่า SSL Pinning ผ่านไฟล์ XML แทนการเขียน Code โดยตรง ทำให้ง่ายขึ้นและลด Risk ของ Bug ในการ Implement

<!-- res/xml/network_security_config.xml -->
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
  <domain-config>
    <domain includeSubdomains="true">api.yourdomain.com</domain>
    <pin-set expiration="2027-01-01">
      <!-- Primary pin -->
      <pin digest="SHA-256">AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</pin>
      <!-- Backup pin -->
      <pin digest="SHA-256">BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=</pin>
    </pin-set>
  </domain-config>
</network-security-config>

จากนั้นระบุไฟล์นี้ใน AndroidManifest.xml โดยเพิ่ม android:networkSecurityConfig="@xml/network_security_config" ใน application tag ข้อดีของวิธีนี้คือ Android OS จัดการ Pinning ให้โดยอัตโนมัติโดยไม่ต้องเขียน Custom URLSessionDelegate เองทั้งหมด

สังเกตว่ามี attribute expiration ซึ่งเป็น Safety Valve สำคัญ ถ้า App ยังเชื่อมต่อ Server ได้หลัง Certificate หมดอายุ Pinning จะหยุดทำงานอัตโนมัติและ Fallback กลับเป็น Normal Certificate Validation ป้องกัน User Lockout ในกรณีฉุกเฉิน

ข้อควรระวังและ Anti-pattern ที่พบบ่อย

การ Implement SSL Pinning ผิดวิธีอาจทำให้เกิดปัญหาร้ายแรงกว่าไม่มี Pinning เลย ต่อไปนี้คือข้อผิดพลาดที่พบบ่อยในทีมพัฒนา

1. Pin Certificate เดียวโดยไม่มี Backup

การ Pin เพียง 1 Certificate โดยไม่มี Backup Pin เป็นความเสี่ยงสูงมาก ถ้า Certificate ถูก Revoke กะทันหันหรือ Server ต้องเปลี่ยน Certificate ฉุกเฉิน ผู้ใช้ทุกคนที่ยังใช้ App เวอร์ชั่นเก่าจะเชื่อมต่อไม่ได้จนกว่าจะอัพเดต App ใหม่ ซึ่งใช้เวลาอย่างน้อย 1-2 วันกว่า App Store จะอนุมัติ

2. Hard-code ค่า Pin โดยตรงโดยไม่มี Fallback Logic

บาง Developer ใส่ค่า Hash ตรงๆ เป็น String Constant แล้วถ้า Certificate เปลี่ยน App จะ Crash หรือ Network Error ทุกครั้ง ควรมี Logic ที่แสดง Error Message ที่เข้าใจได้ เช่น "กรุณาอัพเดต App" แทนที่จะปล่อยให้เกิด Unhandled Exception

3. ลืมทดสอบ Production Certificate

ระหว่าง Development มักใช้ Development Certificate และ Staging Environment ซึ่ง Pin คนละ Hash กับ Production ถ้าลืม Update Hash ก่อน Release App จะทำงานได้บน Dev แต่พังทันทีที่ผู้ใช้จริงเปิด

4. ไม่ Handle Network Timeout แยกจาก Pin Failure

เมื่อ Connection ล้มเหลว ควรแยกแยะให้ได้ว่าเกิดจาก Certificate ไม่ตรง (ควรแจ้งเตือนทีม Security) หรือเกิดจาก Network ไม่มีสัญญาณ (ควรให้ผู้ใช้ลองใหม่) การรวมสองกรณีเป็น Error เดียวทำให้ Debug ยากมาก

เลือก SSL Certificate ที่รองรับ Pinning ได้ง่าย

ไม่ใช่ทุก SSL Certificate ที่เหมาะกับ SSL Pinning เท่ากัน Certificate ที่ Private Key เปลี่ยนบ่อยหรือมี Auto-renewal แบบอัตโนมัติ (เช่น Let's Encrypt ที่ Renew ทุก 90 วัน) อาจทำให้ต้องอัพเดต App บ่อยมากหากเลือก Certificate Pinning

สำหรับ App ที่ต้องการ SSL Pinning แนะนำ Certificate ประเภท OV (Organization Validation) หรือ DV ที่มีอายุ 1 ปี เพื่อให้มีเวลาเตรียมอัพเดต App รองรับ Certificate ใหม่ได้อย่างไม่เร่งรีบ AsiaGB มี Certificate หลายประเภทที่เหมาะกับการใช้งานนี้ ทั้ง RapidSSL DV, GeoTrust OV และ Wildcard ที่รองรับ Subdomain หลายตัวในคราวเดียว

ประเภท Certificate เหมาะกับ Pinning แบบไหน ความถี่ Renew
DV (Domain Validation) 1 ปี Public Key Pinning (Key เดิมนาน 3–5 ปี) 1 ครั้ง/ปี
OV (Organization Validation) Public Key Pinning (เหมาะกับ Banking/Enterprise) 1 ครั้ง/ปี
Wildcard DV Public Key Pinning (ครอบ Subdomain ทั้งหมด) 1 ครั้ง/ปี
Let's Encrypt (Free) ไม่แนะนำ (Renew ทุก 90 วัน = ต้อง Update App บ่อย) ทุก 90 วัน

ต้องการ SSL Certificate สำหรับ App ของคุณ

AsiaGB มี SSL Certificate ทุกประเภท DV, OV, Wildcard เหมาะกับ Mobile App และ API รับประกันโดย CA ชั้นนำ

ดู SSL Certificate