Cryptographic Vulnerability: Insecure mode
Vulnerabilidad criptográfica: Modo inseguro
Descripción
Un cifrado por bloques procesa los bloques de datos de tamaño fijo. Generalmente, el tamaño de un mensaje es mayor que el tamaño del bloque. Por lo tanto, el mensaje largo se divide en una serie de bloques de mensaje secuenciales, y el cifrado opera sobre estos bloques de uno en uno.
Los cifrados por bloques comunes son:
ECB: (Electronic Code Book) es la forma más sencilla de procesar una serie de bloques de mensaje enumerados secuencialmente. El usuario toma el primer bloque de texto en texto plano y lo cifra con la clave para producir el primer bloque de texto cifrado. Luego toma el segundo bloque de texto plano y sigue el mismo proceso con la misma clave, y así sucesivamente. Este modo es inseguro y no debe utilizarse.CBC: (Cipher Block Chaining) proporciona dependencia del mensaje para generar el texto cifrado y hace que el sistema sea no determinista. En el modo CBC, el bloque de texto plano actual se suma al bloque de texto cifrado anterior, y luego el resultado se cifra con la clave. El descifrado es, por tanto, el proceso inverso, que implica descifrar el texto cifrado actual y luego sumar el bloque de texto cifrado anterior al resultado.CFB: (Cipher Feedback) cada bloque de texto cifrado se 'retroalimenta' en el proceso de cifrado para cifrar el siguiente bloque de texto plano. El modo CFB requiere un vector de inicialización (IV) como bloque de entrada aleatorio inicial de n bits. No es necesario que el IV sea secreto. El modo CFB difiere significativamente del modo ECB; el texto cifrado correspondiente a un bloque de texto plano dado depende no solo de ese bloque de texto plano y de la clave, sino también del bloque de texto cifrado anterior. En otras palabras, el bloque de texto cifrado depende del mensaje.OFB: (Output Feedback) implica retroalimentar los bloques de salida sucesivos del cifrado por bloques subyacente. Estos bloques de retroalimentación proporcionan una cadena de bits para alimentar el algoritmo de cifrado, que actúa como generador de flujo de claves, como en el caso del modo CFB. El flujo de claves generado se combina mediante XOR con los bloques de texto plano. El modo OFB requiere un IV como bloque de entrada aleatorio inicial de n bits. No es necesario que el IV sea secreto.CTR: (Counter) Se puede considerar como una versión basada en contadores del modo CFB sin la retroalimentación. En este modo, tanto el remitente como el receptor necesitan tener acceso a un contador fiable, que calcula un nuevo valor compartido cada vez que se intercambia un bloque de texto cifrado. Este contador compartido no es necesariamente un valor secreto, pero el desafío es que ambas partes deben mantener el contador sincronizado.
La mayoría de las implementaciones utilizarán de forma predeterminada el modo inseguro ECB si no se especifica explícitamente. El modo de operación utilizado para cifrar los datos es vulnerable y debe sustituirse por uno seguro.
Recomendación
Al utilizar el cifrado AES, es importante elegir el modo de operación correcto para garantizar la confidencialidad y la integridad de los datos cifrados. La elección del modo puede depender del caso de uso específico y de los requisitos de seguridad. Aquí se enumeran algunos modos de cifrado comunes a considerar:
Electronic Codebook (ECB): Este es el modo más simple e implica dividir el texto plano en bloques y cifrar cada bloque por separado utilizando la misma clave. Sin embargo, este modo es vulnerable a los ataques, ya que bloques de texto plano idénticos producirán bloques de texto cifrado idénticos.Cipher Block Chaining (CBC): En este modo, cada bloque de texto plano se combina mediante XOR con el bloque de texto cifrado anterior antes de ser cifrado. Esto añade aleatoriedad y hace que sea más difícil identificar patrones en los datos cifrados. Sin embargo, este modo es vulnerable a los ataques si el mismo IV (vector de inicialización) se utiliza varias veces o se inicializa con un valor no aleatorio.Counter (CTR): Este modo implica cifrar un valor de contador y combinarlo mediante XOR con el texto plano para generar el texto cifrado. Este es un modo popular para aplicaciones de streaming, ya que permite el cifrado y descifrado en paralelo. Sin embargo, requiere valores de contador únicos y puede ser vulnerable si un atacante puede adivinar los valores del contador.Galois/Counter Mode (GCM): Este modo proporciona protección tanto de la confidencialidad como de la integridad al utilizar una combinación del modo CTR y un código de autenticación de mensajes (MAC). Utiliza un IV único e incorpora datos adicionales, como un número de secuencia, para evitar ataques de repetición (replay attacks). Este modo se utiliza habitualmente en aplicaciones que requieren protección tanto de la confidencialidad como de la integridad.
En general, se recomienda utilizar el modo GCM para el cifrado AES, ya que proporciona tanto confidencialidad como protección de la integridad. Sin embargo, es importante gestionar adecuadamente la clave y el IV para garantizar la seguridad.
El uso de modos de cifrado autenticado como GCM puede ayudar a frustrar ataques como los ataques de oráculo de relleno (padding oracle attacks).
import javax.crypto.Cipher
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.SecretKeySpec
import java.security.SecureRandom
import java.util.Base64
fun encrypt(plaintext: String, key: ByteArray): String {
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
val random = SecureRandom()
val iv = ByteArray(12)
random.nextBytes(iv)
val ivSpec = GCMParameterSpec(128, iv)
val secretKey = SecretKeySpec(key, "AES")
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec)
val encryptedBytes = cipher.doFinal(plaintext.toByteArray())
val combined = ByteArray(iv.size + encryptedBytes.size)
System.arraycopy(iv, 0, combined, 0, iv.size)
System.arraycopy(encryptedBytes, 0, combined, iv.size, encryptedBytes.size)
return Base64.getEncoder().encodeToString(combined)
}
fun decrypt(ciphertext: String, key: ByteArray): String {
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
val combined = Base64.getDecoder().decode(ciphertext)
val iv = combined.copyOfRange(0, 12)
val ivSpec = GCMParameterSpec(128, iv)
val secretKey = SecretKeySpec(key, "AES")
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec)
val decryptedBytes = cipher.doFinal(combined.copyOfRange(12, combined.size))
return String(decryptedBytes)
}
fun main() {
val key = "01234567890123456789012345678901".toByteArray()
val plaintext = "Hello, world!"
val encryptedText = encrypt(plaintext, key)
println("Encrypted: $encryptedText")
val decryptedText = decrypt(encryptedText, key)
println("Decrypted: $decryptedText")
}
import Foundation
import CryptoKit
func encrypt(plaintext: String, key: SymmetricKey) throws -> Data {
let sealedBox = try AES.GCM.seal(plaintext.data(using: .utf8)!, using: key)
return sealedBox.combined!
}
func decrypt(ciphertext: Data, key: SymmetricKey) throws -> String {
let sealedBox = try AES.GCM.SealedBox(combined: ciphertext)
let decryptedData = try AES.GCM.open(sealedBox, using: key)
return String(data: decryptedData, encoding: .utf8)!
}
func main() {
let key = SymmetricKey(size: .bits256)
let plaintext = "Hello, world!"
let encryptedData = try! encrypt(plaintext: plaintext, key: key)
print("Encrypted: \(encryptedData.base64EncodedString())")
let decryptedText = try! decrypt(ciphertext: encryptedData, key: key)
print("Decrypted: \(decryptedText)")
}
main()
import 'dart:convert';
import 'dart:typed_data';
import 'package:pointycastle/export.dart';
Uint8List encrypt(Uint8List plaintext, Uint8List key) {
final cipher = GCMBlockCipher(AESFastEngine());
final parameters = AEADParameters(KeyParameter(key), 128, Uint8List(12));
cipher.init(true, parameters);
final encrypted = cipher.process(plaintext);
final Uint8List iv = parameters.nonce;
final combined = Uint8List(iv.length + encrypted.length);
combined.setRange(0, iv.length, iv);
combined.setRange(iv.length, combined.length, encrypted);
return combined;
}
Uint8List decrypt(Uint8List ciphertext, Uint8List key) {
final cipher = GCMBlockCipher(AESFastEngine());
final parameters = AEADParameters(KeyParameter(key), 128, Uint8List(12));
cipher.init(false, parameters);
final Uint8List iv = ciphertext.sublist(0, 12);
final encrypted = ciphertext.sublist(12);
final decrypted = cipher.process(Uint8List.fromList(encrypted));
return decrypted;
}
void main() {
final key = Uint8List.fromList(List.generate(32, (index) => index)); // Your 256-bit key
final plaintext = 'Hello, world!';
final plaintextBytes = Uint8List.fromList(utf8.encode(plaintext));
final encrypted = encrypt(plaintextBytes, key);
print('Encrypted: ${base64Encode(encrypted)}');
final decrypted = decrypt(encrypted, key);
print('Decrypted: ${utf8.decode(decrypted)}');
}
Enlaces
Estándares
- OWASP_MASVS_L1:
- MSTG_CRYPTO_2
- MSTG_CRYPTO_3
- MSTG_CRYPTO_4
- OWASP_MASVS_L2:
- MSTG_CRYPTO_2
- MSTG_CRYPTO_3
- MSTG_CRYPTO_4
- PCI_STANDARDS:
- REQ_2_2
- REQ_3_6
- REQ_3_7
- REQ_4_2
- REQ_6_2
- OWASP_MASVS_v2_1:
- MASVS_CRYPTO_1
- MASVS_CRYPTO_2
- SOC2_CONTROLS:
- CC_2_1
- CC_4_1
- CC_6_7
- CC_7_1
- CC_7_2
- CC_7_4
- CC_7_5
- CNIL_FOR_DEVELOPERS:
- DEVELOPERS_4_1_4
- HIPAA_CONTROLS:
- SECURITY252
- SECURITY212
- SECURITY213