หมวดหมู่ OWASP: MASVS-CRYPTO: วิทยาการเข้ารหัสลับ
ภาพรวม
นักพัฒนาแอปใช้วิทยาการเข้ารหัสลับเพื่อปกป้องการรักษาความลับและความสมบูรณ์ของข้อมูลโดยใช้อัลกอริทึมที่มีประสิทธิภาพ อย่างไรก็ตาม การจัดเก็บคีย์มักไม่ค่อยได้ใช้ และมักพบว่ามีการฮาร์ดโค้ดคีย์ลงในแอปพลิเคชันเป็นสตริงหรืออาร์เรย์ไบต์ในโค้ดหรือในไฟล์เนื้อหา เช่น strings.xml หากมีการเปิดเผยข้อมูลลับในไฟล์ใดๆ ของแอป ก็จะขัดต่อ หลักการของ Kerchoff และถือได้ว่าโมเดลความปลอดภัยนั้นบกพร่อง
ผลกระทบ
ผู้โจมตีที่มีสิทธิ์เข้าถึงเครื่องมือวิศวกรรมย้อนกลับสามารถดึงข้อมูลลับที่ฮาร์ดโค้ดไว้ได้อย่างง่ายดาย ผลกระทบอาจแตกต่างกันไปตามเงื่อนไข แต่ในหลายๆ กรณีจะนำไปสู่ปัญหาด้านความปลอดภัยที่สำคัญ เช่น การเข้าถึงข้อมูลที่ละเอียดอ่อน
การบรรเทาผลกระทบ
หากต้องการบรรเทาปัญหานี้ ให้พิจารณาใช้ KeyChain API เมื่อต้องการข้อมูลเข้าสู่ระบบทั่วทั้งระบบ หรือใช้ผู้ให้บริการ Android Keystore เพื่อให้แอปแต่ละแอปจัดเก็บข้อมูลเข้าสู่ระบบของตัวเองที่แอปนั้นๆ เท่านั้นที่จะเข้าถึงได้
ข้อมูลโค้ดต่อไปนี้แสดงวิธีจัดเก็บและใช้คีย์สมมาตรโดยใช้ KeyStore
Kotlin
private val ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore"
private val ANDROID_KEY_STORE_ALIAS = "AES_KEY_DEMO"
@Throws(
KeyStoreException::class,
NoSuchAlgorithmException::class,
NoSuchProviderException::class,
InvalidAlgorithmParameterException::class
)
private fun createAndStoreSecretKey() {
val builder: KeyGenParameterSpec.Builder = KeyGenParameterSpec.Builder(
ANDROID_KEY_STORE_ALIAS,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
val keySpec: KeyGenParameterSpec = builder
.setKeySize(256)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setRandomizedEncryptionRequired(true)
.build()
val aesKeyGenerator: KeyGenerator =
KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE_PROVIDER)
aesKeyGenerator.init(keySpec)
val key: SecretKey = aesKeyGenerator.generateKey()
}
@Throws(
KeyStoreException::class,
UnrecoverableEntryException::class,
NoSuchAlgorithmException::class,
CertificateException::class,
IOException::class,
NoSuchPaddingException::class,
InvalidKeyException::class,
IllegalBlockSizeException::class,
BadPaddingException::class
)
private fun encryptWithKeyStore(plainText: String): ByteArray? {
// Initialize KeyStore
val keyStore: KeyStore = KeyStore.getInstance(ANDROID_KEY_STORE_PROVIDER)
keyStore.load(null)
// Retrieve the key with alias androidKeyStoreAlias created before
val keyEntry: KeyStore.SecretKeyEntry =
keyStore.getEntry(ANDROID_KEY_STORE_ALIAS, null) as KeyStore.SecretKeyEntry
val key: SecretKey = keyEntry.secretKey
// Use the secret key at your convenience
val cipher: Cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, key)
return cipher.doFinal(plainText.toByteArray())
}
Java
static private final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
static private final String ANDROID_KEY_STORE_ALIAS = "AES_KEY_DEMO";
private void createAndStoreSecretKey() throws KeyStoreException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(
ANDROID_KEY_STORE_ALIAS,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT);
KeyGenParameterSpec keySpec = builder
.setKeySize(256)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setRandomizedEncryptionRequired(true)
.build();
KeyGenerator aesKeyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE_PROVIDER);
aesKeyGenerator.init(keySpec);
SecretKey key = aesKeyGenerator.generateKey();
}
private byte[] encryptWithKeyStore(final String plainText) throws KeyStoreException, UnrecoverableEntryException, NoSuchAlgorithmException, CertificateException, IOException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
// Initialize KeyStore
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE_PROVIDER);
keyStore.load(null);
// Retrieve the key with alias ANDROID_KEY_STORE_ALIAS created before
KeyStore.SecretKeyEntry keyEntry = (KeyStore.SecretKeyEntry) keyStore.getEntry(ANDROID_KEY_STORE_ALIAS, null);
SecretKey key = keyEntry.getSecretKey();
// Use the secret key at your convenience
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(plainText.getBytes());
}