Sunday, April 1, 2012

How to decrypt an LTPA cookie and view the user information


Lightweight Third-Party Authentication (LTPA), is an authentication technology used in IBM WebSphere and Lotus Domino products. When accessing web servers that use the LTPA technology it is possible for a web user to re-use their login across physical servers.

In a business that extensively use different IBM products, it is possible to configure LTPA tokens to communicate authentication information between different platforms. For example, an IBM WebSphere Application Server based web application can be configured to propagate the credentials of the logged in user to a WebSphere Process Server based process using LTPA cookies. In a perfect world when everything works fine, there is no issue, but there are several instances when communication does not work as expected and there is a need to see the values that was sent and received as LTPA cookies for diagnostic purposes.


From the trace logs we can find that the LTPA cookie is a Base64 encoded encrypted cookie. The encryption key itself is encrypted in DESede/ECB/PKCS5Padding mode with the SHA hash of the LTPA token password (supplied while exporting the token) padded with 0X0 up to 24 bytes. Then the data in the cookie is encrypted using the above key using AES/CBC/PKCS5Padding or DESede/ECB/PKCS5Padding. So inorder to decrypt the cookie correctly, we need
 1) The LTPA token (which can be read using any ASCII based text editor) that contains the DES Key and the private and public keys
 2) The password using the LTPA token was exported

Once we have these information, use the below code to decrypt the cookie

import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.StringTokenizer;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESedeKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

public class DecryptLTPA {
 private static final String AES = "AES/CBC/PKCS5Padding";
 private static final String DES = "DESede/ECB/PKCS5Padding";

 public static void main(String[] args) {
  String ltpaKey = "<DES key from ltpa token>";
  String ltpaPassword = "<password used to export ltpa token>";
  String tokenCipher = "<the header text to dercypt>";

  try {
   Base64 b = new Base64();
   byte[] secretKey = null;
   MessageDigest md = MessageDigest.getInstance("SHA");
   md.update(ltpaPassword.getBytes());
   byte[] hash3DES = new byte[24];
   System.arraycopy(md.digest(), 0, hash3DES, 0, 20);
   Arrays.fill(hash3DES, 20, 24, (byte) 0);
   secretKey = decrypt(b.decode(ltpaKey), hash3DES, DES);
   byte[] ltpaByteArray = b.decode(tokenCipher);

   String algorithm, userInfo, expires, signature, ltpaPlaintext;
   try {
    algorithm="DES";
    ltpaPlaintext = new String(decrypt(ltpaByteArray, secretKey, DES));
   } catch (Exception e) {
    algorithm="AES";
    ltpaPlaintext = new String(decrypt(ltpaByteArray, secretKey, AES));
   }

   System.err.println("Alogorithm:["+algorithm+"]");
   StringTokenizer st = new StringTokenizer(ltpaPlaintext, "%");
   userInfo = st.nextToken();
   expires = st.nextToken();
   signature = st.nextToken();
   System.err.println("Full token string:[" + ltpaPlaintext + "]");
   Date d = new Date(Long.parseLong(expires));
   SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss z");
   System.err.println("Token is for:[" + userInfo + "]");
   System.err.println("Token expires at:[" + sdf.format(d) + "]");
   System.err.println("Token signature:[" + signature + "]");

  } catch (Exception e) {
   e.printStackTrace();
  }
 }

 public static byte[] decrypt(byte[] ciphertext, byte[] key, String algorithm) throws Exception {
  SecretKey sKey = null;

  if (algorithm.indexOf("AES") != -1) {
   sKey = new SecretKeySpec(key, 0, 16, "AES");
  } else {
   DESedeKeySpec kSpec = new DESedeKeySpec(key);
   SecretKeyFactory kFact = SecretKeyFactory.getInstance("DESede");
   sKey = kFact.generateSecret(kSpec);
  }
  Cipher cipher = Cipher.getInstance(algorithm);

  if (algorithm.indexOf("ECB") == -1) {
   if (algorithm.indexOf("AES") != -1) {
    IvParameterSpec ivs16 = generateIvParameterSpec(key, 16);
    cipher.init(Cipher.DECRYPT_MODE, sKey, ivs16);
   } else {
    IvParameterSpec ivs8 = generateIvParameterSpec(key, 8);
    cipher.init(Cipher.DECRYPT_MODE, sKey, ivs8);
   }
  } else {
   cipher.init(Cipher.DECRYPT_MODE, sKey);
  }
  return cipher.doFinal(ciphertext);
 }

 private static IvParameterSpec generateIvParameterSpec(byte key[], int size) {
  byte[] row = new byte[size];

  for (int i = 0; i < size; i++) {
   row[i] = key[i];
  }

  return new IvParameterSpec(row);
 }
}



5 comments:

  1. Thanks very much for this. I am trying to build a single signon app using LTPA and this was very useful.

    ReplyDelete
  2. Great example!
    Do you have any code to validate the signature?

    ReplyDelete
    Replies
    1. I am currently looking for solution to verify signature, but there are some issues with it.
      So if you have a solution, please, send me a message to jmaniak@mail.ru

      Delete
  3. I decrypt token of IBM websphere portal v6.1 ,it throw Exception:

    Can you help me 3ks.

    javax.crypto.BadPaddingException: Given final block not properly padded
    at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
    at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
    at com.sun.crypto.provider.DESedeCipher.engineDoFinal(DashoA13*..)
    at javax.crypto.Cipher.doFinal(DashoA13*..)
    at ltpatoken.LtpaToken.decrypt(LtpaToken.java:105)
    at ltpatoken.LtpaToken.DecryptLtpaToke(LtpaToken.java:52)
    at ltpatoken.LtpaToken.main(LtpaToken.java:33)

    ReplyDelete
    Replies
    1. That means you are not having the right LTPA token or the right password for decrypting the token. You have to use the same password used while exporting the token and the correct token text from the exported LTPA token

      Delete

Blog Archive