Background
SailPoint IdentityIQ (IIQ) uses a custom encryption scheme to protect application-level configuration secrets. These include database credentials, connector passwords, service account tokens, and similar operational values. These are not user passwords. IdentityIQ does not manage them through lifecycle or authentication logic. Instead, internal utility classes encrypt them and store the results directly in configuration files and the database.
SailPoint does not document how to decrypt these values outside a running IdentityIQ instance. In configuration files, they appear as opaque strings like:
1:ACP:DDVEEogM5MwWjarEM7jgR1hIMAurKChltZba9W/ug+Bd5CvR3+uKJOBVxiRpY8Fj
with no supported way to confirm what they contain unless you have a working IdentityIQ system and elevated access. The number before :ACP: is the alias. That value selects which AES key IdentityIQ uses for encryption and decryption.
This post documents the exact encryption and key-handling behavior used by IdentityIQ. The analysis comes directly from decompiled Java classes. It also introduces iiq_decrypt.py, a standalone Python tool that performs decryption without requiring the IIQ runtime.
Note: This is not an exploit. Everything described here reflects normal IdentityIQ behavior. The point is simple: if an attacker can read the configuration files, the encryption offers limited protection.
This tool is available on GitHub: https://github.com/covertchannelblog/iiq_decrypt
Scope and Constraints
In scope
- Configuration secrets stored in the alias:ACP:<base64> format
- Default-key encryption (alias 1)
- Keystore-backed encryption (alias 2+)
- Key storage, masking, and retrieval logic (iiq.cfg, iiq.dat)
- Decryption without a running IIQ instance
Out of scope
- End-user passwords or authentication mechanisms
- Authorization bypasses
- Tampering, re-encryption, or privilege escalation
High-Level Architecture
IdentityIQ implements configuration encryption using three cooperating components:
- Transformer – Performs AES encryption and decryption and formats the output
- SPKeyStore – Chooses the correct key based on the alias prefix
- AsyncHandler – Applies a reversible masking routine to the keystore password
All cryptographic operations ultimately use 128-bit AES. The surrounding key-handling logic is specific to IdentityIQ and undocumented.
Transformer Class: Encryption Function
At the lowest level, IdentityIQ uses a standard AES construction:
- Algorithm: AES/CBC/PKCS5Padding
- IV: Random 16-byte initialization vector
- Layout: IV (16 bytes) followed by ciphertext
- Encoding: Base64
This behavior appears directly in the decompiled Transformer class.
Decompiled excerpt (Transformer.java)
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv = new byte[16];
SecureRandom random = new SecureRandom();
random.nextBytes(iv);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
byte[] encrypted = cipher.doFinal(clearText.getBytes("UTF-8"));
After encryption, IdentityIQ prepends the IV to the ciphertext and Base64-encodes the result:
byte[] combined = new byte[iv.length + encrypted.length];
System.arraycopy(iv, 0, combined, 0, iv.length);
System.arraycopy(encrypted, 0, combined, iv.length, encrypted.length);
String encoded = Base64.encodeBytes(combined);
The final storage format is:
<alias>:ACP:<base64( [IV (16 bytes)] [ciphertext] )>
SPKeyStore Class: Key Resolution
The alias prefix at the start of the encrypted string determines which AES key IdentityIQ uses.
Alias 1: The Default Key
Alias 1 maps to a static AES key compiled directly into the IdentityIQ codebase.
Decompiled excerpt (SPKeyStore.java)
private static final byte[] DEFAULT_KEY = new byte[] {
0x2d, 0x45, 0x7a, 0x13, ...
};
During key resolution:
if ("1".equals(alias)) {
return new SecretKeySpec(DEFAULT_KEY, "AES");
}
This has two important consequences:
- Every IdentityIQ deployment ships with the same default key
- Anyone with the ciphertext can decrypt secrets that use alias 1
The default key exists to simplify initial setup, but it often remains in use indefinitely.
Alias 2+: Keystore-Backed Keys
Aliases greater than 1 resolve to keys stored in a Java JCEKS keystore, usually located at:
WEB-INF/classes/iiq.dat
Key retrieval is direct:
Key key = keyStore.getKey(alias, password);
return new SecretKeySpec(key.getEncoded(), "AES");
IdentityIQ does not apply key derivation, stretching, or wrapping beyond the JCEKS format.
AsyncHandler: Password Masking
Access to iiq.dat requires a password stored in iiq.cfg. This value is not plaintext, but the unmasking logic and decryption key live in the application binary.
The code responsible sits in AsyncHandler.
Decompiled excerpt (SPKeyStore.java — inner class AsyncHandler)
byte[] decoded = Base64.decode(maskedValue);
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, defaultKey);
byte[] unmasked = cipher.doFinal(decoded);
After decryption, the method slices out fixed offsets:
byte[] password = Arrays.copyOfRange(unmasked, 624, unmasked.length - 312);
The actual password sits between fixed-length blocks of random data. This hides its position, not its value.
Implication: If you can read iiq.cfg, you can recover the keystore password using logic already embedded in IdentityIQ.
End-to-End Flow
When you put the pieces together, decryption follows a simple decision path:
The Tool
iiq_decrypt.py is a small Python implementation of the behavior described above.
Capabilities
- Default-key decryption (1:ACP:) with no external files
- Keystore-backed decryption (2+:ACP:) using iiq.cfg and iiq.dat
- JCEKS key enumeration for alias validation
- Hard failure on padding or key mismatch
Dependencies
- pycryptodome (AES primitives)
- pyjks (JCEKS parsing)

The tool was validated against a live IdentityIQ 8.5 environment:
- Default-key secrets, including database credentials
- Keystore-backed secrets such as connector passwords
- Plaintext values containing special characters
Security Implications
- Alias 1 relies on a global, static AES key baked into the binary
- The keystore password masking is fully reversible
- Read access to iiq.cfg and iiq.dat allows recovery of all configuration secrets
- The design discourages casual inspection but does not stop a local attacker
Bottom Line: Treat iiq.cfg and iiq.dat as equivalent to plaintext secrets. Anyone who has these files can recover every encrypted configuration value.
Decryption Demo
Limitations
- Secrets with aliases greater than 1 require access to both iiq.cfg and iiq.dat.