Aller au contenu

Cryptographic Vulnerability: Insecure mode

Vulnérabilité cryptographique : Mode non sécurisé

Description

Un chiffrement par bloc traite les blocs de données d'une taille fixe. Généralement, la taille d'un message est supérieure à la taille du bloc. Par conséquent, le message long est divisé en une série de blocs de message séquentiels, et le chiffrement opère sur ces blocs un par un.

Les chiffrements par bloc courants sont :

  • ECB : (Electronic Code Book) est le moyen le plus simple de traiter une série de blocs de message listés séquentiellement. L'utilisateur prend le premier bloc de texte en clair et le chiffre avec la clé pour produire le premier bloc de texte chiffré. Il prend ensuite le deuxième bloc de texte en clair et suit le même processus avec la même clé, et ainsi de suite. Ce mode est non sécurisé et ne doit pas être utilisé.
  • CBC : (Cipher Block Chaining) fournit une dépendance au message pour la génération du texte chiffré et rend le système non déterministe. En mode CBC, le bloc de texte en clair actuel est ajouté au bloc de texte chiffré précédent, puis le résultat est chiffré avec la clé. Le déchiffrement est donc le processus inverse, qui implique de déchiffrer le texte chiffré actuel, puis d'ajouter le bloc de texte chiffré précédent au résultat.
  • CFB : (Cipher Feedback) chaque bloc de texte chiffré est réinjecté dans le processus de chiffrement afin de chiffrer le bloc de texte en clair suivant. Le mode CFB nécessite un vecteur d'initialisation (IV) comme bloc d'entrée aléatoire initial de n bits. L'IV n'a pas besoin d'être secret. Le mode CFB diffère considérablement du mode ECB, le texte chiffré correspondant à un bloc de texte en clair donné dépend non seulement de ce bloc de texte en clair et de la clé, mais également du bloc de texte chiffré précédent. En d'autres termes, le bloc de texte chiffré dépend du message.
  • OFB : (Output Feedback) implique de réinjecter les blocs de sortie successifs du chiffrement par bloc sous-jacent dans celui-ci. Ces blocs de rétroaction fournissent une chaîne de bits pour alimenter l'algorithme de chiffrement qui agit comme le générateur de flux de clés, comme dans le cas du mode CFB. Le flux de clés généré est combiné par l'opération XOR avec les blocs de texte en clair. Le mode OFB nécessite un IV comme bloc d'entrée aléatoire initial de n bits. L'IV n'a pas besoin d'être secret.
  • CTR : (Counter) Il peut être considéré comme une version basée sur un compteur du mode CFB sans la rétroaction. Dans ce mode, l'expéditeur et le destinataire doivent tous deux avoir accès à un compteur fiable, qui calcule une nouvelle valeur partagée chaque fois qu'un bloc de texte chiffré est échangé. Ce compteur partagé n'est pas nécessairement une valeur secrète, mais le défi est que les deux parties doivent maintenir le compteur synchronisé.

La plupart des implémentations utiliseront par défaut le mode non sécurisé ECB s'il n'est pas explicitement spécifié. Le mode de fonctionnement utilisé pour chiffrer les données est vulnérable et doit être remplacé par un mode sécurisé.

Recommandation

Lors de l'utilisation du chiffrement AES, il est important de choisir le mode de fonctionnement correct pour garantir la confidentialité et l'intégrité des données chiffrées. Le choix du mode peut dépendre du cas d'utilisation spécifique et des exigences de sécurité. Voici quelques modes de chiffrement courants à considérer :

  • Electronic Codebook (ECB) : C'est le mode le plus simple et implique de diviser le texte en clair en blocs et de chiffrer chaque bloc séparément à l'aide de la même clé. Cependant, ce mode est vulnérable aux attaques car des blocs de texte en clair identiques produiront des blocs de texte chiffrés identiques.
  • Cipher Block Chaining (CBC) : Dans ce mode, chaque bloc de texte en clair est combiné par l'opération XOR avec le bloc de texte chiffré précédent avant le chiffrement. Cela ajoute de l'aléatoire et rend plus difficile l'identification de modèles dans les données chiffrées. Cependant, ce mode est vulnérable aux attaques si le même IV (vecteur d'initialisation) est utilisé plusieurs fois ou initialisé avec une valeur non aléatoire.
  • Counter (CTR) : Ce mode implique le chiffrement d'une valeur de compteur et son opération XOR avec le texte en clair pour générer le texte chiffré. C'est un mode populaire pour les applications de streaming car il permet un chiffrement et un déchiffrement en parallèle. Cependant, il nécessite des valeurs de compteur uniques et peut être vulnérable si un attaquant parvient à deviner les valeurs du compteur.
  • Galois/Counter Mode (GCM) : Ce mode offre une protection de la confidentialité et de l'intégrité en utilisant une combinaison du mode CTR et d'un code d'authentification de message (MAC). Il utilise un IV unique et intègre des données supplémentaires telles qu'un numéro de séquence pour prévenir les attaques par rejeu. Ce mode est couramment utilisé dans les applications qui nécessitent une protection de la confidentialité et de l'intégrité.

En général, il est recommandé d'utiliser le mode GCM pour le chiffrement AES car il offre à la fois la confidentialité et la protection de l'intégrité. Cependant, il est important de gérer correctement la clé et l'IV pour garantir la sécurité.

L'utilisation de modes de chiffrement authentifiés tels que GCM peut aider à contrecarrer des attaques telles que les attaques par oracle de remplissage (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)}');
}

Liens

Normes

  • 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