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());
}