Code Signing (การลงนามโค้ด) เป็นกลไกลายเซ็นดิจิทัลสำหรับไฟล์ปฏิบัติการที่รับประกันความถูกต้องของนักพัฒนาและความสมบูรณ์ของแอปพลิเคชัน ใน Android ไฟล์ APK ทุกไฟล์จะต้องลงนามด้วยใบรับรองก่อนการติดตั้งบนอุปกรณ์หรือเผยแพร่บน Google Play ตามข้อมูลของ Google, 2024 Android รองรับรูปแบบการลงนามสี่รุ่น: ตั้งแต่ v1 ที่ใช้ JAR ไปจนถึง v4 สำหรับการติดตั้งแบบสตรีมมิ่ง
ประเด็นสำคัญ
Code Signing เป็นกระบวนการเข้ารหัสลับที่นักพัฒนาลงนามโค้ดที่ปฏิบัติการได้ด้วยใบรับรองดิจิทัลของตน ลายเซ็นถูกสร้างขึ้นโดยใช้การเข้ารหัสแบบอสมมาตร: คีย์ส่วนตัวของนักพัฒนาสร้างลายเซ็นดิจิทัล และคีย์สาธารณะถูกฝังอยู่ในใบรับรอง ทุกคนสามารถตรวจสอบลายเซ็นโดยใช้คีย์สาธารณะ แต่การแก้ไขโค้ดโดยไม่ทำให้ลายเซ็นเสียหายนั้นเป็นไปไม่ได้
ในการพัฒนาแอปมือถือ การลงนามโค้ด ทำหน้าที่สามประการ ประการแรก — การรับรองตัวตน: ผู้ใช้และแพลตฟอร์มสามารถระบุนักพัฒนาแอปพลิเคชันได้ ประการที่สอง — ความสมบูรณ์: การเปลี่ยนแปลงใด ๆ ใน APK หลังการลงนามจะทำให้ลายเซ็นไม่ถูกต้อง ประการที่สาม — การอัปเดตที่เชื่อถือได้: แพลตฟอร์มอนุญาตให้อัปเดตแอปพลิเคชันด้วย APK ที่ลงนามด้วยใบรับรองเดียวกับเวอร์ชันที่ติดตั้งเท่านั้น
ลายเซ็นดิจิทัลของแอปพลิเคชัน Android มี ความสำคัญทางกฎหมาย ตามกฎหมายรัสเซีย (63-FZ) และ eIDAS ของยุโรป ลายเซ็นอิเล็กทรอนิกส์ที่มีคุณสมบัติเหมาะสมเทียบเท่ากับลายเซ็นที่เขียนด้วยมือ อย่างไรก็ตาม การลงนาม APK ด้วยใบรับรองที่ลงนามด้วยตนเอง (แนวทางปฏิบัติทั่วไปใน Android) ไม่มีคุณสมบัติเหมาะสม — มันยืนยันความสมบูรณ์ แต่ไม่ใช่ตัวตนของนักพัฒนาจากมุมมองทางกฎหมาย
Android รองรับรูปแบบการลงนาม APK สี่รูปแบบ แต่ละรูปแบบแก้ปัญหาของเวอร์ชันก่อนหน้าและเพิ่มความสามารถใหม่ รูปแบบทั้งหมดสามารถอยู่ร่วมกันใน APK เดียวกัน — นี่เป็นสิ่งจำเป็นสำหรับความเข้ากันได้ย้อนหลังกับ Android เวอร์ชันเก่า
รูปแบบ v1 (JAR signing) ปรากฏใน Android 1.0 มันลงนามไฟล์แต่ละไฟล์ภายในคลังเก็บ APK โดยใช้รายการใน META-INF/MANIFEST.MF ข้อเสีย: ผู้โจมตีสามารถแก้ไข APK (เพิ่มหรือลบไฟล์) และลงนามเฉพาะไฟล์ที่แก้ไขใหม่โดยไม่แตะต้องไฟล์อื่น ทำให้ v1 เสี่ยงต่อการโจมตีบางประเภท รูปแบบ v2 (APK Signature Scheme) ที่นำมาใช้ใน Android 7.0 ลงนามไฟล์ APK ทั้งหมดเป็นหนึ่งเดียว รวมถึงทุกไบต์ยกเว้นลายเซ็นเอง ซึ่งกำจัดความเป็นไปได้ในการแก้ไขแบบเลือกเฉพาะ
| รูปแบบ | Android | คุณสมบัติ | การหมุนเวียนคีย์ |
|---|---|---|---|
| v1 (JAR) | 1.0+ | ลงนามทีละไฟล์ | ไม่ |
| v2 | 7.0+ | ลงนาม APK ทั้งหมด | ไม่ |
| v3 | 9.0+ | ลงนาม + หมุนเวียน | ใช่ |
| v4 | 11.0+ | สตรีมมิ่ง + ADB | ใช่ |
รูปแบบ v3 ที่นำมาใช้ใน Android 9.0 แก้ปัญหาที่มีมานาน: จะทำอย่างไรหากคีย์ลงนามถูกบุกรุกหรือหมดอายุ? ก่อนหน้านี้ การเปลี่ยนคีย์ลงนามหมายความว่าแอปพลิเคชันถูกถือว่าเป็นใหม่ — ไม่สามารถติดตั้งทับแอปพลิเคชันที่มีอยู่ได้ v3 เพิ่มกลไก การหมุนเวียน: APK สามารถรวมหลักฐานการหมุนเวียน (proof-of-rotation) ที่ลงนามด้วยคีย์เก่า ระบบตรวจสอบห่วงโซ่และอนุญาตให้อัปเดตแอปพลิเคชันที่ลงนามด้วยคีย์ใหม่
Keystore เป็นคอนเทนเนอร์ที่ปลอดภัยซึ่งมีคีย์ส่วนตัวและใบรับรองสำหรับการลงนามแอปพลิเคชัน ในการพัฒนา Android ใช้รูปแบบ JKS (Java KeyStore) หรือ PKCS12 Keystore ถูกสร้างขึ้นด้วยยูทิลิตี้ keytool ซึ่งเป็นส่วนหนึ่งของ JDK แต่ละคีย์ในที่เก็บจะถูกระบุด้วยนามแฝง (alias) และป้องกันด้วยรหัสผ่าน
ใบรับรองใน keystore มี คีย์สาธารณะ และข้อมูลเจ้าของ: ชื่อองค์กร ประเทศ ระยะเวลาที่มีผลบังคับใช้ สำหรับแอปพลิเคชัน Android ใบรับรองสามารถลงนามด้วยตนเองได้ — Google ไม่ต้องการหน่วยงานออกใบรับรอง (CA) ซึ่งทำให้ Android แตกต่างจาก iOS อย่างไรก็ตาม ระยะเวลามีผลบังคับใช้ของใบรับรองต้องมีอย่างน้อย 25 ปี เนื่องจากแอปพลิเคชันจะถูกอัปเดตด้วยคีย์เดียวกัน
# การสร้าง keystore ใหม่สำหรับการลงนาม
keytool -genkey -v -keystore my-release.keystore \
-alias my-app-alias \
-keyalg RSA \
-keysize 2048 \
-validity 10000
# การดูเนื้อหา keystore
keytool -list -v -keystore my-release.keystore
Android รองรับอัลกอริทึมสองแบบสำหรับคีย์ลงนาม: RSA และ ECDSA RSA ด้วยขนาดคีย์ 2048 บิตเป็นมาตรฐานโดยพฤตินัย รองรับโดย Android ทุกรุ่น ECDSA (อัลกอริทึมลายเซ็นดิจิทัลเส้นโค้งวงรี) ด้วยเส้นโค้ง P-256 ให้ความแข็งแกร่งทางการเข้ารหัสเท่ากันด้วยขนาดคีย์ที่เล็กกว่า ตั้งแต่ Android 9.0 เป็นต้นไป แนะนำให้ใช้ ECDSA เนื่องจากเร็วกว่าในการตรวจสอบบนอุปกรณ์มือถือ
ใน Android Gradle Plugin การลงนามถูกกำหนดค่าผ่านบล็อก signingConfigs ใน build.gradle ระดับโมดูล สำหรับบิวด์ดีบัก Android Studio จะสร้าง keystore ดีบักโดยอัตโนมัติด้วยรหัสผ่านที่รู้จัก สำหรับบิวด์รีลีส นักพัฒนาจะระบุเส้นทางไปยัง keystore ของตน นามแฝงคีย์ และรหัสผ่าน ขอแนะนำให้เก็บรหัสผ่านในไฟล์กำหนดค่าแยกต่างหากที่ไม่รวมอยู่ในการควบคุมเวอร์ชัน
แนวทางปฏิบัติสมัยใหม่คือ การจัดการการลงนามแบบรวมศูนย์ ผ่าน CI/CD Jenkins, GitLab CI หรือ GitHub Actions สามารถเก็บ keystore เป็นสิ่งประดิษฐ์ที่ได้รับการป้องกันและรหัสผ่านเป็นความลับของสภาพแวดล้อม ซึ่งป้องกันการรั่วไหลของคีย์ผ่านคลังเก็บและทำให้การหมุนเวียนคีย์ง่ายขึ้นเมื่อจำเป็น
// build.gradle (ระดับแอป) — การกำหนดค่าการลงนาม
android {
signingConfigs {
release {
storeFile file("my-release.keystore")
storePassword System.getenv("KEYSTORE_PASSWORD")
keyAlias System.getenv("KEY_ALIAS")
keyPassword System.getenv("KEY_PASSWORD")
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
}
เพื่อความเข้ากันได้สูงสุด APK ควรลงนามด้วย ทั้งสามรูปแบบ (v1 + v2 + v3) Android Gradle Plugin รวมทุกรูปแบบตามค่าเริ่มต้น APK ที่ลงนามด้วย v2 เท่านั้นจะไม่ติดตั้งบน Android 6.0 และต่ำกว่า APK ที่มีเพียง v1 จะไม่ได้รับประโยชน์ด้านความสมบูรณ์ของ v2 บน Android 7.0+ การรวมทุกรูปแบบไม่เพิ่มขนาด APK เกิน 1–2% และรับประกัน ความเข้ากันได้ กับอุปกรณ์ใด ๆ
Play App Signing เป็นบริการของ Google Play ที่จัดการคีย์ลงนามแอปพลิเคชันแบบรวมศูนย์ นักพัฒนาอัปโหลด APK ที่ลงนามด้วยคีย์อัปโหลด (upload key) ไปยัง Google Play Console และ Google Play ลงนามอีกครั้งด้วยคีย์การแจกจ่าย (distribution key) ก่อนส่งถึงผู้ใช้ ซึ่งป้องกันคีย์การแจกจ่ายจากการสูญหายหรือถูกบุกรุก
ข้อดีของ Play App Signing: ความปลอดภัย — คีย์การแจกจ่ายถูกเก็บไว้ในที่เก็บที่ได้รับการป้องกันของ Google; การหมุนเวียน — สามารถขอเปลี่ยนคีย์ผ่านคอนโซลได้; การกู้คืน — หากคีย์อัปโหลดหายไป สามารถสร้างคีย์ใหม่ได้ ข้อเสีย: สำหรับแอปพลิเคชันที่มีอยู่ก่อนการนำ Play App Signing มาใช้ การย้ายต้องสร้างแอปพลิเคชันใหม่ เนื่องจากคีย์การแจกจ่ายเก่าถูกใช้แล้ว
# การรับลายนิ้วมือใบรับรอง (SHA-256)
keytool -list -v -keystore my-release.keystore \
-alias my-app-alias | grep "SHA256"
# การตรวจสอบลายเซ็น APK ด้วย apksigner
apksigner verify --verbose app-release.apk
หากคีย์ลงนามหายไปและไม่ได้ใช้ Play App Signing การกู้คืนความสามารถในการอัปเดตแอปพลิเคชันนั้น เป็นไปไม่ได้ — คุณจะต้องสร้างแอปพลิเคชันใหม่ด้วยชื่อแพ็กเกจใหม่ นี่เป็นหนึ่งในเหตุผลหลักในการใช้ Play App Signing Google แนะนำให้เก็บสำเนาสำรองของ keystore ไว้ในที่เก็บแบบออฟไลน์ที่ปลอดภัย (ไดรฟ์ USB ที่เข้ารหัส ตู้นิรภัยของธนาคาร)
เมื่อติดตั้ง APK Android จะทำการ ตรวจสอบลายเซ็น หลายขั้นตอน ขั้นแรก — การตรวจสอบใบรับรอง: หมดอายุหรือไม่ รูปแบบถูกต้องหรือไม่ ขั้นที่สอง — การตรวจสอบลายเซ็น: ลายเซ็นเข้ารหัสตรงกับเนื้อหา APK หรือไม่ ขั้นที่สาม — การเปรียบเทียบใบรับรองกับเวอร์ชันที่ติดตั้ง: หากแอปพลิเคชันมีอยู่แล้วบนอุปกรณ์ ใบรับรองต้องตรงกัน มิฉะนั้นการติดตั้งจะถูกบล็อก
ระบบตรวจสอบถูกสร้างไว้ใน PackageManagerService เมื่อประมวลผลคำขอติดตั้ง PMS จะแยกลายเซ็นจาก APK ตรวจสอบโดยใช้คลาส android.util.PackageParser และเปรียบเทียบกับลายเซ็นที่เก็บไว้ของแอปพลิเคชันที่ติดตั้ง (ถ้ามี) หากไม่ตรงกัน ผู้ใช้จะได้รับข้อผิดพลาด “INSTALL_FAILED_UPDATE_INCOMPATIBLE” กลไกนี้ป้องกัน การโจมตีแบบแทนที่ (มัลแวร์ไม่สามารถแทนที่แอปพลิเคชันที่ถูกต้องด้วยเวอร์ชันของตัวเอง)
นักพัฒนาสามารถตรวจสอบลายเซ็น APK ได้อย่างอิสระโดยใช้ยูทิลิตี้ apksigner จาก Android SDK Build Tools คำสั่ง apksigner verify --verbose app.apk แสดงว่า APK ลงนามด้วยรูปแบบใด ใบรับรองถูกต้องหรือไม่ และลายเซ็นตรงกับเนื้อหาหรือไม่ สำหรับการตรวจสอบลายเซ็นของแอปพลิเคชันที่ติดตั้งโดยโปรแกรม ให้ใช้ PackageManager.getPackageInfo() ด้วยแฟล็ก GET_SIGNATURES
// การตรวจสอบลายเซ็นของแอปพลิเคชันที่ติดตั้งโดยโปรแกรม
fun getAppSignature(context: Context, packageName: String): String? {
val pm = context.packageManager
val info = pm.getPackageInfo(
packageName,
PackageManager.GET_SIGNATURES
)
return info.signatures?.firstOrNull()?.toCharsString()
}
ความปลอดภัยของคีย์ลงนาม เป็นด้านที่สำคัญของการพัฒนา Android การบุกรุกคีย์ทำให้ผู้โจมตีสามารถลงนามการอัปเดตแอปพลิเคชันของคุณด้วยโค้ดของตนเอง กฎหลัก: อย่าเก็บคีย์ในคลังเก็บ อย่าใช้คีย์เดียวกันสำหรับแอปพลิเคชันต่าง ๆ อย่าโอนคีย์ผ่านช่องทางที่ไม่ปลอดภัย (อีเมล แอปส่งข้อความ)
แนวทางปฏิบัติที่แนะนำคือ การแยกคีย์ ใช้คีย์แยกสำหรับแต่ละแอปพลิเคชันและคีย์แยกสำหรับอัปโหลดไปยัง Google Play (upload key) สำหรับบิวด์ดีบัก Android Studio สร้าง debug.keystore ที่ใช้ร่วมกัน — ไม่ควรใช้สำหรับบิวด์รีลีส ระยะเวลามีผลบังคับใช้ของใบรับรองควรเป็น 25–30 ปี (มาตรฐานปัจจุบัน ได้รับการยืนยันโดย Google)
| แนวทางปฏิบัติ | คำแนะนำ |
|---|---|
| การเก็บคีย์ | สื่อที่เข้ารหัส ความลับ CI/CD |
| ระยะเวลาใบรับรอง | อย่างน้อย 25 ปี |
| อัลกอริทึม | RSA 2048+ หรือ ECDSA P-256 |
| การแยก | คีย์แยกต่อแอปพลิเคชัน |
| การสำรอง | สำเนา keystore แบบออฟไลน์ |
ตรวจสอบ ความสมบูรณ์ของห่วงโซ่การลงนาม เป็นประจำ เมื่อพนักงานที่สามารถเข้าถึงคีย์ลาออก ให้อัปเดตคีย์อัปโหลดผ่าน Google Play Console ใช้เครื่องมือเช่น Google Play Integrity API เพื่อตรวจสอบว่าแอปพลิเคชันของคุณไม่ถูกดัดแปลงบนอุปกรณ์ของผู้ใช้ API ส่งคืนข้อมูลเกี่ยวกับลายเซ็นและความสมบูรณ์ ส่งไปยังเซิร์ฟเวอร์เพื่อตรวจสอบ
คำถามที่พบบ่อย
Code Signing คือลายเซ็นดิจิทัลของไฟล์ APK ที่ยืนยันว่าแอปพลิเคชันถูกสร้างขึ้นโดยนักพัฒนาเฉพาะและไม่ถูกแก้ไขหลังการลงนาม หากไม่มีลายเซ็น APK จะไม่ติดตั้งบนอุปกรณ์
ใช้ยูทิลิตี้ keytool จาก JDK: keytool -genkey -v -keystore my-release.keystore -alias my-alias -keyalg RSA -keysize 2048 -validity 10000 ระบุ keystore ที่ได้ใน build.gradle ในบล็อก signingConfigs
หากคีย์หายไปและคุณไม่ได้ใช้ Play App Signing การอัปเดตแอปพลิเคชันจะเป็นไปไม่ได้ คุณจะต้องสร้างแอปพลิเคชันใหม่บน Google Play ด้วยชื่อแพ็กเกจใหม่ ใช้ Play App Signing เพื่อป้องกันการสูญเสียคีย์
v1 ลงนามแต่ละไฟล์ภายใน APK ทีละไฟล์ — ผู้โจมตีสามารถแก้ไขไฟล์เดียวและลงนามใหม่เฉพาะไฟล์นั้น v2 ลงนาม APK ทั้งหมดเป็นหนึ่งเดียว — การเปลี่ยนแปลงใด ๆ ทำให้ลายเซ็นไม่ถูกต้อง ซึ่งให้ระดับความปลอดภัยที่สูงกว่า
Play App Signing คือบริการของ Google Play ที่เก็บคีย์การแจกจ่ายแอปพลิเคชันแบบรวมศูนย์ นักพัฒนาอัปโหลด APK ที่ลงนามด้วยคีย์อัปโหลด และ Google ลงนามอีกครั้งก่อนส่งถึงผู้ใช้ ป้องกันคีย์จากการสูญหายหรือถูกขโมย
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ