Skip to main content

Insecure generation of random numbers - Static IV

Need

Secure generation of random numbers and dynamic initialization vectors

Context

  • Usage of Dart 2.0 for developing high-performance, cross-platform applications
  • Usage of crypto for cryptographic operations

Description

Non compliant code

import 'dart:convert';
import 'dart:typed_data';
import 'package:crypto/crypto.dart';

class EncryptionService {
static final key = utf8.encode('12345678901234567890123456789012');
static final iv = utf8.encode('1234567890123456');

static String encrypt(String plaintext) {
final c = Cipher('AES/CBC/PKCS7');
final encrypter = Encrypter(c);
final encrypted = encrypter.encrypt(plaintext, iv: IV.fromUtf8(iv));
return encrypted.base64;
}

static String decrypt(String encryptedText) {
final c = Cipher('AES/CBC/PKCS7');
final encrypter = Encrypter(c);
final decrypted = encrypter.decrypt64(encryptedText, iv: IV.fromUtf8(iv));
return decrypted;
}
}

The above code is an encryption service in a Dart backend application. It uses the AES/CBC/PKCS7 cipher for encryption and decryption.

The vulnerability here is that the initialization vector (IV) is hardcoded and static. The IV is supposed to be random and unique for each encryption operation. If an attacker knows the IV, they could potentially reverse engineer the encryption process to predict the random values that should remain as secrets.

In the encrypt and decrypt functions, the IV is set to a static, hard-coded value 1234567890123456. This means that every time data is encrypted, the same IV is used. This makes the encryption predictable and susceptible to reverse engineering attacks.

The key is also hard-coded and static, which is another security vulnerability, but that's not the focus of this context.

By using a static, hard-coded IV, the application is not securely generating random numbers, which is a serious security vulnerability.

Steps

  • Replace the static, hardcoded initialization vector (IV) with a dynamically generated one. This can be achieved by using a secure random number generator.
  • Ensure that a new, unique IV is generated each time a message is encrypted. This prevents an attacker from being able to predict the IV and reverse engineer the encryption.
  • Store the IV alongside the encrypted data. Since the IV does not need to be kept secret (unlike the key), it can be safely stored with the encrypted data. This allows the correct IV to be used when decrypting the data.
  • Update the decrypt function to retrieve the IV from the encrypted data before attempting to decrypt it.

Compliant code

import 'dart:convert';
import 'dart:typed_data';
import 'dart:math';
import 'package:crypto/crypto.dart';

class EncryptionService {
static final key = utf8.encode('12345678901234567890123456789012');

static String encrypt(String plaintext) {
final c = Cipher('AES/CBC/PKCS7');
final encrypter = Encrypter(c);
final iv = _generateRandomIV();
final encrypted = encrypter.encrypt(plaintext, iv: IV.fromUtf8(iv));
return base64.encode(iv) + encrypted.base64;
}

static String decrypt(String encryptedText) {
final c = Cipher('AES/CBC/PKCS7');
final encrypter = Encrypter(c);
final iv = base64.decode(encryptedText.substring(0, 24));
final realEncryptedText = encryptedText.substring(24);
final decrypted = encrypter.decrypt64(realEncryptedText, iv: IV.fromUtf8(iv));
return decrypted;
}

static Uint8List _generateRandomIV() {
final random = Random.secure();
final iv = List<int>.generate(16, (i) => random.nextInt(256));
return Uint8List.fromList(iv);
}
}

The updated code now generates a new, unique initialization vector (IV) each time a message is encrypted. This is done in the _generateRandomIV function, which uses Dart's Random.secure() function to generate a secure random number for each byte of the IV.

The encrypt function has been updated to generate a new IV each time it is called. This IV is then used to encrypt the plaintext message. The encrypted message is then concatenated with the IV (after both have been base64 encoded) to form the final encrypted string. This ensures that the IV is stored alongside the encrypted data, allowing it to be retrieved when the data needs to be decrypted.

The decrypt function has also been updated to retrieve the IV from the encrypted string before attempting to decrypt it. It does this by first base64 decoding the first 24 characters of the string (which represent the IV), and then removing these characters from the string to leave just the encrypted message. The IV and encrypted message are then used to decrypt the original plaintext message.

This approach ensures that a unique IV is used for each encryption operation, preventing an attacker from being able to predict the IV and reverse engineer the encryption.

References