Why is this an issue?
When encrypting data with Counter (CTR) derived block cipher modes of operation, it is essential not to reuse the same initialization vector (IV)
with a given key, such IV is called a "nonce" (number used only once). Galois/Counter (GCM) and Counter with Cipher Block Chaining-Message
Authentication Code (CCM) are both CTR-based modes of operation.
An attacker, who has knowledge of one plaintext (original content) and ciphertext (encrypted content) pair, is able to retrieve the corresponding
plaintext of any other ciphertext generated with the same IV and key. It also drastically decreases the key recovery computational complexity by
downgrading it to a simpler polynomial root-finding problem.
When using GCM, NIST recommends a 96 bit length nonce using a 'Deterministic' approach or at least 96 bits using a 'Random Bit Generator (RBG)'.
The 'Deterministic' construction involves a counter, which increments per encryption process. The 'RBG' construction, as the name suggests, generates
the nonce using a random bit generator. Collision probabilities (nonce-key pair reuse) using the 'RBG-based' approach require a shorter key rotation
period, 2^32 maximum invocations per key.
Noncompliant code example
fun encrypt(key: ByteArray, ptxt: ByteArray) {
val nonce: ByteArray = "7cVgr5cbdCZV".toByteArray() // The initialization vector is a static value
val gcmSpec = GCMParameterSpec(128, nonce) // The initialization vector is configured here
val skeySpec = SecretKeySpec(key, "AES")
val cipher: Cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, gcmSpec) // Noncompliant
}
Compliant solution
fun encrypt(key: ByteArray, ptxt: ByteArray) {
val random: SecureRandom = SecureRandom()
val nonce: ByteArray = ByteArray(12)
random.nextBytes(nonce) // Random 96 bit IV
val gcmSpec = GCMParameterSpec(128, nonce)
val skeySpec = SecretKeySpec(key, "AES")
val cipher: Cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, gcmSpec)
}
Resources