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

ImpactDetails
IntegrityScope: Data Tampering

Encrypted data modified without detection.
AuthorizationScope: Privilege Escalation

Modified tokens grant elevated access.
AuthenticationScope: 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

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

  1. MITRE. "CWE-649: Reliance on Obfuscation or Encryption without Integrity Checking." https://cwe.mitre.org/data/definitions/649.html

  2. NIST. Authenticated Encryption guidelines.