Reliance on Obfuscation or Encryption of Security-Relevant Inputs without Integrity Checking
Description
Reliance on Obfuscation or Encryption of Security-Relevant Inputs without Integrity Checking occurs when an application encrypts or obfuscates security-critical data but fails to verify that the data hasn't been tampered with. Encryption provides confidentiality but not integrity. Attackers can modify encrypted data in predictable ways (bit-flipping, block manipulation) to alter the decrypted values without knowing the encryption key.
Risk
Attackers can modify encrypted tokens to gain unauthorized access. Price manipulation by flipping bits in encrypted price values. Role escalation by modifying encrypted role identifiers. Session tampering by manipulating encrypted session data. Privilege escalation without knowing encryption keys. Data corruption through targeted bit manipulation.
Solution
Use authenticated encryption (AES-GCM, ChaCha20-Poly1305). Add HMAC to encrypted data for integrity verification. Verify integrity before decryption. Use digital signatures for non-repudiation. Never rely solely on encryption for security. Implement proper key management with separate keys for encryption and authentication.
Common Consequences
| Impact | Details |
|---|---|
| Integrity | Scope: Data Tampering Encrypted data modified without detection. |
| Authorization | Scope: Privilege Escalation Modified tokens grant elevated access. |
| Authentication | Scope: Bypass Tampered credentials accepted as valid. |
Example Code + Solution Code
Vulnerable Code
// VULNERABLE: Encryption without integrity check
public class VulnerableTokenService {
private SecretKey key;
private Cipher cipher;
public VulnerableTokenService() throws Exception {
key = new SecretKeySpec(secretKeyBytes, "AES");
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); // No authentication!
}
public String createToken(String userId, String role) throws Exception {
String data = userId + ":" + role;
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] iv = cipher.getIV();
byte[] encrypted = cipher.doFinal(data.getBytes());
// VULNERABLE: No integrity protection
// Attacker can modify encrypted bytes
return Base64.encode(iv) + "." + Base64.encode(encrypted);
}
public String[] parseToken(String token) throws Exception {
String[] parts = token.split("\\.");
byte[] iv = Base64.decode(parts[0]);
byte[] encrypted = Base64.decode(parts[1]);
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
// VULNERABLE: Decrypts without verifying integrity
// Bit-flipped data will decrypt to modified values
String data = new String(cipher.doFinal(encrypted));
return data.split(":");
}
}
// VULNERABLE: Obfuscation as security
public class VulnerableObfuscatedPrice {
public String obfuscatePrice(double price) {
// VULNERABLE: Just XOR - easily reversible and modifiable
long priceBits = Double.doubleToLongBits(price);
long obfuscated = priceBits ^ 0xDEADBEEF;
return Long.toHexString(obfuscated);
}
public double deobfuscatePrice(String obfuscated) {
// VULNERABLE: No integrity check
// Attacker can modify hex and get different price
long bits = Long.parseUnsignedLong(obfuscated, 16);
long priceBits = bits ^ 0xDEADBEEF;
return Double.longBitsToDouble(priceBits);
}
}
# VULNERABLE: Python AES without authentication
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
import base64
class VulnerableEncryption:
def __init__(self, key):
self.key = key # 16/24/32 bytes
def encrypt(self, data):
# VULNERABLE: CBC mode without HMAC
iv = get_random_bytes(16)
cipher = AES.new(self.key, AES.MODE_CBC, iv)
# Pad data
padded = self._pad(data.encode())
encrypted = cipher.encrypt(padded)
# Return IV + ciphertext - no authentication tag!
return base64.b64encode(iv + encrypted).decode()
def decrypt(self, token):
data = base64.b64decode(token)
iv = data[:16]
ciphertext = data[16:]
cipher = AES.new(self.key, AES.MODE_CBC, iv)
# VULNERABLE: No integrity verification
# Modified ciphertext will decrypt to modified plaintext
decrypted = cipher.decrypt(ciphertext)
return self._unpad(decrypted).decode()
# VULNERABLE: ECB mode allows block manipulation
class VulnerableECBEncryption:
def __init__(self, key):
self.key = key
def encrypt(self, data):
# VULNERABLE: ECB mode - identical blocks encrypt identically
cipher = AES.new(self.key, AES.MODE_ECB)
padded = self._pad(data.encode())
return base64.b64encode(cipher.encrypt(padded)).decode()
def decrypt(self, token):
cipher = AES.new(self.key, AES.MODE_ECB)
# VULNERABLE: Blocks can be reordered/replaced
decrypted = cipher.decrypt(base64.b64decode(token))
return self._unpad(decrypted).decode()
# VULNERABLE: Cookie encryption without integrity
def create_auth_cookie_vulnerable(user_id, role):
data = f"{user_id}|{role}".encode()
cipher = AES.new(key, AES.MODE_CBC, iv)
encrypted = cipher.encrypt(pad(data))
# VULNERABLE: No HMAC
return base64.b64encode(iv + encrypted)
// VULNERABLE: Node.js encryption without authentication
const crypto = require('crypto');
class VulnerableEncryption {
constructor(key) {
this.key = key; // 32 bytes for AES-256
this.algorithm = 'aes-256-cbc'; // No authentication!
}
encrypt(data) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(this.algorithm, this.key, iv);
let encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex');
// VULNERABLE: No HMAC or authentication tag
return iv.toString('hex') + ':' + encrypted;
}
decrypt(token) {
const [ivHex, encrypted] = token.split(':');
const iv = Buffer.from(ivHex, 'hex');
const decipher = crypto.createDecipheriv(this.algorithm, this.key, iv);
// VULNERABLE: No integrity check before decryption
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
}
// VULNERABLE: Base64 "encryption" of roles
function encodeRoleVulnerable(role) {
// VULNERABLE: Just encoding, not encryption
// Anyone can decode and re-encode different value
return Buffer.from(role).toString('base64');
}
function decodeRoleVulnerable(encoded) {
return Buffer.from(encoded, 'base64').toString('utf8');
}
<?php
// VULNERABLE: PHP encryption without integrity
class VulnerableEncryption {
private $key;
private $method = 'AES-256-CBC'; // No authentication!
public function __construct($key) {
$this->key = $key;
}
public function encrypt($data) {
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($this->method));
$encrypted = openssl_encrypt($data, $this->method, $this->key, 0, $iv);
// VULNERABLE: No HMAC
return base64_encode($iv . base64_decode($encrypted));
}
public function decrypt($token) {
$data = base64_decode($token);
$ivLength = openssl_cipher_iv_length($this->method);
$iv = substr($data, 0, $ivLength);
$encrypted = base64_encode(substr($data, $ivLength));
// VULNERABLE: Decrypts modified data
return openssl_decrypt($encrypted, $this->method, $this->key, 0, $iv);
}
}
// VULNERABLE: "Encrypted" session data
function setSessionVulnerable($userId, $role, $key) {
$data = "$userId:$role";
$iv = random_bytes(16);
$encrypted = openssl_encrypt($data, 'AES-128-CBC', $key, 0, $iv);
// VULNERABLE: Attacker can bit-flip to change role
setcookie('session', base64_encode($iv . base64_decode($encrypted)));
}
?>
Fixed Code
// SAFE: Authenticated encryption with AES-GCM
public class SafeTokenService {
private SecretKey key;
public SafeTokenService(byte[] keyBytes) {
this.key = new SecretKeySpec(keyBytes, "AES");
}
public String createToken(String userId, String role) throws Exception {
String data = userId + ":" + role;
// Use AES-GCM which provides authentication
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
byte[] iv = new byte[12]; // GCM recommended IV size
SecureRandom.getInstanceStrong().nextBytes(iv);
GCMParameterSpec spec = new GCMParameterSpec(128, iv); // 128-bit tag
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
byte[] ciphertext = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
// ciphertext includes authentication tag
return Base64.getUrlEncoder().encodeToString(iv) + "." +
Base64.getUrlEncoder().encodeToString(ciphertext);
}
public String[] parseToken(String token) throws Exception {
String[] parts = token.split("\\.");
byte[] iv = Base64.getUrlDecoder().decode(parts[0]);
byte[] ciphertext = Base64.getUrlDecoder().decode(parts[1]);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.DECRYPT_MODE, key, spec);
try {
// SAFE: GCM verifies integrity before decrypting
// Throws AEADBadTagException if tampered
String data = new String(cipher.doFinal(ciphertext), StandardCharsets.UTF_8);
return data.split(":");
} catch (AEADBadTagException e) {
throw new SecurityException("Token integrity check failed");
}
}
}
// SAFE: HMAC with encryption (Encrypt-then-MAC)
public class SafeEncryptThenMac {
private SecretKey encKey;
private SecretKey macKey;
public String encrypt(String data) throws Exception {
// Encrypt
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv = new byte[16];
SecureRandom.getInstanceStrong().nextBytes(iv);
cipher.init(Cipher.ENCRYPT_MODE, encKey, new IvParameterSpec(iv));
byte[] ciphertext = cipher.doFinal(data.getBytes());
// MAC over IV + ciphertext
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(macKey);
mac.update(iv);
mac.update(ciphertext);
byte[] tag = mac.doFinal();
return Base64.encode(iv) + "." + Base64.encode(ciphertext) + "." + Base64.encode(tag);
}
public String decrypt(String token) throws Exception {
String[] parts = token.split("\\.");
byte[] iv = Base64.decode(parts[0]);
byte[] ciphertext = Base64.decode(parts[1]);
byte[] tag = Base64.decode(parts[2]);
// Verify MAC first
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(macKey);
mac.update(iv);
mac.update(ciphertext);
byte[] expectedTag = mac.doFinal();
// SAFE: Constant-time comparison
if (!MessageDigest.isEqual(tag, expectedTag)) {
throw new SecurityException("Integrity check failed");
}
// Only decrypt after MAC verification
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, encKey, new IvParameterSpec(iv));
return new String(cipher.doFinal(ciphertext));
}
}
# SAFE: Python authenticated encryption
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives import hashes, hmac
from cryptography.exceptions import InvalidTag
import os
import base64
class SafeEncryption:
def __init__(self, key):
self.key = key # 32 bytes for AES-256-GCM
def encrypt(self, data):
# Use AES-GCM for authenticated encryption
aesgcm = AESGCM(self.key)
nonce = os.urandom(12) # 96-bit nonce for GCM
# Encrypt with authentication
ciphertext = aesgcm.encrypt(nonce, data.encode(), None)
# ciphertext includes 16-byte authentication tag
return base64.urlsafe_b64encode(nonce + ciphertext).decode()
def decrypt(self, token):
data = base64.urlsafe_b64decode(token)
nonce = data[:12]
ciphertext = data[12:]
aesgcm = AESGCM(self.key)
try:
# SAFE: Verifies integrity before decrypting
plaintext = aesgcm.decrypt(nonce, ciphertext, None)
return plaintext.decode()
except InvalidTag:
raise ValueError("Token integrity check failed - possible tampering")
# SAFE: Encrypt-then-MAC implementation
class SafeEncryptThenMAC:
def __init__(self, enc_key, mac_key):
self.enc_key = enc_key
self.mac_key = mac_key
def encrypt(self, data):
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
iv = os.urandom(16)
cipher = Cipher(algorithms.AES(self.enc_key), modes.CBC(iv))
encryptor = cipher.encryptor()
# Pad data
padded = self._pad(data.encode())
ciphertext = encryptor.update(padded) + encryptor.finalize()
# Compute HMAC over IV + ciphertext
h = hmac.HMAC(self.mac_key, hashes.SHA256())
h.update(iv + ciphertext)
tag = h.finalize()
return base64.urlsafe_b64encode(iv + ciphertext + tag).decode()
def decrypt(self, token):
data = base64.urlsafe_b64decode(token)
iv = data[:16]
ciphertext = data[16:-32]
tag = data[-32:]
# Verify HMAC first
h = hmac.HMAC(self.mac_key, hashes.SHA256())
h.update(iv + ciphertext)
try:
h.verify(tag)
except Exception:
raise ValueError("Integrity check failed")
# Only decrypt after verification
cipher = Cipher(algorithms.AES(self.enc_key), modes.CBC(iv))
decryptor = cipher.decryptor()
padded = decryptor.update(ciphertext) + decryptor.finalize()
return self._unpad(padded).decode()
// SAFE: Node.js authenticated encryption
const crypto = require('crypto');
class SafeEncryption {
constructor(key) {
this.key = key; // 32 bytes for AES-256-GCM
}
encrypt(data) {
// Use AES-GCM for authenticated encryption
const iv = crypto.randomBytes(12); // 96-bit IV for GCM
const cipher = crypto.createCipheriv('aes-256-gcm', this.key, iv);
let encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex');
// Get authentication tag
const authTag = cipher.getAuthTag();
// Include IV and auth tag
return iv.toString('hex') + ':' + encrypted + ':' + authTag.toString('hex');
}
decrypt(token) {
const [ivHex, encrypted, authTagHex] = token.split(':');
const iv = Buffer.from(ivHex, 'hex');
const authTag = Buffer.from(authTagHex, 'hex');
const decipher = crypto.createDecipheriv('aes-256-gcm', this.key, iv);
decipher.setAuthTag(authTag);
try {
// SAFE: GCM verifies integrity
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
} catch (error) {
throw new Error('Token integrity check failed');
}
}
}
// SAFE: Using libsodium for authenticated encryption
const sodium = require('sodium-native');
class SafeSodiumEncryption {
constructor(key) {
this.key = key; // sodium.crypto_secretbox_KEYBYTES
}
encrypt(data) {
const nonce = Buffer.alloc(sodium.crypto_secretbox_NONCEBYTES);
sodium.randombytes_buf(nonce);
const plaintext = Buffer.from(data);
const ciphertext = Buffer.alloc(plaintext.length + sodium.crypto_secretbox_MACBYTES);
// Authenticated encryption
sodium.crypto_secretbox_easy(ciphertext, plaintext, nonce, this.key);
return nonce.toString('base64') + '.' + ciphertext.toString('base64');
}
decrypt(token) {
const [nonceB64, ciphertextB64] = token.split('.');
const nonce = Buffer.from(nonceB64, 'base64');
const ciphertext = Buffer.from(ciphertextB64, 'base64');
const plaintext = Buffer.alloc(ciphertext.length - sodium.crypto_secretbox_MACBYTES);
// SAFE: Verifies authentication before decrypting
if (!sodium.crypto_secretbox_open_easy(plaintext, ciphertext, nonce, this.key)) {
throw new Error('Decryption failed - integrity check failed');
}
return plaintext.toString();
}
}
Exploited in the Wild
Cookie Tampering
Bit-flipping attacks on encrypted session cookies.
Price Manipulation
Modifying encrypted prices in e-commerce.
Padding Oracle Attacks
CBC padding oracles enabling decryption.
Tools to test/exploit
-
PadBuster — padding oracle attacks.
-
Custom bit-flipping scripts.
-
Cryptographic analysis tools.
CVE Examples
-
CVE-2016-0777: OpenSSH memory disclosure.
-
ASP.NET padding oracle (MS10-070).
References
-
MITRE. "CWE-649: Reliance on Obfuscation or Encryption without Integrity Checking." https://cwe.mitre.org/data/definitions/649.html
-
NIST. Authenticated Encryption guidelines.