
In a previous blog, I covered how to hack the JVM Kerberos/GSS libraries to enable server-side access to the session key. This was a stop-gap solution to get my SOAP Kerberos enabled web service playing nicely with a WSE 3.0 .NET web service. Obviously its pretty dodgy changing the Sun forbidden packages and then re-distributing them with your product. However, when the pressure is on to get a project done, expediency is pretty attractive.
Note: I have subsequently tested this decoder against Active Directory and Apache Directory Server and it works fine against both - using both DES and ARC-FOUR_HMAC encryption.
To make up for this oversight in the JVM (no server-side session key access through GSS), we must come up with our own service ticket decrypting algorithm. This is an interesting exercise, working from an RFC it made me feel like I was back at university again. Once again, checking the Sun forums gave me bits and pieces of info, but nowhere near a complete solution.
A pocketful of acronyms
The Kerberos v5 protocol uses ASN.1 and the DER to encode and decode all of the Kerberos
protocol messages. ASN.1 or Abstract Syntax Notation One is a way of describing complex types by building the description from simple types. DER or Distinguished Encoding Rules specifies how to encode the ASN.1 notation into a bunch of bytes. Kerberos v5 uses the ASN.1 and DER to encode and decode its messages. Thankfully, there are classes that come with the JVM that we can make use of to achieve our decryption, so we don't really need to worry about ASN.1 or DER too much.
Pseudo-Echo
The basic outline of the function (for those who are interested in the detail) is as follows:
1. Decode the service ticket into a set of DER values.
2. Get the AP-REQ packet and extract the encrypted ticket from it.
3. Get the private key from the server's credentials that matches
the encryption type of the ticket.
4. Decrypt the ticket using the private key.
5. Extract the detail that is needed from the ticket (session key,
client principal name etc)
6. Bobs your uncle!
The actual project binary distribution (see the link below) is all set to run against your KDC/domain controller. All you need to do are:
- Change client.properties to point to your realm, KDC and contain your client login/password and the service principal name of the service you want to begin a session with.
- Change server.properties to point to your realm, KDC and contain your service principal's password.
- Change jaas.conf to contain your service principal name.
- Run client.bat then run server.bat and it should print out the session key bytes and the client principal name.
![]() | KerberosTicketDecoder.java |
package javamonkey.app.gss;
import java.util.Iterator;
import java.util.Set;
import javax.security.auth.Subject;
import javax.security.auth.kerberos.KerberosKey;
import sun.security.krb5.EncryptionKey;
import sun.security.krb5.internal.EncTicketPart;
import sun.security.krb5.internal.Ticket;
import sun.security.krb5.internal.crypto.KeyUsage;
import sun.security.util.DerInputStream;
import sun.security.util.DerValue;
/**
* Kerberos Ticket Decoder provides the ability to decode a Kerberos v5 service
* ticket, so the session key and client principal name can be accessed.
* @author Ants
*/
public class KerberosTicketDecoder {
private byte[] serviceTicket;
private Subject subject;
private boolean decoded = false;
private EncryptionKey sessionKey;
private String cname;
/**
* Construct a Kerberos Ticket Decoder. This takes the service ticket that is
* to be decoded and the JAAS subject that contains the secret key for the
* target service.
* @param serviceTicket the AP-REQ service ticket that is to be decode
* @param subject the JAAS subject containing the secret key for the server
* principal
*/
public KerberosTicketDecoder( byte[] serviceTicket, Subject subject) {
this.serviceTicket = serviceTicket;
this.subject = subject;
}
/**
* Get the client principal name from the decoded service ticket.
* @return the client principal name
*/
public String getClientPrincipalName() {
if ( !decoded) {
decodeServiceTicket();
}
return cname;
}
/**
* Get the session key from the decoded service ticket.
* @return the session key
*/
public EncryptionKey getSessionKey() {
if ( !decoded) {
decodeServiceTicket();
}
return sessionKey;
}
// Decode the service ticket.
private void decodeServiceTicket() {
try {
parseServiceTicket( serviceTicket);
decoded = true;
}
catch ( Exception e) {
e.printStackTrace();
}
}
// Parses the service ticket (GSS AP-REQ token)
private void parseServiceTicket( byte[] ticket) throws Exception {
DerInputStream ticketStream = new DerInputStream( ticket);
DerValue[] values = ticketStream.getSet( ticket.length, true);
// Look for the AP_REQ.
//
// AP-REQ ::= [APPLICATION 14] SEQUENCE
for (int i=0; i<values.length; i++) {
DerValue value = values[i];
if ( value.isConstructed((byte)14)) {
value.resetTag( DerValue.tag_Set);
parseApReq( value.toDerInputStream(), value.length());
return;
}
}
throw new Exception( "Could not find AP-REQ in service ticket.");
}
// Parse the GSS AP-REQ token.
private void parseApReq( DerInputStream reqStream, int len) throws Exception {
byte apOptions = 0;
DerValue ticket = null;
DerValue[] values = reqStream.getSet(len, true);
//
// AP-REQ ::= {
// pvno[0] INTEGER,
// msg-type[1] INTEGER,
// ap-options[2] APOptions,
// ticket[3] Ticket,
// authenticator[4] EncryptedData
// }
//
for (int i=0; i<values.length; i++) {
DerValue value = values[i];
if ( value.isContextSpecific((byte)2)) {
apOptions = value.getData().getDerValue().getBitString()[0];
// apOptions not used yet.
}
else if ( value.isContextSpecific((byte)3)) {
ticket = value.getData().getDerValue();
}
}
if ( ticket == null) {
throw new Exception("No Ticket found in AP-REQ PDU");
}
decryptTicket( new Ticket(ticket), subject);
}
// Decrypt the ticket.
// APOptions ::= BIT STRING {
// reserved(0),
// use-session-key(1),
// mutual-required(2)
// }
// Ticket ::= [APPLICATION 1] SEQUENCE {
// tkt-vno[0] INTEGER,
// realm[1] Realm,
// sname[2] PrincipalName,
// enc-part[3] EncryptedData
// }
//
// EncTicketPart ::= [APPLICATION 3] SEQUENCE {
// flags[0] TicketFlags,
// key[1] EncryptionKey,
// crealm[2] Realm,
// cname[3] PrincipalName,
// transited[4] TransitedEncoding,
// authtime[5] KerberosTime,
// starttime[6] KerberosTime OPTIONAL,
// endtime[7] KerberosTime,
// renew-till[8] KerberosTime OPTIONAL,
// caddr[9] HostAddresses OPTIONAL,
// authorization-data[10] AuthorizationData OPTIONAL
// }
private void decryptTicket( Ticket ticket, Subject svrSub)
throws Exception {
System.out.println( "key encryption type = " + ticket.encPart.getEType());
// Get the private key that matches the encryption type of the ticket.
EncryptionKey key = getPrivateKey( svrSub, ticket.encPart.getEType());
// Decrypt the service ticket and get the cleartext bytes.
byte[] ticketBytes = ticket.encPart.decrypt( key, KeyUsage.KU_TICKET);
if ( ticketBytes.length <= 0) {
throw new Exception( "Key is empty.");
}
// EncTicketPart provides access to the decrypted attributes of the service
// ticket.
byte[] temp = ticket.encPart.reset( ticketBytes, true);
EncTicketPart encPart = new EncTicketPart( temp);
this.sessionKey = encPart.key;
this.cname = encPart.cname.toString();
}
// Get the private server key.
private EncryptionKey getPrivateKey( Subject sub, int keyType)
throws Exception {
KerberosKey key = getKrbKey( sub, keyType);
return new EncryptionKey( key.getEncoded(), key.getKeyType(),
new Integer( keyType));
}
// Get the Kerberos Key from the subject that matches the given key type.
private KerberosKey getKrbKey( Subject sub, int keyType) {
Set<Object> creds = sub.getPrivateCredentials(Object.class);
for ( Iterator<Object> i = creds.iterator(); i.hasNext();) {
Object cred = i.next();
if ( cred instanceof KerberosKey) {
KerberosKey key = (KerberosKey) cred;
if ( key.getKeyType() == keyType) {
return (KerberosKey) cred;
}
}
}
return null;
}
}
Download the NetBeans project and source/binaries here. Let me know if you are having trouble in getting it to work. Also let me know if you test it against the MIT KDC as I don't have one setup. |

Download the NetBeans project and source/binaries here.
Thx, great work but I have some problems:
1) I use heimdal
2) Using V5 protokol
3) supported enctypes: des-cbc-md5
When I try to decrypte ServiceTicket I get error: extra data given to DerValue constructor
in code:
EncTicketPart encPart = new EncTicketPart(new DerValue(ticketBytes, 0,ticketBytes.length - 1)); // Strip off the trailing 00
I don't get it working.
Is there any easy way to decrypte the ticket using keytab file?
Hi, I've changed the code slightly to get it working now. You can either download the project and try it or look at the code in the blog post. The change was as follows:
byte[] ticketBytes = ticket.encPart.decrypt( key, KeyUsage.KU_TICKET);
if ( ticketBytes.length <= 0) {
throw new Exception( "Key is empty.");
}
byte[] temp = ticket.encPart.reset( ticketBytes, true);
EncTicketPart encPart = new EncTicketPart( temp);
Let me know how it works.
Hi,
I'm trying to write a Java service that needs to talk to a c kerberos client that looks like the client here...
http://www.h5l.org/manual/HEAD/krb5/page_introduction.html
The client calls krb5_sendauth().
And I need to simulate in java the service side's krb5_recvauth()
Is the code you have here doing that?
If not can you please give me some pointers since you're an expert at this.
Hi, thanks for this great article as its really helped me out. I am wondering if you've had any success exttracting the Windows PAC from the authorizationData? I am not sure how to extract the PAC from the ASN1 encoded byte array returned by authorizationData.asn1Encode(). Any thoughts on this?
Joe B, Did you ever find out how to extract the PAC? Thats of interest to me as well!
-AR
Thanks for writing this article. I have been able to receive a token, and inside are two tickets. one has an RC4-HMAC TGT key, and one has a DES-CBC-MD5 key. So, your code, which only looks for KerberosKeys doesn't know what to do with these 2 KerberosTickets. I took the key out of the RC4 ticket, to try decrypt the encPart with it, but I get 'Checksum failed'. Does the fact that I have 2 KerberosTickets and no KerberosKeys mean anything?
Hmm. Otherwise, not entirely sure what changed, but now i have 5 KerberosKeys (1,3,16,17,23), and it finds the RC4 key to decrypt with, but still gets checksum error. I cannot get acceptSecurityContext to not give me a Checksum error. I am using Java 1.6+.
Solved. For all those crazy kids who also get the checksum error, remember to get a keytab and user/computer mapping, and remove the spn mapping to your computer, because your AD cannot have the same SPN applied to 2 objects. i think that will help someone some day.
Nice post
Wow good and better one
Dan,
I'm also getting the frustrating checksum failure. May be you can help me. Can you please explaing what do you mean by removing the spn mapping to "your computer". Does "your computer" here mean the client PC or the server running the service protected by kerberos.
Good post
Is your code released with a license/usage policy? Great work!
Use it as you wish, I don't have a license for it. This blog is all about learning, so people are free to take what they want and use how they wish. :-)
I just got back to taking this for a spin and am having some issues validating a signature using the session key and Apache XML Security. Essentially, I'm using the session key this way, am I missing something?
SecretKey secretKey = new SecretKeySpec(decoder.getSessionKey().getBytes(), "DES");
xmlsec.verify(xml, secretKey, resolver);
Output is:
15:56:49,954 DEBUG [ElementProxy] setElement("Signature", "null")
15:56:49,955 DEBUG [ElementProxy] setElement("SignedInfo", "null")
15:56:49,955 DEBUG [ElementProxy] setElement("SignatureMethod", "null")
15:56:49,955 DEBUG [ElementProxy] setElement("KeyInfo", "null")
15:56:49,955 DEBUG [XMLSignature] SignatureMethodURI = http://www.w3.org/2000/09/xmldsig#hmac-sha1
15:56:49,955 DEBUG [IntegrityHmac$IntegrityHmacSHA1] engineGetJCEAlgorithmString()
15:56:49,956 DEBUG [XMLSignature] jceSigAlgorithm = HmacSHA1
15:56:49,956 DEBUG [XMLSignature] jceSigProvider = Apple
15:56:49,956 DEBUG [XMLSignature] PublicKey = javax.crypto.spec.SecretKeySpec@180f5
15:56:49,956 DEBUG [SignerOutputStream] Canonicalized SignedInfo:
15:56:49,956 DEBUG [SignerOutputStream] ...
15:56:49,956 WARN [XMLSignature] Signature verification failed.
Hi,
This is very informative. Thanks for sharing it.
I am trying to parse AP-REQ inside a pcap file.
I have the correct keytab and I am able to see the decrypted packets in wireshark.
But, I want to do this in C++ and Heimdal.
So, it will be helpful, if you can tell me, if any API's are available for C++?
Thanks,
Narendra
Did anyone used this code with application-server SSO solution, such as SPNEGO?
I'm using SPNEGO and I need to access informations from PAC field.
Well, I've developed the solution myself. Your code, unfortunatelly, didn't work. But code from JaasLounge didn't work either.
I've described solution, partially with source code, in stackoverflow discussion:
http://stackoverflow.com/q/4508555/531954
Not sure what you are talking about lechlukasz, this code definitely decrypts the AP-REQ......and has been tested with Active Directory, Apache Directory Server, and interop-ing with WSE 3.0.
My code doesn't give access to PAC if thats what you were after, but that wasn't the point of the blog - it was to get access to the session key on the server side, which the Java security api doesn't allow you to access.
Hi,
Can you please make the API to work with IBM JDK also? Or is there any easier way to get the session key from service ticket using IBM JDK?
Fantastic work ! I follow it and solved my issues. You explained everything very clearly and briefly. I am impressed with the way you keep everything in the front of us. Your post is very useful for me.