Secure generation of random numbers and dynamic initialization vectors
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.
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.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.