EncryptedSharedPreferences เป็นคอมโพเนนต์ของไลบรารี AndroidX Security ที่ให้การเข้ารหัสข้อมูลอย่างโปร่งใสซึ่งบันทึกผ่าน API SharedPreferences ต่างจาก SharedPreferences ทั่วไปที่ข้อมูลถูกเก็บในไฟล์ XML ธรรมดา EncryptedSharedPreferences จะเข้ารหัสคีย์และค่าโดยอัตโนมัติก่อนเขียนลงดิสก์ ตาม Android Developers ไลบรารีใช้ AES-256 GCM สำหรับค่าและ AES-256 SIV (RFC 5297) สำหรับคีย์ เพื่อรับประกันความลับและความสมบูรณ์ของข้อมูล
ประเด็นสำคัญ
EncryptedSharedPreferences เป็นคลาสจากแพ็คเกจ androidx.security.crypto ที่เปิดตัวใน AndroidX Security 1.0.0 (2019) มันใช้อินเทอร์เฟส SharedPreferences แต่การดำเนินการเขียนทั้งหมด (putString, putInt, putBoolean ฯลฯ) จะเข้ารหัสข้อมูลล่วงหน้า และการดำเนินการอ่านจะถอดรหัสข้อมูลก่อนส่งคืน
SharedPreferences มาตรฐานบันทึกข้อมูลในไฟล์ XML ในไดเรกทอรีของแอป (/data/data/package/shared_prefs/) ไฟล์ไม่ได้ถูกเข้ารหัส — ด้วยการเข้าถึง root ไปยังอุปกรณ์หรือระหว่างการวิเคราะห์ข้อมูลสำรอง ข้อมูลทั้งหมดจะถูกอ่านเป็น XML ธรรมดา โทเค็นการยืนยันตัวตน คีย์ API และข้อมูลส่วนตัวของผู้ใช้กลายเป็นสิ่งที่ผู้โจมตีสามารถเข้าถึงได้
EncryptedSharedPreferences แก้ปัญหานี้ในระดับไลบรารี: ข้อมูลถูกเข้ารหัสก่อนเขียนลงดิสก์และถอดรหัสเมื่ออ่าน นักพัฒนาไม่จำเป็นต้องเรียกใช้ฟังก์ชันการเข้ารหัสด้วยตนเอง — API ยังคงเหมือนกับ SharedPreferences ทั่วไป
ไลบรารี AndroidX Security v1.0.0 เปิดตัวในเดือนธันวาคม 2019 EncryptedSharedPreferences แทนที่วิธีการเข้ารหัสด้วยตนเองที่ล้าสมัยผ่าน Cipher + SharedPreferences เวอร์ชันเสถียรปัจจุบันคือ 1.1.0-alpha06 (2024) รองรับ API 19+ ไลบรารีเป็นส่วนหนึ่งของ Jetpack และไม่ต้องได้รับอนุญาตเพิ่มเติม
ตาม Google Security Blog (2024) EncryptedSharedPreferences เป็นวิธีที่แนะนำในการเก็บการตั้งค่าแอปที่ละเอียดอ่อนซึ่งไม่ต้องการการซิงค์ผ่านคลาวด์ สำหรับสถานการณ์ที่ซับซ้อนมากขึ้น Room ที่มีการเข้ารหัส SQLCipher เป็นที่แนะนำ
EncryptedSharedPreferences ใช้รูปแบบการเข้ารหัสสองระดับ: Master Key ถูกเก็บใน Android Keystore และใช้คีย์ที่ได้รับสำหรับการเข้ารหัสข้อมูล ซึ่งรวมการป้องกันของ Keystore เข้ากับประสิทธิภาพของการเข้ารหัสแบบสมมาตร
สำหรับค่า ใช้ AES-256 GCM (โหมด Galois/Counter) — โหมดการเข้ารหัสที่รับรองความถูกต้อง (AEAD) ซึ่งรับประกันความลับและความสมบูรณ์ของข้อมูล สำหรับคีย์ (ชื่อพารามิเตอร์) ใช้ AES-256 SIV (RFC 5297) — การเข้ารหัสแบบกำหนดแน่นอนที่จำเป็นสำหรับการค้นหาคีย์โดยไม่เปิดเผยเนื้อหา
แต่ละไฟล์ของ EncryptedSharedPreferences ประกอบด้วยคู่คีย์-ค่าที่เข้ารหัส โครงสร้างไฟล์ประกอบด้วย: ส่วนหัวพร้อมข้อมูลเมตา (เวอร์ชัน ตัวระบุคีย์) จากนั้นรายการของรายการที่เข้ารหัส ไฟล์ไม่ใช่ XML ที่ถูกต้องและไม่สามารถอ่านได้ด้วยโปรแกรมแก้ไขข้อความ
คลาส MasterKey รับผิดชอบในการสร้างและจัดการคีย์หลัก 256 บิตที่เก็บใน Android Keystore MasterKey.Builder อนุญาตให้กำหนดค่า: ประเภทพื้นที่เก็บข้อมูล (Keystore หรือซอฟต์แวร์) การป้องกันด้วยไบโอเมตริกซ์ และอายุของคีย์ โดยค่าเริ่มต้น คีย์หลักจะถูกสร้างใน Android Keystore ด้วยอัลกอริธึม AES/GCM/NoPadding
import androidx.security.crypto.MasterKey
import androidx.security.crypto.EncryptedSharedPreferences
fun getEncryptedPrefs() {
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.AES256_GCM_SPEC)
.build()
val prefs = EncryptedSharedPreferences.create(
context,
"secure_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
}
EncryptedSharedPreferences.create รับพารามิเตอร์ห้าตัว: บริบท ชื่อไฟล์ คีย์หลัก รูปแบบการเข้ารหัสคีย์ และรูปแบบการเข้ารหัสค่า การเลือกรูปแบบส่งผลต่อประสิทธิภาพและระดับความปลอดภัย
AES256_SIV — การเข้ารหัสแบบกำหนดแน่นอน: คีย์ที่เหมือนกันจะสร้างข้อความเข้ารหัสเดียวกันเสมอ ซึ่งจำเป็นสำหรับการค้นหาคีย์ (SharedPreferences.getX(key)) ข้อเสีย: ผู้โจมตีสามารถระบุได้ว่าคีย์ใดถูกใช้โดยการเปรียบเทียบข้อความเข้ารหัสที่ซ้ำกัน AES256_SIV2 — เวอร์ชันที่ปรับปรุงแล้วพร้อมการสุ่มเพิ่มเติม
สำหรับค่า ใช้ AES256_GCM GCM เพิ่ม IV 12 ไบต์ (เวกเตอร์เริ่มต้น) และแท็กรับรองความถูกต้อง 16 ไบต์ให้กับแต่ละค่า ซึ่งให้ความลับ (ไม่มีใครสามารถอ่านค่าได้) และการรับรองความถูกต้อง (ไม่มีใครสามารถแก้ไขค่าโดยไม่ถูกตรวจพบ)
เมธอด setUserAuthenticationRequired(true) ใน MasterKey.Builder ต้องการการยืนยันด้วยไบโอเมตริกซ์ก่อนดึงคีย์หลักจาก Keystore ซึ่งเพิ่มชั้นป้องกันเพิ่มเติม: แม้แอปกำลังทำงานบนอุปกรณ์ที่ปลดล็อกแล้ว ผู้โจมตีก็ไม่สามารถอ่าน EncryptedSharedPreferences โดยไม่มี Face ID หรือ Touch ID
สำคัญ: ด้วย setUserAuthenticationRequired คีย์หลักจะไม่สามารถใช้ได้หากผู้ใช้เปลี่ยนแปลงหรือลบข้อมูลไบโอเมตริกซ์ ต้องจัดการ KeyPermanentlyInvalidatedException และสร้างคีย์หลักใหม่พร้อมการย้ายข้อมูล
fun createBiometricKey(): MasterKey {
return MasterKey.Builder(context)
.setKeyScheme(MasterKey.AES256_GCM_SPEC)
.setUserAuthenticationRequired(true)
.setRequestStrongBoxBacked(true)
.build()
}
fun writeSecureToken(token: String) {
try {
prefs.edit().putString("auth_token", token).apply()
} catch (e: KeyPermanentlyInvalidatedException) {
// ข้อมูลไบโอเมตริกซ์เปลี่ยนแปลงแล้ว — จำเป็นต้องสร้างคีย์ใหม่
}
}
มาดูตัวอย่างแบบสมบูรณ์ของการรวม EncryptedSharedPreferences ในแอป Android โดยใช้ Kotlin กัน ไลบรารี androidx.security:security-crypto ถูกเพิ่มผ่าน Gradle
ในไฟล์ build.gradle (app) ให้เพิ่ม: implementation “androidx.security:security-crypto:1.1.0-alpha06” สำหรับโปรเจกต์ Kotlin ต้องใช้ kotlin-stdlib ด้วย การเริ่มต้น MasterKey เกิดขึ้นครั้งเดียว โดยปกติใน Application.onCreate หรือผ่านคอนเทนเนอร์ DI
หลังจากสร้างอินสแตนซ์ EncryptedSharedPreferences แล้ว API ก็ไม่แตกต่างจาก SharedPreferences ทั่วไป edit() ส่งคืน Editor เมธอดทั้งหมด (putString, getString, putBoolean, getBoolean) ทำงานเหมือนกัน ข้อแตกต่างเพียงอย่างเดียวคือภายใน: ข้อมูลถูกเข้ารหัสเมื่อเขียนและถอดรหัสเมื่ออ่าน
class AuthRepository(context: Context) {
private val prefs = createEncryptedPrefs(context)
fun saveCredentials(login: String, password: String) {
prefs.edit()
.putString("login", login)
.putString("password", password)
.apply()
}
fun getToken(): String? {
return prefs.getString("auth_token", null)
}
fun clearAll() {
prefs.edit().clear().apply()
}
}
เพื่อย้ายข้อมูลที่มีอยู่จาก SharedPreferences ที่ไม่ได้เข้ารหัสไปยัง EncryptedSharedPreferences: อ่านข้อมูลทั้งหมดจากไฟล์เก่า สร้าง EncryptedSharedPreferences ใหม่ เขียนข้อมูลทั้งหมด และลบไฟล์เก่า Google ไม่มีตัวย้ายข้อมูลในตัว — นักพัฒนาต้องimplementด้วยตนเอง
การเลือกระหว่าง SharedPreferences และ EncryptedSharedPreferences ขึ้นอยู่กับประเภทของข้อมูลที่เก็บ สำหรับการตั้งค่าอินเทอร์เฟส (ธีม ภาษา การเรียงลำดับ) SharedPreferences ทั่วไปก็เพียงพอ สำหรับข้อมูลที่เป็นความลับ (โทเค็น รหัสผ่าน คีย์) จำเป็นต้องใช้ EncryptedSharedPreferences
EncryptedSharedPreferences ช้ากว่าทั่วไปเนื่องจากการดำเนินการเข้ารหัส การเขียนค่าสตริงเดียวใช้เวลา ~5-15 มิลลิวินาที (ขึ้นอยู่กับขนาดข้อมูลและการเร่งด้วยฮาร์ดแวร์ AES) การอ่านใช้เวลา 2-5 มิลลิวินาที สำหรับแอปส่วนใหญ่นี้ไม่สังเกตเห็น แต่สำหรับการดำเนินการแบบกลุ่ม (การย้าย การกู้คืน) ให้ใช้ apply() แทน commit()
SharedPreferences ทั่วไปไม่มีการป้องกันการเข้ารหัสใด ๆ: ไฟล์ XML สามารถอ่านได้โดยกระบวนการใด ๆ ที่มีสิทธิ์ root หรือผ่าน adb backup EncryptedSharedPreferences เข้ารหัสข้อมูลในระดับแอปพลิเคชัน และคีย์หลักถูกเก็บใน Android Keystore พร้อมการป้องกันด้วยฮาร์ดแวร์เพิ่มเติม (StrongBox)
| คุณลักษณะ | SharedPreferences | EncryptedSharedPreferences |
|---|---|---|
| การเก็บ | XML ธรรมดา | ไฟล์ไบนารีที่เข้ารหัส |
| การเข้ารหัส | ไม่มี | AES-256 GCM + SIV |
| การป้องกันคีย์ | ไม่มี | Android Keystore + StrongBox |
| ประสิทธิภาพ | 0.1-1 มิลลิวินาที | 2-15 มิลลิวินาที |
| คำแนะนำ | การตั้งค่า UI | โทเค็น, คีย์, PII |
ใช้ EncryptedSharedPreferences เพื่อเก็บ: โทเค็นรีเฟรช OAuth, คีย์ API สำหรับบริการภายนอก, อีเมลหรือเบอร์โทรศัพท์ของผู้ใช้ และการตั้งค่าแอปที่ละเอียดอ่อน (PIN, แฟล็กการยืนยันตัวตน) EncryptedSharedPreferences ไม่เหมาะสำหรับการเก็บข้อมูลไบโอเมตริกซ์หรือเอกสารขนาดใหญ่ — ให้ใช้ EncryptedFile หรือ Room กับ SQLCipher แทน
กฎทั่วไป: หากข้อมูลรั่วไหลจะเป็นอันตรายต่อผู้ใช้หรือธุรกิจ — ใช้ EncryptedSharedPreferences หากข้อมูลเป็นเพียงความสวยงาม (ธีม ภาษา การเรียงลำดับ) — SharedPreferences ทั่วไป การนำ EncryptedSharedPreferences มาใช้ตั้งแต่ต้นนั้นสมเหตุสมผล โดยไม่ต้องปรับโครงสร้าง: การแทนที่ในโปรเจกต์ที่มีอยู่จะต้องมีการย้ายข้อมูลและการจัดการข้อมูลเก่าที่ยังไม่ได้เข้ารหัส
โปรดจำไว้ว่า EncryptedSharedPreferences ไม่ได้ปกป้องข้อมูลระหว่างที่แอปทำงาน — เฉพาะบนดิสก์เท่านั้น หากผู้โจมตีเข้าถึงหน่วยความจำของกระบวนการ ข้อมูลที่ถอดรหัสแล้วอาจถูกดักจับได้ ใช้การป้องกันเพิ่มเติม: ProGuard/DexGuard สำหรับการทำให้โค้ดสับสน
คำถามที่พบบ่อย
Jetpack DataStore เป็นทางเลือกที่ทันสมัยกว่าของ SharedPreferences ซึ่งใช้ Flow และ coroutine ของ Kotlin DataStore ไม่ได้เข้ารหัสข้อมูลโดยค่าเริ่มต้น แต่สามารถรวมกับ EncryptedSharedPreferences หรือใช้กับการเข้ารหัสด้วยตนเองผ่าน Proto DataStore กับโปรโตคอลการเข้ารหัส
ไม่แนะนำ EncryptedSharedPreferences ถูกออกแบบมาสำหรับปริมาณน้อย (สูงสุด 100-200 KB) สำหรับข้อมูลที่ใหญ่ขึ้น ให้ใช้ Room กับ SQLCipher หรือการเข้ารหัสไฟล์ผ่าน EncryptedFile จากไลบรารี AndroidX Security เดียวกัน
ไม่ ไม่มีการย้าย schema อัตโนมัติ เมื่อเปลี่ยนโครงสร้างข้อมูล นักพัฒนาต้องอ่านข้อมูลเก่าด้วยตนเองผ่าน KeyGen เก่าและเขียนผ่าน KeyGen ใหม่ แนะนำให้เก็บเวอร์ชัน schema ไว้ในพารามิเตอร์แยกต่างหาก
AndroidX Security 1.0.0 รองรับ API 19+ (Android KitKat) เวอร์ชัน 1.1.0-alpha06 ก็รองรับ API 19+ เช่นกัน StrongBox ต้องการ API 28+ และอุปกรณ์ที่รองรับฮาร์ดแวร์ (Google Pixel 3+, Samsung Galaxy S9+)
ใช่ refresh token เป็นหนึ่งในกรณีการใช้งานหลัก การเข้ารหัส AES-256 GCM, คีย์หลักใน Keystore, การป้องกันด้วยไบโอเมตริกซ์ — ระดับที่เพียงพอสำหรับโทเค็น OAuth สำหรับ access token ที่มีอายุสั้นก็เหมาะสมเช่นกัน แม้ว่าบางทีมจะชอบเก็บไว้ในหน่วยความจำ
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม