Using Kerberos to lock, Part 3: Establish security communication with e-banking
content:
Send a TGT request to the KDC server to extract the ticket and the key from the ticket response to obtain a service ticket generation service ticket request from the service ticket response to extract the service ticket and the subclile key to create a secure communication context to the electronic bank's business logic server Send a Security Message Decoding Server Message Example Mobile Bank Application Conclusion Reference Information About The Author's Evaluation
related information:
Simplify Enterprise Java Authentication with Single Sign-on
subscription:
DeveloperWorks Newsletter
Set the server, request the ticket, get a response
FAHEEM KHAN Free Consultant March 27, 2004
If you have already learned the first two parts of this series, you can start the third part, that is, the last part, you will set a KDC server, send the Kerberos bill request and get its response. You will then learn the low-level ASN1 processing method required to deal with the response of the KDC server to obtain a ticket and session key. After obtaining a service bill, a request for establishing a security context will be sent to the electronic bank's business logic server. Finally, you will learn to actually communicate with the e-banking logic server.
Recalling the first article of this series, it introduces mobile banking MIDlet applications and explains how Kerberos meets the security requirements of this application. The article also describes the data format used by Kerberos to provide security. The second article of this series shows how to generate an ASN.1 data type in J2ME. The DES encryption is introduced with the Bounce Castle encryption library, and the Kerberos key is generated with the user's password. Finally, put these content together and generate a Kerberos bill request. Kerberos customers developed in this series of articles do not require a particular Kerberos server, which can be implemented using all KDC. The reference part contains some links to the KDC server that can be used by the Kerberos client. Regardless of the selected KDC server, you must tell the server that the user mobile bank MIDlet does not need to send pre-certified data in the request of TGT (PADATA, the first article of this series) of the KDC-REQ structure shown in FIG. Three fields). Depending on the Kerberos specification, send the PADATA field is optional. Thus, the KDC server typically allows configuring specific users such that the TGT request can be accepted for the configured user without the PADATA field. To minimize the load on the Kerberos client, you must tell the KDC server to accept the electronic bank mobile user without PADATA-free TGT request. In this example, I use Microsoft's KDC server to test the J2ME-based mobile banking application. The readme.txt file in this source code download contains how to set the KDC server and how to tell it to accept the guidance of TGT requests without PADATA fields. (In my article "Simplified Enterprise Java Certification" with single point, I used the same KDC server to show a single sign-on. For the link, please refer to the reference material.) Send the TGT to the KDC server to request the KDC server, Send TGT requests to it. Take a look at the getticketresponse () method in Listing 1. It is the same as the GetticketResponse () method in the list 12 in this series, only one place: this method now includes J2ME code to send TGT requests to the KDC server. The new code is labeled in Listing 1, so you can observe the new code not in Listing 12. In the New Code section of Listing 1, I created a new DataGram object (DG) based on an existing DataGramConnection object (DC). Note that in the last section of this article, the mobile bank MIDlet creates a DC object I used here to create a DataGram object. After creating a DG object, the getticketResponse () method calls its Send () method, sending a ticket request to the KDC server. After sending a TGT request to the server, the GetticketPonse () method of the Listing 1 receives the TGT response of the server. After receiving the response, it returns the response to the calling application. Listing 1. GetticketResponse () method public byte [] getticketResponse ()
{
Byte TicketRequest [];
BYTE MSG_TYPE [];
Byte pvno [] = getTagandLengthbytes (asn1dattypes.context_specific,
1, GetIntegerbytes (5));
Msg_type = gettagandLengthbytes (Asn1DataTypes.Context_specific, 2, getintegerbytes (10));
BYTE KDC_OPTIONS [] = gettagandLengthbytes (asn1dattypes.context_specific,
0, getBitStringBytes (new byte [5]));
Byte generalstringsequence [] = getsequenceBytes
getGeneralstringBytes (username);
Byte name_string [] = gettagandLengthbytes (asn1dattypes.context_specific,
1, generalstringsequence);
Byte name_type [] = gettagandLengthbytes (asn1dattypes.context_specific,
0, getIntegerbytes (asn1dattypes.nt_principal);
Byte PrincipalNamesequence [] = GetSequenceBytes
ConcatenateBytes (Name_TYPE, NAME_STRING);
Byte cname [] = gettagandLengthbytes (asn1dattypes.context_specific,
1, PrincipalNamesequence);
Byte realm [] = getTagandLengthbytes (asn1dattypes.context_specific,
2, getGeneralstringBytes (realmname));
Byte sgeneralstringsequence [] =
ConcatenateBytes (Getgeneral4tringBytes (KdcServiceName),
getGeneralstringBytes (realmname));
Byte sname_string [] = gettagandLengthbytes (asn1DataTypes.context_specific,
1, getsequenceBytes (sgeneralstringsequence));
BYTE SNAME_TYPE [] = gettagandLengthbytes (asn1dattypes.context_specific,
0, getintegerbytes (asn1dattypes.nt_unknown);
Byte sprincipalnamesequence [] = getsequencebytes
(ConcatenateBytes (SNAME_TYPE, SNAME_STRING);
Byte sname [] = gettagandLengthbytes (asn1dattypes.context_specific,
3, sprincipalnamesequence);
Byte till [] = getTagandLengthbytes
Asn1DataTypes.Context_specific,
5,
getGeneralizedTimeBytes
NEW STRING ("19700101000000z). getBytes ()));
BYTE NONCE [] = GetTagandLengthbytes
Asn1DataTypes.Context_specific,
7.
Getintegerbytes (getrandomnumber ()));
Byte eType [] = getTagandLengthbytes (asn1dattypes.context_specific,
8,
GetSequenceBytes (GetIntegerBytes (3)));
BYTE REQ_BODY [] = GetTagandLengthbytes
Asn1DataTypes.Context_specific,
4,
GetSequenceBytes
ConcatenateBytes
KDC_OPTIONS,
ConcatenateBytes
CNAME,
ConcatenateBytes
Realm,
ConcatenateBytes
SNAME,
ConcatenateBytes
TILL,
ConcatenateBytes
(nonce, eType)
)
)
)
)
)
)
);
TicketRequest = GetTagandLengthbytes
Asn1DataTypes.Application_type,
10,
GetSequenceBytes
ConcatenateBytes
PVNO,
ConcatenateBytes
(MSG_TYPE, REQ_BODY)
)
)
);
/ ****** New code begins ****** /
Try {
DataGram DG = DC.NewDataGram (TicketRequest, TicketRequest.Length);
Dc.send (DG);
} catch (IllegalargumentException IL) {
Il.printStackTrace ();
}
Catch (Exception IO) {
IO.PrintStackTrace ();
}
BYTE TicketResponse [] = NULL;
Try
{
DataGram DG = DC.NewDataGram (700);
DC.Receive (DG);
IF (Dg.getLength ()> 0) {
TicketResponse = New byte [DG.GetLength ()];
System.ArrayCopy (Dg.getData (), 0, TicketResponse, 0, Dg.getLength ());
Else
Return NULL;
} catch (ioexception ie) {
IE.PrintStackTrace ();
}
/ ****** New code ends ****** /
Return TicketResponse;
} // getticketResponse
Processing TGT Response Since the TGT response from KDC has been received, the response will now be processed to extract the ticket and session key from the response. Naturally, response processing includes some low-level ASN.1 processing (just like the low-level ASN.1 generation method encountered when generating ticket requests in the second article of this series). Therefore, before showing how to extract bills and session keys in the ticket response, I will implement and explain some low-level ASN.1 processing methods and some low-level encryption support methods. As before, the low-level ASN1 processing method is placed in the Asn1DataTypes class. The following method is in the ASN1DATYPES.JAVA file in the source code download in this article:
ISSEQUENCE () getintegervalue () getnumberoflength () getnumberoflength () getLength () getasn1structure () getContents () Below is a description of each low-level ASN.1 processing method listed above. ISSEquence () method shown in Listing 2 takes a single byte as a parameter and checks if this byte is an ASN.1 Sequence byte. If the byte value represents a sequence, then it returns true, otherwise it returns false. Listing 2. Issequence () method public boolean issequence (byte tagbyte)
{
IF (tagbyte == (byte) 0x30)
Return True;
Else
Return False;
} // issequence
GetIntegerValue () method shown in GetIntegerValue () is taken only one input parameter, which is an array of contents of an ASN.1 INTEGER data type. It converts the input byte array into a J2ME INT data type and returns J2ME INT. This method needs this method when you extract the content byte from the ASN.1 Integer, and you want to know what the Integer value it is indicated. Also use this method to convert the length byte to J2ME INT. Note that the GetIntegerValue () method is designed to process only the positive INTEGER value. ASN.1 stores a positive Integer in the sequence of Most-Significant-Byte-First). For example, decimal 511 represented by ASN.1 is 0x01 0xFF. The complete bit representation of the decimal value can be written (for 511, which is 1 11111111), then writes the hexadecimal value for each byte (for 511, it is 0x01, 0xFF), and finally at the highest significant bit priority The hexadecimal value is written in sequence. On the other hand, one INT in J2ME is always four-word, and the lowest effective byte occupies the rightmost position. Fill in zero at the space in the positive INTEGER value. For example, for 511, J2ME INT is 0x00 0x00 0x01 0xFF. This means that each byte of the input array must be placed in the respective position in the output J2ME INT when converting an ASN.1 INTEGER to a J2ME INT. For example, if the input byte array contains two bytes of data (0x01, 0xFF), these bytes must be placed in the output INT as described below:
0x00 must be written to the leftmost or highest valid location output INT. Similarly, 0x00 must be written at positions adjacent to the highest valid byte of the output int. The first byte (0x01) of the input array is placed in the output INT position adjacent to the lowest active position. The second byte (0xFF) of the output array is placed in the lowest effective or rightmost position of the output INT. The FOR cycle in the GetIntegerValue () method calculates the correct position of each byte, and then copies this byte to its corresponding position. It is also important to note that J2ME INT always has four bytes, and the getIntegerValue () method can only handle up to four byte integer values. With limited capacity, J2ME-based Kerberos customers do not need to handle larger values. Listing 3. GetIntegerValue () method
Public int getintegervalue (byte [】 INTVALUEASBYTES)
{
INT INTVALUE = 0;
INT i = intValueasbytes.length;
For (int y = 0; y
INTVALUE | = ((int) INTVALUEASBYTES [Y] & 0xFF) << ((i- (y 1)) * 8);
Return INTVALUE;
} // getintegervalue ()
Isasn1Structure () method shown in ISASN1Structure () Listing 4 analyzes whether an input byte indicates a specific type of ASN.1 structure with a specific tag number (ie, a context specificium, application level (Application Level) Or the label byte (first byte) of universal type). This method takes three parameters. The first parameter (tagbyte) is the input byte to be analyzed. The second and third parameters (tagtype and tagnumber) represent the tag type and tag number to look for. In order to check if tagbyte has the tag type of the tag number required, isasn1Structure () method first builds a new temporary tag byte (TEMPTAGBYTE) with TagType and Tagnumber parameters. Then compare Temptagbyte with tagbyte. If they are the same, then the method returns True, if it is not the same, it returns false. Listing 4. Isasn1Structure () method
Public Boolean Isasn1Structure (Byte tagbyte, int tagtype, int tagnumber)
{
BYTE TEMPTAGBYTE = (BYTE) (TagType Tagnumber);
IF (tagbyte == temptagbyte)
Return True;
Else
Return False;
} // isasn1structure
GetNumberoflengthThbytes () Listing 5 The getNumberOflength, which is displayed, takes a parameter (firstLengthbyte). The firstLengthbyte parameter is the first length byte of the ASN.1 structure. The getNumberoflengthThbytes () method processes the first length byte to calculate the number of bytes of the ASN.1 structure. This is a tool method, and other methods in the Asn1DataTypes class use it when you need to know the length byte of an ASN.1 structure. The implementation strategy of the getNumberoflengthbytes () method in Listing 5 is as follows:
Check if the maximum effective bit of FirstLENGTHBYTE (8th) is zero. The IF (FIRSTLENGTHBYTE) & (1 << 8) == 0 in Listing 5 completes this task. If the maximum valid bit is zero, then the length byte follows the single-byte length representation. In Part 1 of this series we said there are two length indication-single bytes and multiple bytes. There is always a length byte in the single-byte length indication method. Therefore, if the highest valid bit is zero, simply return 1 byte as the length byte. If the highest effective bit of FirstLengthbyte is 1, this means that the length byte follows the multi-byte length representation. At this time, the ELSE block in Listing 5 acquires control. In the multi-byte length format, how many length bytes are specified after the highest valid bit of FirstLengthbyte. For example, if the value of FirstLengthbyte is 1000 0010, then the leftmost 1 (maximum active bit) explains the rear length byte using a multi-byte length representation. Other 7 (000 0010) Description There are two length bytes. Therefore, here the getNumberoflengthbytes () method should return 3 (FirstLengthbytes plus two length bytes). The first line of the ELSE block in Listing 5 (FirstLengthByte & = (Byte) 0x7f;) Deletes the highest significant bit of FirstLengthbyte. The second line in the ELSE block (int) (int) (int) firstLengthbyte 1) enforces FirstLengThbyte to Integer, add 1 in the obtained INTEGER value, and return this Integer. Listing 5. GetNumberoflengthBytes () Method PUBLIC INT GETNUMBEROFLENGTHBYTES (BYTE firstLENGTHBYTE) {
IF ((FirstLENGTHBYTE & 1 << 8) == 0)
Return 1;
Else {
FirstLengthbyte & = (byte) 0x7f;
Return (int) firstLENGTHBYTE 1;
}
} // getNumberoflengthThbytes
The purpose of getLength () This method is to check how many bytes of a specific AS1 structure. Processing applications typically have a byte array composed of an embedded hierarchy composed of a plurality of ASN.1 structures. The getLength () method calculates the number of bytes in a particular structure. This method takes two parameters. The first parameter (Asn1Structure) is a byte array, which should include at least one complete ASN.1 structure, which contains tag bytes, length bytes, and content bytes. The second parameter (Offset) is an offset value in an Asn1Structure byte array. This parameter specifies the start position of the ASN.1 structure contained in the Asn1Structure byte array. The getLength () method returns a total of the number of bytes equivalent to the ASN.1 structure starting at the OFFSET byte. Look at the list 6, it shows an implementation of the getLength () method:
The first step is to pass the second byte of the ASN.1 structure to the GetNumberoflengthbytes () method. This ASN.1 structure begins with the OFFSET byte, so it is expected that the OFFSET byte is actually label bytes. Because all Kerberos structures contain only one label byte, the second byte (the byte behind the OFFSET byte) is the first length byte. The first length byte means the total byte number of length bytes, the getNumberoflengthThbytes () method returns the number of length bytes. Int numberoflengthbytes = getNumberoflengthThbytes (asn1structure [offset 1]); this line performs this task. If the getNumberOflengthbytes () method returns a value greater than 1, the multi-byte length representation must be processed. In this case, the length byte starting from OFFSET 2 (let the overlay bytes and the first length byte) is read into a variable called LengthValueasbytes. Then convert the length value from the ASN.1 byte to J2ME INT with the GetIntegerValue () method. Finally, add 1 (to compensate for the label byte) in the length value, then return the length value to the calling application. If the getNumberoflengthThbytes () method returns 1, the single-byte length representation is handled. In this case, simply convert the first (and only one) length byte to J2ME INT, it adds 1 (to compensate for the label byte) in the length value, and return the obtained value Give the call application. Listing 6 getLength () method public int getLength (Byte [] asn1structure, int offset) {
Int structureLength;
INT Numberoflengthbytes = GetNumberoflengthThbytes (As1Structure [offset 1]);
BYTE [] LengthValueasbytes = New Byte [Numberoflengthbytes - 1];
IF (Numberoflengthbytes> 1)
{
For (int i = 0; I Lengthvalueasbytes [i] = asn1structure [offset i 2]; StructureLength = GetIntegerValue (LengthValueasbytes); } Else StructureLength = (int) (asn1structure [offset 1]); StructureLength = Numberoflengthbytes 1; Return StructureLength; } // getLength () The getasn1structure () method in the GetAsn1Structure Listing 7 is identified from a byte array containing a series of ASN.1 structures and extracts specific ASN.1 structures. This method has three parameters. The first parameter (INPUTBYTEARRAY) is an input byte array that requires the required ASN.1 structure from this byte array. The second parameter is an int, which specifies the type of tag to find. The third parameter specifies the tag number. Take a look at the getasn1strucute () method in Listing 7. It puts the OFFSET value to zero and go to the Do-While loop. In the Do-While cycle, read the first byte in the byte array into the byte named tagbyte. Then use the Isasn1Structure () method to check if the first byte of the input array is the required ASN.1 structure. If the first byte represents the required structure, then use the getLength () method to find the desired number of bytes to return. The desired byte is then copied to the byte array called OutputBytes and returns these bytes to the calling application. If the first byte does not mean the required structure, then jump to the next structure. To do this, set the OFFSET value to the start position of the next structure. The Do-While loop checks the next structure in the next loop and checks the entire input array in this way. If you don't find the required structure, the Do-While loop exits and returns NULL. Listing 7. Getasn1Structure () method public byte [] getasn1structure (byte [] InputByteaRray, int tagtype, int tagnumber) { BYTE TAGBYTE; INT OFFSET = 0; Do { Tagbyte = INPUTBYTEARRAY [OFFSET]; IF (Isasn1Structure (Tagbyte, tagtype, tagnumber) { Int longthofstructure = getLength (InputByTearray, Offset); Byte [] OutputBytes = new byte [lonngustructure]; For (int x = 0; x OutputBytes [x] = inputByteArray [x offset]; Return OutputBytes; } Else Offset = getLength (InputByteArray, Offset); WHILE (OFFSET Return NULL; } // getasn1structure GetContents () method shown in GetContents () Listing 8 takes an ASN1Structure byte array and returns an array of byte arrays that contain Asn1Structure content. The getContents () method assumes that the byte array provided is a valid ASN1 structure, so it ignores the first byte of the structure indicating the label byte. It passes the second byte (ie, the first length byte) to the getNumberoflengthbytes () method, which returns the number of length bytes in the byte array in ASN1Structure. Then it builds a new byte array called ContentBytes and copys the contents of Asn1Structure to the ContentBytes array (remove labels and length bytes). Listing 8. GetContents () method Public Byte [] getContents (byte [] asn1structure) { INT numberoflengthbytes = getNumberoflengthThbytes (asn1structure [1]); Byte [] contentBytes = new byte [asn1structure.length - (Numberoflengthbytes 1)]; For (int x = 0; x ContentBytes [x] = asn1structure [x numberoflengthbytes 1]; Return ContentBytes; } // getContents Some low-level encryption support methods In addition to the low-level processing methods previously described, some low-level encryption support methods are required to process one ticket response. That's why before explaining the processing of the ticket response, I will discuss the following methods for providing encryption support for the Kerberos client: Encrypt () Decrypt () getMD5DigestValue () DecryptandverifyDigest () These methods are components of the KerberoscLient class that can be found in the Kerberosclient.java file, which can be found in the source code download in this article. Here is an explanation of these methods: Encrypt () method displayed in Encrypt () Listing processing low-layer encryption and encrypts an input byte array. This method takes three bytes array parameters, namely a keybytes, pure text data to be encrypted, and an initial vector or IVBYTES. It uses keys and IV encrypted plain text data and returns encrypted plain text data. Note In the Encrypt () method in Listing 9, I use Desengine, CBCBLockCIpher, KeyParameter, and Parameterswithiv classes to encrypt this plain text data. These classes belong to the Bounce Castle encryption library presentation when discussing the getFinalKey () method in the list 11 in the second article. Look back and compare the encrypt () method in the list 9 and the getFinalKey () method in the list 11 in the second article. Note the following: The getFinalKey () method uses a Parameterswithiv class that wraps the initial vector. The Kerberos specification requires that the encryption key is used as IV when generating an encrypted key. Therefore, the encryption algorithm in the method uses an encryption key as IV. Therefore, the algorithm in the getFinalKey () method uses this encryption key as an IV. On the other hand, the Encrypt () method is designed to use or does not use IV values. A higher level application logic can provide an IV value or ignore it when using an Encrypt () method. If the application requires data encryption without the IV value, it will pass NULL as the third parameter. If there is an IV, the encrypt () method initializes CBCBlockCIpher with a parameterswithiv instance. Note that in the IF (IvBytes! = Null) block of Listing 9, I passed a parameterswithiv instance as the second parameter called to the cbccipher.init () method. If the third parameter is null, the encrypt () method uses a KeyParameter object to initiate the CBCBLockCIpher object. Note In the ELSE block in Listing 9, I passed a keyParameter instance as the second parameter called by the cbccipher.init () method. The getFinalKey () method in the list 11 of the second article returns the processing result of the last piece of the input data. On the other hand, the encrypt () method connects the result of each step of plain text to string together and returns all the processed (encrypted) bytes that are connected together. Listing 9. Encrypt () method public byte [] encrypt (byte [] keybytes, byte [] plaindata, byte [] ivbytes) { Byte [] encrypteddata = new byte [plaindata.length]; Cbcblockcipher cbcipher = new cbcblockcipher (new desengine ()); KeyParameter KeyParameter = New KeyParameter (Keybytes); IF (ivbytes! = null) { Parameterswithiv Kpwithiv = New Parameterswithiv (KeyParameter, Ivbytes); Cbccipher.init (True, Kpwithiv); Else Cbccipher.init (True, KeyParameter); INT OFFSET = 0; INT processedbyteslength = 0; While (Offset Try { ProcessedBytesLength = Cbccipher.ProcessBlock (Plaindata, OFFSET, EncryptedData, Offset ); OFFSET = ProcessedBytesLength; } catch (exception e) { E.PrintStackTrace (); } // catch } Return EncryptedData; } Decrypt () (inventory 10) Decrypt () method is identical to the encrypt () method, but only when decryption, the first parameter of the cbccipher.init () method is false (encrypted it is true). Listing 10. Decrypt () method Public Byte [] Decrypt (Byte [] Keybytes, Byte [] encrypteddata, byte [] ivbytes) { Byte [] plaindata = new byte [encryptedData.length]; Cbcblockcipher cbcipher = new cbcblockcipher (new desengine ()); KeyParameter KeyParameter = New KeyParameter (Keybytes); IF (ivbytes! = null) { Parameterswithiv Kpwithiv = New Parameterswithiv (KeyParameter, Ivbytes); Cbccipher.init (False, Kpwithiv); Else Cbccipher.init (false, keyparameter); INT OFFSET = 0; INT processedbyteslength = 0; While (Offset Try { ProcessedByteslength = Cbccipher.ProcessBlock (EncryptedData, OFFSET, Plaindata, Offset ); OFFSET = ProcessedBytesLength; } catch (exception e) { E.PrintStackTrace (); } // catch } Return Plaindata; } // Decrypt () GetMD5DigestValue () method shown in GETMD5DIGESTVALUE () Take an input data byte array and return a MD5 summary value calculated by input data. The Bounce Castle encryption library contains MD5 summary support in a class named MD5Digest. Summary uses the MD5Digest class to calculate the four steps: First, instantiate a MD5Digest object. Then, call the UPDATE () method of the MD5Digest object, and transfer the data to be summarized at the same time. Then, instantiate an array of output bytes used to include the MD5 summary value. Finally, call the DOFINAL () method of the MD5Digest object, and transfer the output byte array. The DOFINAL () method calculates the summary value and put it in an output byte array. Listing 11. GetMd5DigestValue () method Public Byte [] getMd5DigestValue (byte [] data) { Md5Digest Digest = New MD5Digest (); Digest.Update (Data, 0, Data.Length); Byte DigestValue [] = new byte [digest.getdigestsize ()]; Digest.dofinal (DigestValue, 0); Return DigestValue; } DecryptandverifyDigest () recalled that in the first article Fig. 3 and inventory 2, the ticket response of the KDC server contains a field named Enc-Part, which is packaged a encrypted data structure called EncryptedData. Just as described in the description of Figure 3 of the first article, the EncryptedData structure consists of three fields. The decryptandverifydigest () method shown in Listing 12 takes an EncryptedData structure (essentially the content of the ENC-Part field) and a decryption key as a parameter, and returns a plain text representation of the EncryptedData structure. The encryption process steps are as follows: Step 1: Note that in Listing 2 of the first article, the EncryptedData structure is actually a Sequence in the ETYPE, KVNO, and Cipher fields. Therefore, the first step is to check if the input byte array is a sequence. To this end, ISSEQUENCE () method is called. Step 2: If the input byte array is a sequence, then you need to resolve this sequence and extract its content. Call the getContents () method to extract the sequence content. In sequence content, interested is the first field (ETYPE, a context-specific tag number 0), indicating the encryption type. Use the getasn1structure () method call to extract the ETYPE field from Sequence content. Step 3: Call the getContents () method to extract the ETYPE field, which is an ASN.1 INTEGER. Call the getContents () method again to extract the contents of Integer. Then passes the Integer content to the GetIntegerValue () method, this method returns the Inetger content of the J2ME INT format. Store the J2ME INT value as a variable called ETYPEVALUE. ETYPEVALUE INT specifies the encryption type used when generating an EncryptedData structure. Step 4: Recalling that the Kerberos client only supports a type of encryption - DES-CBC - its identification number 3. Therefore, I check if ETYPEVALUE is 3. If it is not 3 (ie, the server uses a non-DES-CBC encryption algorithm), then the Kerberos client cannot handle this process. Step 5: The next step is to extract the third field from EncryptedData Sequence content (Cipher, the context-specific tag number 2). Call the GetAsn1Structure () method to complete this task. Step 6: Next, call the getContents () method to extract the contents of the Cipher field. The content of the Cipher field is an ASN.1 OCTET STRING. You also need to call the getContents () method to extract the contents of the OCTET STRING. Step 7: OCTET STRING content is encrypted, so you need to decrypt it with the decrypt () method discussed earlier. Step 8: The decryption data byte array is composed of three parts. The first part consists of the first eight, which contains a random number called Confounder. Confounder bytes are meaningless, they just help to increase the difficulty of hackers' attacks. The 9th to 24th bytes of the decrypted data constitute the second portion, which contains a 16-byte MD5 summary value. This summary value is data to the entire decryption - 16 summary bytes (second part) is calculated using zero-filled-calculated. The third part is to get the actual plain text data. Since the eighth step is performed, the 9th to 24th bytes of the decrypted data must be filled with zero, calculate a MD5 summary value for the entire data, and the summary value and the second part (nine to the first) 24 bytes) matches. If the two summary values match, the integrity of the message is verified. Step 9: If the integrity check is passed, the third portion of the decrypted data (the 25th byte to the end) is returned. Listing 12. DecryptandverifyDigest () method public byte [] DecryptandverifyDigest (byte [] encryptedData, byte [] DecryptionKey { / ****** step 1: ****** / IF (ISSEquern (EncryptedData [0])) { / ****** step 2: ****** / Byte [] etype = getasn1structure (getContents (encrypteddata), CONTEXT_SPECIFIC, 0); IF (eType! = null) { / ****** STEP 3: ****** / / INT ETYPEVALUE = GetIntegerValue (getContents (ETYPE))); / ****** step 4: ****** / IF (ETYPEVALUE == 3) { / ****** step 5: ****** / Byte [] cipher = getasn1structure (getContents (EncryptedData), Context_specific, 2); / ****** STEP 6: ****** / Byte [] ciphertext = getContents (getContents (cipher); IF (ciphertext! = null) { / ****** step 7: ****** / Byte [] Plaindata = Decrypt (DecryptionKey, Ciphertext, NULL; / ****** STEP 8: ****** / INT data_offset = 24; Byte [] ciphercksum = new byte [16]; For (int i = 8; i Ciphercksum [i-8] = plaindata [i]; For (int J = 8; j PLAINDATA [J] = (Byte) 0x00; Byte [] digestbytes = getmd5digestValue (Plaindata); For (int x = 0; x IF (! (ciphercksum [x] == DigestBytes [x]))) Return NULL; } Byte [] DecryptedandverifiedData = New byte [plaindata.length - data_offset]; / ****** step 9: ****** / For (int i = 0; i DecryptedandverifiedData [i] = plaindata [i data_offset]; Return DecryptedandVerifiedData; Else Return NULL; Else Return NULL; Else Return NULL; Else Return NULL; } // DecryptandverifyDigest We have discussed low-level ASN.1 processing and low-level encryption support methods, which can now be discussed how to use these methods to process the ticket response to the GetticKetResponse () method in the previous list 1. Take a look at the getticketAndKey () method shown in Listing 13 (it belongs to the Kerberosclient class). This method takes a ticket response byte array and a decryption key byte array as a parameter. This method extracts the bill and key from the ticket response. The getTicketandKey () method returns an instance of a class called TicketandKey (this is a wrapper to be extracted from the ticket response). I have already shown TicketandKey classes in Listing 14. This class has only four ways: two subsetTer methods and two getter methods. SetKey () and getKey () methods set and get the key byte, respectively. Setticket () and getticket () methods set and get the ticket byte separately. Take a look at the process taken in the getTicket Andkey () method of Listing 13. Recalling how the Kerberos key and tickets are stored in the ticket response in the discussion of the first article. Extracting the key from the ticket response is a long process, including the following steps: 1. First, check if the TicketResponse byte array really contains the ticket response. To this end, I use Isasn1Structure () method. If the isasn1structure () method returns false, then it indicates that the input TicketResponse byte array is not a valid ticket response. In this case, no one-step processing is not performed and NULL is returned. Note In Listing 13, I called two ISASN1Structure () methods. The first call isaSn1Structure () method uses "11" as the value of the third parameter, while the ISASN1Structure () method is called for the second time, "13" is used as the value of the third parameter. This is because "11" is the application-specific label number (Listing 2 of the first article of this series), and "13" is the application-specific label number specific to the service ticket (this series) Listing 4 of the first article). If the TicketResponse byte array is a TGT response or service ticket response, then one of these two ways will return TRUE, and further processing can be performed. If these two ways calls do not return True, then the TicketResponse byte array is not a ticket response, it is necessary to return NULL and do not do any further processing. 2. The second step is to extract the contents of the ticket response structure. To this end, I used the getContents () method call. 3. The content of the ticket response should be an Asn.1 Sequence, which can be checked for the issequence () method. 4. Next, I call the getContents () method to extract the contents of Sequence. 5. The content of Sequence is the seven structures of the ticket response (shown in Listing 2 of Figure 3 and the first article). In addition to these seven structures, only two: ticket and enc-part. Therefore, the fifth step is to extract the Ticket field from Sequence content (call the getasn1structure () method), extract the Ticket field (call the getContents () method), and store the content into the TicketandKey object created in front. Note that the Ticket field is a context-specific tag number 5, and the content of this field is actual bill, which starts with an application level, as shown in Listing 3 and Figure 9 of the first article. 6. Here, the key must be extracted from the sequence content obtained in step 4. This button is in the ENC-Part field of the sequence content. Therefore, in step 6, I call the getasn1structure () method to capture the ENC-Part field from Sequence content. 7. After obtaining the Enc-Part field, you want to call the getContents () method to get its content. The content of the ENC-Part field constitutes an EncryptedData structure. 8. You can pass the EncryptedData structure to the DecryptandVerifyDigest () method. This method decrypts the EncryptedData structure and performs a summary verification check for EncryptedData. 9. If the decryption and summary verification process is successfully performed, the decryptandverifyDigest () method extracts ASN.1 data from the decrypted ciphertext data. ASN.1 data should comply with the structure shown in Figure 4 in the first article. Note that the required key is the first field in the structure shown in Figure 4 of the first article. One application level tag number "25" or "26" packages plain text data. This structure is called EnckdCreppart (encrypted KDC reply). Thus, the next step is to check if the data returned by the decryptandverifyDigest () method is a label number 25 or 26 of the application level. 10. The next step is to extract the contents of the enckdcreppart structure. Call the getContents () method to extract the content you need. Enckdcreppart content is a Sequence, so you must also extract the sequence content. Call the getContents () method again to extract the sequence content. 11. The first field of the sequence content (called Key, a context-specific tag number 0) contains the KEY field. The getasn1structure () method can be called to extract the first field from Sequence content. 12. Below, extract the contents of the key field. Call the getConents () method can return these content. The content of the key field makes another ASN.1 structure called EncryptionKey, which is a two field - the Sequence, KEYTYPE and KeyValue - SEQUENCE. Call the getContents () method again to extract the contents of Sequence. 13. The desired session key is in the second field of the sequence content (KeyValue). Therefore, the GetASn1Structure () method must be called to extract the keyValue field from the sequence content (the tag number 1 specific to the context). 14. There is already a keyValue field. The getContents () method must be called to extract its content. KeyValue content is an OCTET STRING, so you must call the getContents () method again to extract the contents of the OCTET STRING. It is the key you want to find. So just pack this key byte in the KeyandTicket object (by calling its setKey () method) and returns the KeyandTicket object. Listing 13. GetticketandKey () Method Public TicketAndkey GetticKetAndKey (byte [] TicketResponse, Byte [] DecryptionKey { TicketandKey TicketAndKey = New TicketAndKey (); int offset = 0; / ***** step 1: ***** / IF ((ISASN1Structure (TicketResponse [0], Application_Type, 11)) || (Isasn1Structure (TicketResponse [0], Application_Type, 13)))) { Try { / ***** step 2: ***** / Byte [] KDC_REP_SEQUENCE = getContents (TicketResponse); / ***** STEP 3: ***** / IF (iSsequence (kdc_rep_sequence [0])) { / ***** step 4: ***** / Byte [] KDC_REP_SEQUENCECECONTENT = GetContents (KDC_REP_SEQUENCE); / ***** step 5: ***** / Byte [] Ticket = getContents (Getasn1Structure (kdc_rep_sequencecontent, Context_specific, 5)); TicketandKey.Setticket (Ticket); / ***** STEP 6: ***** / Byte [] enc_part = getasn1structure (kdc_rep_sequencecontent, Context_specific, 6); IF (enc_part! = null) { / ***** step 7: ***** / BYTE [] ENC_DATA_SEQUENCE = getContents (Enc_PART); / ***** step 8: ***** / Byte [] plaintext = decryptandverifydigest (enc_data_sequence, DecryptionKey; IF (plaintext! = null) { / ***** step 9: ***** / IF ((ISASN1Structure (Plaintext [0], Application_Type, 25)) || (Isasn1Structure (Plaintext [0], Application_Type, 26))) { / ***** step 10: ***** / BYTE [] ENC_REP_PART_CONTENT = getContents (getContext); / ***** step 11: ***** / Byte [] enc_key_structure = getasn1structure (ENC_REP_PART_CONTENT, CONTEXT_SPECIFIC, 0); / ***** step 12: ***** / Byte [] enc_key_sequence = getContents (getContents (enc_key_structure); / ***** step 13: ***** / Byte [] enc_key_val = getasn1structure (enc_key_sequence, Context_specific, 1); / ***** step 14: ***** / Byte [] enc_key = getContents (getContents (Enc_Key_VAL)); TicketandKey.setKey (Enc_Key); Return TicketAndKey; Else Return NULL; Else Return NULL; Else Return NULL; Else Return null;} catch (exception e) { E.PrintStackTrace (); } Return NULL; Else Return NULL; } // getticketAndKey () Listing 14. TicketandKey class Public Class TicketAndkey { Private byte [] key; Private byte [] ticket; Public void setkey (byte [] key) { THIS.KEY = KEY; } // setKey () Public Byte [] getKey () { Return Key; } // getKey Public void setticket (Byte [] Ticket) { this.ticket = ticket; } // setticket Public Byte [] getticket () { Return Ticket; } // getticket } Getting a service ticket has been processed the TGT response and the TGT and session key are extracted. You can now use this TGT and session key to the KDC server to request a service ticket. Request for service tickets is similar to the request for TGT to me in Listing 1. The optional PADATA field I omitted in the TGT request is no longer optional in the service ticket request. Therefore, you need to add a PADATA field in the service ticket request. The PADATA field is Sequence containing two fields - padata-type and padata-value -. The Padata-Value field has several types of data, so the corresponding PADATA-TYPE field specifies the type of the data belled in the PADATA-VALUE field. In Figure 5 of the first article of this series, I introduced the structure of the PADATA field in the service ticket. There was a copy of the Padata field in the service ticket request (a KRB_AP_REQ structure), which also wrapped TGT and other data. Therefore, a certification head must be generated before you can start generating a ticket request. Here is the process of analyzing the generated certification header. Generate a certification head I added the following method to generate a certified head in the KerberoscLient class: GetMd5DigestValue () getchceksumbytes () AuthordigestandenCrypt () getAuthenticationHeader () These four methods are Helper methods. The fifth method (GetAuthenticationHeader ()) uses the Helper method and generates a certification head. AuthordigestandenCrypt () Listing 15 The authordigestandencrypt () method is taken with a plain text data byte array and an encryption key. This method calculates a summary value to plain text data, encrypts plain text data, and returns an EncryptedData structure, which is fully matched with the structure of the DecryptandverifyDigest () method that is passed to the list 12 as input. It can be said that the Authordigestandencrypt () method of the list 15 is the opposite of the decryptandverifyDigest () method discussed earlier. The authordigestandencrypt () method takes the plain text data returned by the DecryptandverifyDigest () method as the input. Similar to this, the EncryptedData structure returned by the AuthordEncrypt () method is the structure I passed to the DecryptandverifyDigest () method as an input. The Authordigestandencrypt () method implements the following policies: First, eight random bytes are generated, which constitutes the Confounder. Then, declare a byte array called ZeroedChecksum, which has sixteen bytes and initializes zero. This has six six zero arrays as a summary value of all zero. Third, fill in the input data byte arrays in other bytes to make the number of bytes in the array become the number of times the number of eight. Write a method called getPaddedData () (as shown in Listing 16), which takes a byte array and returns this array after filling. Below, the link (step 1) Confounder, (step 2) is a summary of zero summary and the filled plain text byte array. The fourth step is to calculate the MD5 summary value for the byte array of the third step. The fifth step is to place the summary byte to their respective position. The results of the fifth are the third step, but the summary of the entire zero is now replaced with a real summary value. The Encrypt () method is now called to encrypt the byte array obtained by step 5. Then, generate the ETYPE field (the tag number 0 specific to the context) is generated. Then, call the GetOCTStringBytes () method to package the encrypted byte array of steps 6 to the OCTET STRING. The OCTET STRING is then packaged into the Cipher field (a context-specific tag number 2). Finally, link the ETYPE and CIPHER fields, package this string into a sequence and return this sequence. Listing 15. AuthordigestandenCrypt () method public byte [] authordigestandencrypt (byte [] key, byte [] data) { / ****** step 1: ****** / BYTE [] confounder = concatenatebytes (getrandomnumber (), getrandomnumber ()); / ****** step 2: ****** / Byte [] zeroedchecksum = new byte [16]; / ****** STEP 3: ****** / / Byte [] paddeddatabytes = controlnatebytes (confounder, ConcatenateBytes (ZeroEdchecksum, GetPaddedData (DATA) ) ); / ****** step 4: ****** / Byte [] checksumbytes = getmd5digestvalue (paddeddatabytes); / ****** step 5: ****** / For (int i = 8; i <24; i ) PaddedDataBytes [i] = ChecksumBytes [I-8]; / ****** STEP 6: ****** / Byte [] encryptedData = Encrypt (key, paddeddatabytes, null); / ****** step 7: ****** / Byte [] etype = gettagandLengthbytes Asn1DataTypes.Context_specific, 0, GetIntegerbytes (3) ); / ****** STEP 8: ****** / Byte [] cipher = gettagandLengthbytes Asn1DataTypes.Context_specific, 2, getOCTStringBytes (EncryptedData) ); / ****** step 9: ****** / byte [] asn1_encrypteddata = getsequencebytes ConcatenateBytes (ETYPE, CIPHER) ); Return asn1_encrypteddata; } // authordigestandencrypt Listing 16. getPaddedData () method Public Byte [] getPaddedData (byte [] data) { INT Numbertopad = 8 - (DATA.LENGTH% 8); IF (Numbertopad> 0 && numbertopad! = 8) { Byte [] bytespad = new byte [numbertopad]; For (int x = 0; x Bytespad [x] = (byte) Numbertopad; Return ConcatenateBytes (Data, Bytespad); } Else Return Data; } // getpaddeddata () The getChecksumBytes () getChecksumBytes () method generates a structure called Checksum, as shown in Listing 17. The Checksum structure contains two fields: CKSUMTYPE and CHECKSUM. Listing 17. CHECKSUM structure Checksum :: = sequence { CKSUMTYPE [0] integer, Checksum [1] OCTT STRING } There are two places that need the CHECKSUM structure - the first is when generating a service ticket response, and then when generating a security context establishment request. The role of the Checksum structure is different in both cases, and it is necessary to explain when generating a service ticket and context establishment request (ELABORATE). The getChecksumBytes () method shown in Listing 18 takes two bytes array parameters. The first parameter comes with a checksum field, while the second parameter comes with a CKSUMTYPE field. getChecksumBytes () Method Packages the CKSUMTYPE field to a context-specific tag number 0 (which represents the CKSUMTYPE field, as shown by Listing 17), and package the checksum field to a context-specific tag number 1 (it represents the checksum field, Similarly, as shown in Listing 17). Then it links these two fields to pack this array into a sequence and returns this Sequence. Listing 18. GetChecksumBytes () method Public Byte [] getChecksumBytes (byte [] cksumdata, byte [] cksumtype) { Byte [] cksumbytes = gettagandlengthbytes Asn1DataTypes.Context_specific, 3, GetSequenceBytes ConcatenateBytes GetTagandLengthbytes (asn1dattypes.context_specific, 0, CKSUMTYPE ), GetTagandLengthbytes Asn1DataTypes.Context_specific, 1, GetOCTStringBytes (CKSUMDATA) ) ) ) ); Return Cksumbytes; } // getChecksumBytes () GetAuthenticationHeader () In the first article of this series, GetAuthentication Header has introduced the KRB-AP-REQ structure (also known as a certification head) packaged Kerberos bill. In addition, the authentication head is also packaged with the Authenticator field, indicating whether the client has mastered the session or sub-session key. As shown in Figure 5 of the first article, the certification head consists of five fields, namely PVNO, MSG-Type, AP-Options, Ticket, and Authenticator. The GetAuthenticationHeader () method of the Listing 19 generates these five fields one by one, and then connects each field in the correct order to form a complete authentication head. Listing 19. GetAuthenticationHeader () method public byte [] getAuthenticationHeader (byte [] TicketContent, String ClientRealm, String ClientName, Byte [] ChecksumBytes, Byte [] EncryptionKey, INT SequenceNumber ) { Authenticator = NULL; Byte [] VNO = GetTagandLengthbytes Asn1DataTypes.Context_specific, 0, Getintegerbytes (5) ); BYTE [] AP_REQ_MSG_TYPE = GetTagandLengthbytes Asn1DataTypes.Context_specific, 1, GetIntegerbytes (14) ); Byte [] AP_OPTIONS = GetTagandLengthbytes Asn1DataTypes.Context_specific, 2, getBitstringBytes (New byte [5]) ); Byte [] Ticket = GetTagandLengthbytes Asn1DataTypes.Context_specific, 3, TicketContent ); Byte [] realmname = gettagandLengthbytes (asn1dattypes.context_specific, 1, getGeneralstringBytes (ClientRealm) ); BYTE [] generalstringsequence = getsequencebytes GetgeneralstringBytes (ClientName) ); Byte [] name_string = getTagandLengthbytes (asn1dattypes.context_specific, 1, GeneralStringSequence ); Byte [] name_type = gettagandLengthbytes (asn1dattypes.context_specific, 0, getintegerbytes (asn1dattypes.nt_principal) ); Byte [] ClientNamesequence = GetSequenceBytes ConcatenateBytes (name_type, name_string) ); Byte [] cname = gettagandLengthbytes (asn1dattypes.context_specific, 2, ClientNamesequence); Byte [] cusec = getTagandLengthbytes (Asn1DataTypes.Context_specific, 4, getintegerbytes (0) ); Byte [] ctime = getTagandLengthbytes (asn1dattypes.context_specific, 5, getGeneralizedTimeBytes GetutctimeString (System.currentTimeMillis ()). getBytes () ) ); IF (sequencenumber! = 0) { Byte [] etype = gettagandLengthbytes Asn1DataTypes.Context_specific, 0, GetIntegerbytes (3) ); Byte [] ekey = gettagandlengthbytes Asn1DataTypes.Context_specific, 1, getOCTStringBytes (EncryptionKey) ); Byte [] Subkey_sequence = getsequenceBytes (ConcatenateBytes (ETYPE, EKEY)); Byte [] Subkey = GetTagandLengthbytes Asn1DataTypes.Context_specific, 6, Subkey_Sequence ); Byte [] sequencenumberbytes = { (Byte) 0xFF, (Byte) 0xFF, (Byte) 0xFF, (Byte) 0xFF } Sequencenumberbytes [3] = (Byte) SEQUENCENUMBER; Byte [] seqnumber = gettagandLengthbytes Asn1DataTypes.Context_specific, 7, getIntegerbytes (sequencenumberbytes) ); Authenticator = getTagandLengthbytes (asn1dattypes.application_type, 2, GetSequenceBytes ConcatenateBytes (VNO, ConcatenateBytes (Realmname, ConcatenateBytes (CName, ConcatenateBytes (ChecksumBytes, ConcatenateBytes (CUSEC, ConcatenateBytes (CTIME, ConcatenateBytes (Subkey, SEQNumber) ) ) ) ) ) ) ) ); } else { Authenticator = getTagandLengthbytes (asn1dattypes.application_type, 2, GetSequenceBytes ConcatenateBytes (VNO, ConcatenateBytes (Realmname, ConcatenateBytes (CName, ConcatenateBytes (ChecksumBytes, ConcatenateBytes (CUSEC, CTIME) ) ) ) ) ) ); } // if (sequencenumber! = null) BYTE [] ENC_AUTHENTICATOR = GetTagandLengthbytes Asn1DataTypes.Context_specific, 4, Authordigestandencrypt (EncryptionKey, Authenticator) ); Byte [] AP_REQ = GetTagandLengthbytes (Asn1DataTypes.Application_type, 14, GetSequenceBytes ConcatenateBytes (VNO, ConcatenateBytes (ap_req_msg_type, ConcatenateBytes (ap_options, ConcatenateBytes (Ticket, Enc_AuthenTicator) ) ) ) ) ); Return AP_REQ; } // getAuthenticationHeader GetAuthenticationHeader () method has several input parameters: The byte array named TicketContent, which contains the Kerberos Bill (TGT) packaged by the getAuthenticationHeader () method to the certification header. A string type parameter named ClientRealm, which specifies (generating this request) The name of the domain registered by the Kerberos client. A string type parameter named ClientName Specifies the name of the Kerberos client that generates this request. The checksumbytes byte array carries a checksum structure and a getChecksumBytes () method. The EncryptionKey byte array carries an encryption key for generating an encrypted part of the authentication head. The parameter named sequencenumber is an Integer value that identifies the sender's request number. In the first article, Figure 5, the authentication header contains the following fields: PVNO MSG-TYPE AP-OPTION Ticket Authenticator Now let's take a look at how the getAuthenticationHeader () method in Listing 19 achieves how to generate various fields of the authentication header: first generate a PVNO field, it has a specific The tag number 0 of the context and packaged a value of 5 ASN1 INTEGER. Call the GetTagandLengthbytes () method to perform this task. I store the PVNO field in a byte array called VNO. Similarly, the GetTagandThbytes () method is called twice to generate the MSG-Type (specific to the context-specific tag number 1) and the AP-Options field (specific to the context-specific label number 2). Next line (Byte [] Ticket = getTagandens (Asn1DataTypes.context_SPec, 3, TicketContent)) Pack the ticket structure into the context-specific tag number 3, which is the fourth field of the certification head. Then, the fifth field of the authentication head must be generated (named Authenticator, which has a context-specific tag number 4). The Authenticator field is an encryptedData structure. The plain text formality of the Authenticator field is an Authenticator structure. Therefore, first generate a full AUTHENTICATOR structure in plain text format, pass this plain text Authenticator to the AuthiDigestAndenderpt () method, which returns the full encryptedData of Authenticator. Note that in Listing 3 and Figure 5 in the first article, the Authenticator structure of plain text format is composed of the following fields (omitting the last field, it is not required): Authenticator-Vno CREAL CNAME CKSUM CUSEC CTIME SUBEY SEQ-NUMBER When explaining the first article, I have already introduced the meaning of each field. The Authenticator-VNO field is exactly the same as the PVNO field (the VNO byte array is discussed in front of this section, which contains an Integer that is specific to the context-oriented tag number 0 and the band value is 5. So I reuse the same byte array used in the Authenticator_VNO field. This generates the CREALM field now, which is similar to the Realm field introduced in the second article "Generate Request Text" section. Similarly, in that section, the CNAME field of the PrincipalName type is also introduced. Here I don't introduce the generation details of the CREALM and CNAME fields. The next task is to generate a CKSUM field, which is the checksum type. The role of the CKSUM field in the service ticket request is to encrypt the AUTHENTICATOR and some application data. Pay attention to the following three points: The Authenticator structure contains a CKSUM field. The CKSUM field contains an encrypted hash value of some application data. The entire Authenticator structure (including the CKSUM field) is encrypted with a key. As long as the CKSUM field in Authenticator matches the encryption checksum of the application data, the client that generates Authenticator and the application data has a key. Call the application of the getAuthenticationHeader () method (by calling the getChecksumBytes () method) Generate the Checksum structure and passes the checksum byte array as the value of the checksumbytes parameter to the getAuthenticationHeader () method. As a result, there is a Checksum structure in the checksumbytes parameter. Just package CheckSumbytes into the context-specific tag number 3 (this is the tag number of the CKSUM field in the Authenticator structure). Now generate a CUSEC field, it represents a microsecond portion of the client time. This field ranges from 0 to 999999. This means that the maximum value available in this field is 999999 microseconds. However, MIDP does not include any way to provide more accurate time values than one millisecond. Therefore, the microsecond portion of the client cannot be specified. Just transmit a zero value for this field. In the Authenticator structure, you have to generate two fields - Subkey and SEQ-Number. This two fields do not have to include these two fields in the Authenticator generated for the ticket request, but it is necessary to generate context establishment requests with the same getAuthenticationHeader () method. Now, just know if you check if the sequencenumber parameter is zero. For service ticket requests it is zero, establishing it for context to establish it is non-zero. If the SequenceNumber parameter is non-zero, then generate the Subkey and the SEQ-Number field, then link Authenticator-VNO, Realm, CName, CKSUM, CUSEC, CTIME, Subkey, and SEQ-Number fields to form an array of bytes, will this byte The array is packaged into a sequence and then packages Sequence into Authenticator (Application Level Label Number 2). If the sequencenumber parameter is zero, then the Subkey and the SEQ-Number field can be ignored, linking Authenticator-VNO, CREALM, CNAME, CKSUM, CUSEC, and CTIME fields to constitute a series of byte arrays, and package this byte array into a sequence. , Then package this Sequence into Authenticator (application level tag number 2). Below, you need to take a complete Authenticator structure and pass it AuthorDigeStandenCrypt () method. This method returns the full encryptedData of the plain text Authenticator. The next task is the five fields (PVNO, MSG-TYPE, APTION, TICKET, AUTHENTICATOR) of the KRB-AP-REQ structure (PVNO, MSG-TYPE, APTION, TICKET, AUTHENTICATOR), and package this byte array into a Sequecne. Finally, package this Sequence to the application level tag number 14. The authentication head has now been completed, and it can be returned to the call to the application. Generating a service ticket request I discussed all the low-level methods required to generate a service ticket request. The same getticketResponse () method used to request TGT in Listing 1 will generate a service ticket request, and only need to be slightly modified to the Listing 1 so that it can be used for TGT and service ticket requests. Let's take a look at this process. Look at the list 20, which can see the getTicketRespone () method in the modified list 1. Compared with the list 1, the modified version adds some code: Listing 20. getticketResponse () method public byte [] getticketResponse (String Username) String ServerName, String realmname, Byte [] Kerberosticket, Byte [] Key ) { Byte TicketRequest []; BYTE MSG_TYPE []; Byte pvno [] = getTagandLengthbytes (asn1dattypes.context_specific, 1, GetIntegerbytes (5)); MSG_Type = gettagandLengthbytes (asn1datatypes.context_specific, 2, Getintegerbytes (10)); BYTE KDC_OPTIONS [] = gettagandLengthbytes (asn1dattypes.context_specific, 0, getBitStringBytes (new byte [5])); Byte generalstringsequence [] = getsequenceBytes getGeneralstringBytes (username); Byte name_string [] = gettagandLengthbytes (asn1dattypes.context_specific, 1, generalstringsequence); Byte name_type [] = gettagandLengthbytes (asn1dattypes.context_specific, 0, getIntegerbytes (asn1dattypes.nt_principal); Byte PrincipalNamesequence [] = GetSequenceBytes ConcatenateBytes (Name_TYPE, NAME_STRING); Byte cname [] = gettagandLengthbytes (asn1dattypes.context_specific, 1, PrincipalNamesequence); Byte realm [] = getTagandLengthbytes (asn1dattypes.context_specific, 2, getGeneralstringBytes (realmname)); Byte sgneralstringsequence [] = concatenatebytes (GetGeneralstringBytes (ServerName), getGeneralstringBytes (realmname)); Byte sname_string [] = gettagandLengthbytes (asn1DataTypes.context_specific, 1, getsequenceBytes (sgeneralstringsequence)); BYTE SNAME_TYPE [] = gettagandLengthbytes (asn1dattypes.context_specific, 0, getintegerbytes (asn1dattypes.nt_unknown); byte sprincipalnamesequence [] = getsequencebytes ConcatenateBytes (SNAME_TYPE, SNAME_STRING) ); Byte sname [] = gettagandLengthbytes (asn1dattypes.context_specific, 3, sprincipalnamesequence); Byte till [] = getTagandLengthbytes Asn1DataTypes.Context_specific, 5, getGeneralizedTimeBytes NEW STRING ("19700101000000z"). getBytes ()) ); BYTE NONCE [] = GetTagandLengthbytes Asn1DataTypes.Context_specific, 7. Getintegerbytes (getrandomnumber ()) ); Byte eType [] = getTagandLengthbytes Asn1DataTypes.Context_specific, 8, GetSequenceBytes (GetIntegerbytes (3)) ); BYTE REQ_BODY [] = GetTagandLengthbytes Asn1DataTypes.Context_specific, 4, GetSequenceBytes ConcatenateBytes (KDC_Options, ConcatenateBytes (CName, ConcatenateBytes (Realm, ConcatenateBytes (Sname, ConcatenateBytes (Till, ConcatenateBytes (Nonce, ETYPE) ) ) ) ) ) ) ); IF (kerberosticket! = null) { MSG_Type = gettagandLengthbytes (asn1datatypes.context_specific, 2, getIntegerbytes (12)); Sname_string = gettagandLengthbytes (asn1dattypes.context_specific, 1, getsequenceBytes (GetgeneralstringBytes (ServerName))); SNAME_TYPE = GetTagandLengthbytes (asn1dattypes.context_specific, 0, getintegerbytes (asn1dattypes.nt_unknown); SprincipalNamesequence = GetSequenceBytes ConcatenateBytes (SNAME_TYPE, SNAME_STRING) ); Sname = getTagandLengthbytes Asn1DataTypes.Context_specific, SprincipalNamesequence ); Byte [] req_body_sequence = getsequencebytes ConcatenateBytes (KDC_Options, ConcatenateBytes (Realm, ConcatenateBytes (Sname, ConcatenateBytes (Till, ConcatenateBytes (Nonce, ETYPE) ) ) ) ) ); REQ_BODY = GetTagandLengthbytes (asn1dattypes.context_specific, 4. Req_body_sequence ); Byte [] CKSUM = GetChecksumBytes GETMD5DIGESTVALUE (REQ_BODY_SEQUENCE), Getintegerbytes (7) ); Byte [] AuthenticationHeader = GetAuthenticationHeader Kerberosticket, Realmname, Username, CKSUM, Key, 0 ); Byte [] padata_sequence = getsequenceBytes (ConcatenateBytes GetTagandLengthbytes Asn1DataTypes.Context_specific, 1, getIntegerbytes (1)), GetTagandLengthbytes Asn1DataTypes.Context_specific, 2, getOCTStringBytes (AuthenticationHeader) ) ) ); Byte [] padata_sequences = getsequencebytes (padata_sequence); Byte [] padata = gettagandlengthbytes Asn1DataTypes.Context_specific, 3, Padata_Sequences ); TicketRequest = GetTagandLengthbytes Asn1DataTypes.Application_type, 12, GetSequenceBytes ConcatenateBytes (pvno, ConcatenateBytes (MSG_TYPE, ConcatenateBytes (Padata, Req_body) ) ) ) ); } else { TicketRequest = GetTagandLengthbytes Asn1DataTypes.Application_type, 10, GetSequenceBytes ConcatenateBytes (pvno, ConcatenateBytes (MSG_TYPE, REQ_BODY) ) ) ); } Try { DataGram DG = DC.NewDataGram (TicketRequest, TicketRequest.Length); Dc.send (DG); } catch (IllegalargumentException IL) { Il.printStackTrace (); } Catch (Exception IO) { IO.PrintStackTrace (); } BYTE TicketResponse [] = NULL; Try { DataGram DG = DC.NewDataGram (700); DC.Receive (DG); IF (Dg.getLength ()> 0) { TicketResponse = New byte [DG.GetLength ()]; System.ArrayCopy (Dg.getData (), 0, TicketResponse, 0, Dg.getLength ()); Else Return NULL; } catch (ioexception ie) { IE.PrintStackTrace (); } Return TicketResponse; } // getticketResponse There are five parameters displayed in Listing 20: UserName, ServerName, Realmname, Kerberosticket, and Key. To request a service ticket, you need to pass the TGT of the Kerberosticket byte array. On the other hand, when requesting the TGT, it is not necessary to pass one bill, so "NULL" is passed for the Kerberosticket byte array. The main difference between the TGT request and the service ticket request is the PADATA field. The Padata fields of the "Request Service Bill" in this series have discussed the Padata field of the service ticket request. At the end of GetticketResponse (), I added an IF (Kerberosticket! = NULL) block. Only when the Kerberosticket parameter does not enter NULL (it is null in all TGT requests). In the IF (Kerberosticket! = Null) block, I generated a PADATA field. As described in Figure 5 of the first article, this PADATA field is packaged a certification header generated by the getAuthenticationHeader () method. In the getAuthenticationHeader () method, you will also understand that in order to generate a certification head, you need a Checksum structure that can be generated by the getChecksumBytes () method. Now, I will find that when discussing the getChecksumBytes () method, in order to generate a checksum structure, it is necessary to use data for the CKSUMTYPE and CHECKSUM fields. Therefore, generating a authentication head requires three steps: generates the data of the CKSUMTYPE and CHECKSUM fields. If it is a service ticket request, the data of the checksum field is just a SEQUENCE calculation of all subfields for the Req-Body field containing the service ticket request (note 5 in the first article, Req-body is a service bill The fourth field of the request is behind the third field PADATA field requested by the service ticket. The data of the CKSUMTYPE field is an ASN1 representation of Integer 7. This value specifies the type of Checksum. Call the getCheckSumBytes () method and pass the data of the CKSUMTYPE and CHECKSUM fields. The getChecksumBytes () method generates a complete Checksum structure. Call the getAuthenticationHeader () method while passing the Checksum structure. GetAuthenticationHeader () Returns the certification head. After generating the authentication head, you must package it into a Padata field. To this end, there are a few things to do: Call me in the getOCTStringBytes () method described in the second article, package the authentication header into an OCTET STRING. Pack this OCTET STRING into the Padata-Value field (a context-specific tag number 2), call the getTagandLengthbytes () method to complete this task. Call the gettagandens () method again generate the Padata-value field corresponding to the Padata-Value generated in step 2. Now, link Padata-Type and Padata-Value fields. Put the byte array of steps 4 into a sequence. This sequence represents a PADATA structure (shown in Figure 5 and Listing 3 of the first article). The PADATA field shown in the first article is shown in the Padata field shown in Listing 3 is a sequence of the PADATA structure. This means that a PADATA field can contain several PADATA structures. However, only one PADATA structure is to be packaged into the PADATA field, which means that as long as the Sequence obtained from step 5 is packaged into another external or higher SEQUENCE. The higher level of step 6 Sequence represents the Sequence of the PADATA structure, and now you can pack it into the PADATA field (a context-specific tag number 3). All new code added in the GetticketResponse () method can be found in the IF (Kerberosticket! = Null) block at the end of the list 20. At this point, you will end the discussion of modifying an existing GetticketResponse () method to make it used for TGT and service ticket requests. The GetTicketResponse () method generates a service ticket request, sending this request to the KDC, receives the service ticket response, and returns the response to the calling application. Extracting a service ticket from the service ticket response and the subclile key service ticket response is similar to the TGT response. The GetticketAndKey () method in Listing 13 parses a TGT response to extract TGT and session keys. The same approach also parses the service ticket response to extract the service ticket and the subclile key from the service ticket response. So, you don't have to write any code for extracting the service ticket and the subcommitte. Creating a secure communication context now has two items required to establish a secure communication context with the electronic bank's business logic server: sub-session key and service ticket. At this time, the Kerberos client must generate a context establishment request for the business logic server for the e-banking. See Figure 7 and Listing 5 of the first article, which describes messages sent to the e-bank server for the client to establish a security context. The CREATEKERBEROSSESSION () method shown in Listing 21 handles all aspects of the service logic server of the electronic bank (including generating context establishment requests, sending requests to the server, get a response from the server, resolving the response to check if the remote server agrees Context establish requests and returns the results of these work to call applications). Look at the Createkerberossession () method in the list 21, it has the following parameters: The TicketContent byte array has a service bill for establishing a security context. The ClientRealm string packages the name of the domain Realm to which the client belongs. The ClientName string specifies the name of the request client. The SequenceNumber parameter is an Integer that represents this message number (SEQUENCE NUMBER). ENCRYPTIONKEY: Subridden key. Instream and Outstream are the cretekerberossession () method used to communicate with the electronic bank server. As is introduced in the first article, use Java-GSS to implement the server-side logic of e-banking. The GSS-Kerberos mechanism specifies that the service ticket is packaged in a certification head, and this certification header is packaged in the initialcontextToken wrapper displayed in the first article. You can use the GetAuthenticationHeader () method of the list 19 to pack service bills. Recalling that I use the getAuthenticationHeader () method to package a TGT in the GetTicketHeader () method in the list 20. In order to generate a certification head, a Checksum is required. Recalling that when discussing the GetAuthenticationHeader () method of the Listing 19, Checksum is the purpose of encrypting the binding authentication head and some application data. However, with the ticket request authentication header, the context establishes the authentication head without application data. The GSS-Kerberos mechanism uses the Checksum structure for different purposes. In addition to binding the authentication header to some application data, the GSS-Kerberos mechanism uses a checksum structure for physical network addresses (ie, clients can be used to securely communicate with the server) to bind security context. If you use this feature, you can only use the security context on the network address it bind. However, I don't prepare this function in this example mobile banking application. That's why I specified security context in the Checksum structure without any binding. To this end, I wrote a method called getnonetworkBindings (), as shown in Listing 22. GetnonetworkBindings () method is very simple. It only generates a hard-coded byte array indicating that no network binding is required. Then it calls the getCheckSumBytes () method to package hard-encoded arrays into the CKSUM field. After getting the byte array of Checksum without a network, you can pass this array to the getAuthenticationHeader () method, which returns the full authentication header. After generating the authentication head, the CREATEKERBEROSSESSION () method of the Listing 21 will pass the authentication header array with a hard-coded byte array called GssHeaderComponents. The gssheaderComponents byte array contains a BiSS header representation that will accompany a certification head in the context establishment request. Finally, package the serial GSS header and authentication head to a label number 0 of the application level. GSS requires all context establishment requests to package the label number 0 of the application level. The context establishment request is now completed. The next task is to send this request through an output stream (Outstream object). After sending the request, monitor and receive the response on the Instream object. When the CREATEKERBEROSSESSION () method receives the response, it checks the response whether it is to confirm that a new context or displays an error message. To do this, you must know the number of bytes of the length byte behind the message start label byte. GSS header (next to the length byte) provides an answer. No parsing response is made for any further processing. Just know that the e-bank server is created a new session or rejecting a session. If the e-bank server confirms to create a new session, the createkerberossession () method returns true, if not, it returns false. Listing 21. Createkerberossession () method public Boolean Createkerberoberossession Byte [] TicketContent, String ClientRealm, String ClientName, INT Sequenceenumber, Byte [] EncryptionKey, DataInputStream Instream, DataOutputstream outstream ) { Byte [] CKSUM = GetnonetworkBindings (); IF (sequencenumber == 0) Sequencenumber ; Byte [] AuthenticationHeader = GetAuthenticationHeader TicketContent, ClientRealm, ClientName, CKSUM, EncryptionKey, SequenCenumber ); Byte [] gssheadercomponents = { (Byte) 0x6, (Byte) 0x9, (byte) 0x2a, (byte) 0xfffffff86, (Byte) 0x48, (byte) 0xfffffff86, (Byte) 0xfffffff7, (Byte) 0x12, (Byte) 0x1, (Byte) 0x2, (Byte) 0x2, (Byte) 0x1, (Byte) 0x0 } Byte [] contexTrequest = GetTagandLengthbytes Asn1DataTypes.Application_type, 0, ConcatenateBytes GssHeaderComponents, AuthenticationHeader ) ); Try { OutStream.writeInt (ContextRequest.Length); OutStream.write (ContextRequest); Outstream.flush (); Byte [] eBankMessage = New byte [instream.readint ()]; Instream.readfully (eBankMessage); Int RespTokenNumber = GetNumberoflengthThbytes (eBankMessage [1]); RespTokenNumber = 12; BYTE KRB_AP_REP = (BYTE) 0x02; IF (eBankMessage [RespTokenNumber] == KRB_AP_REP) { Return True; Else Return False; } catch (exception IO) { IO.PrintStackTrace (); } Return False; } // Createkerberobessession Listing 22. GetnonetworkBindings () method Public Byte [] getnonetworkBindings () { Byte [] bindingLength = {(byte) 0x10, (byte) 0x0, (Byte) 0x0, (byte) 0x0}; BYTE [] BindingContent = New byte [16]; Byte [] contextflags_bytes = {(byte) 0x3e, (Byte) 0x00, (Byte) 0x00, (Byte) 0x00 } Byte [] CKSumBytes = ConcatenateBytes ConcatenateBytes (BindingLength, BindingContent), ContextFlags_bytes; Byte [] CKSUMTYPE = { (Byte) 0x2, (Byte) 0x3, (Byte) 0x0, (Byte) 0x80, (Byte) 0x3 } Byte [] CKSUM = GetChecksumBytes (cksumbytes, cksumtype); Return CKSUM; } // getnonetworkBindings () Send a security message to the electronic bank's business logic server If the Createkerberossession () method returns true, you know that you have established a security session with the remote Kerberos server. You can start to exchange messages with the Kerveros server. Look at the SendSecureMessage () method of the list 23. This method takes a plain text message, an encryption key, a serial number (which uniquely identifies the sending message) and the input output stream object used to exchange data as a parameter. EndsecureMessage () method generates a secure message that sends this message to the server by output stream to listen to the server's response, and return to the server's response. The message sent to the server is protected by subclock key. This means that only specific recipients (e-banking logical servers with sub-session keys) can decrypt and understand this message. Moreover, the security message contains message integrity data, so the electronic bank server can verify the integrity of messages from the client. Let's take a look at how the SendSecureMessage () method generates a safe GSS message with a plain text message. A GSS security message uses the form of Token (token format bytes). The Token format consists of the following parts: A GSS header, similar to the head that I introduced when discussing the CreatekerberosSession () method. An eight-byte token head. There are several different types of Token in the GSS-Kerveros specification, and each token type is identified by a unique header. The security message header to be generated in the SendSecureMessage () method is our interested. A secure message token is identified by a header with a value of 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0xFF, and 0xFF. A encrypted serial number that helps to detect reply attacks. For example, if there is a malicious hacker wants to reproduce (ie, repeated) a transfer instruction, he cannot generate the correct number of encrypted forms (of course, unless he knows the sub-session key). The encrypted summary of the message. Encrypted message. The five fields listed above are lin together in the correct order, and then package them into an ASN.1 application level tag number 0. This constitutes a complete GSS-Kerberos security message token, as shown in Figure 1. Figure 1. In order to generate the full security token as shown in Figure 1, all five fields must be generated. The first two fields do not have dynamic content, they are the same in all security messages, so I encode their values in the list 23. The other three fields must be dynamically calculated according to the following algorithm: 1. Add additional bytes in a plain text message to make the number of bytes in the message are multiple multiple. 2. Generate an eight-byte random number called Confounder. Links the message that Confounder is filled in step 1. 3. Serial TOKEN header (the second field in Figure 1) and the result of step 2. Then calculate the result of the link to the 16-byte MD5 summary value. 4. Encrypt the 16-byte summary value obtained by submitting step 3 by sub-session key. The encryption algorithm is a DES-CBC using zero IV. The last eight bytes of the encrypted data (the top eight bytes) form the fourth field of Figure 1 (encrypted summary value). 5. Now you must generate an encrypted 8-byte sequence number (the third field of Figure 1). This serial number is encrypted by subclocking keys and step 4 using IV's encrypted summary value. 6. Now take the results of step 2 (link together for Confounder and padded messages) and encrypt it with DES-CBC. To perform this encryption, perform the key generated by the OR operation using a 0xF0 pair of sub-session keys. The result of this encrypted result constitutes the fifth field of Figure 1, which is an encrypted message. After generating each field, link them into one byte array, finally call the GetTagandLengthbytes () method to attach an application level tag number 0 in front of the link-based array. These steps in the SendSecureMessage () method of the list 23 can be observed. After generating a security message, the message is sent to the server by the output stream, listens to the server's response and returns the response to the recipient. Listing 23. SendSecureMessage () Method PUBLIC BYTE [] SendSecureMessage (String Message, Byte [] Sub_SessionKey, INT seqnumber, DataInputStream Instream, DataOutputstream outstream ) { Byte [] gssheadercomponents = { (Byte) 0x6, (Byte) 0x9, (byte) 0x2a, (Byte) 0x86, (Byte) 0x48, (Byte) 0x86, (byte) 0xF7, (Byte) 0x12, (Byte) 0x01, (Byte) 0x02, (Byte) 0x02 } Byte [] tokenheader = {(byte) 0x02, (Byte) 0x01, (Byte) 0x00, (Byte) 0x00, (Byte) 0x00, (Byte) 0x00, (Byte) 0xFF, (Byte) 0xFF } Try { / ***** step 1: ***** / Byte [] paddeddatabytes = getpaddeddata (message.getbytes ()); / ***** step 2: ***** / BYTE [] confounder = concatenatebytes (getrandomnumber (), getrandomnumber ()); / ***** STEP 3: ***** / Byte [] messageBytes = ConcatenateBytes (Confounder, PaddedDataBytes); Byte [] DigestBytes = GETMD5DIGESTVALUE ConcatenateBytes (Tokenheader, Messagebytes); CbcBlockCipher Cipher = New CbcblockCIpher (New desengine ()); KeyParameter KP = New KeyParameter (SUB_SESSIONKEY); Parameterswithiv IV = New Parameterswithiv (KP, New Byte [8]); Cipher.init (TRUE, IV); Byte Processedblock [] = new byte [digestbytes.length]; BYTE Message_cksum [] = new byte [8]; For (int x = 0; x Cipher.ProcessBlock (DigestBytes, x * 8, Processedblock, x * 8); System.ArrayCopy (Processedblock, x * 8, Message_cksum, 0, 8); IV = New Parameterswithiv (KP, Message_cksum); Cipher.init (TRUE, IV); } / ***** step 4: ***** / Byte [] sequencenumber = { (Byte) 0xFF, (Byte) 0xFF, (Byte) 0xFF, (Byte) 0xFF, (Byte) 0x00, (Byte) 0x00, (Byte) 0x00, (Byte) 0x00 } SequenCenumber [0] = (byte) seqnumber; / ***** step 5: ***** / Byte [] encryptedseqnumber = encrypt (sub_sessionkey, sequenceenumber, message_cksum); / ***** STEP 6: ***** / Byte [] EncryptedMessage = Encrypt (getContextKey), MessageBytes, New Byte [8]); Byte [] messageToken = GetTagandLengthbytes Asn1DataTypes.Application_type, 0, ConcatenateBytes GssHeaderComponents, ConcatenateBytes Tokenheader, ConcatenateBytes EncryptedseqNumber, ConcatenateBytes (Message_cksum, EncryptedMessage ) ) ) ) ); / ***** step 7: ***** / OutStream.writeInt (MessageToken.Length); OutStream.write (MessageToken); Outstream.flush (); / ***** step 8: ***** / Byte [] responseToken = new byte [instream.readint ()]; Instream.readfully (responseToken); Return ResponseToken; } catch (ioexception ie) { IE.PrintStackTrace (); } catch (exception e) { E.PrintStackTrace (); } Return NULL; } // sendsecureMessage Public Byte [] getContextKey (Byte KeyValue " { For (INT i = 0; i KeyValue [i] ^ = 0xF0; Return KeyValue; } // getContextKey The decoding server message is like a message generated and sent to the server, the server message returned by the sendSecureMessage () method is secure. It follows the same TOKEN format shown in Figure 1, which means that only clients with sub-session keys can decrypt this message. I have written a method called DecodeSecureMessage () (as shown in Listing 24), which decrypts this message and returns a message in plain text format with a secure message and decryption key. The decoding algorithm is as follows: The first step is to separate the encryption portion of the message (the fifth field shown in Figure 24) to the Token header. The length of the token head is fixed, so only the number of length bytes varies with the total length of the message. Therefore, the number of length bytes is read and the message is copied to a separate byte array accordingly. The second step is to read the message Checksum (the fourth field of Figure 1). Now use the decryption key to decrypt the encrypted message. Then, take the token head (second field of FIG. 1), link it with the decrypted message, and then take the Link byte array MD5 summary. Now encrypt the MD5 summary value. It is necessary to compare the eight-byte messages of step 2 Checksum and the post eight bytes of the MD5 summary value of step fifth. If they match, the integrity check is verified. After verification, remove the Cofounder (the first eight bytes of the decryption message) and return the rest of the message (it is the plain text message required). Listing 24. DecodeSecureMessage () method Public string decodeeSecureMessage (byte [] decryptionkey) { INT msg_tagandheaderLength = 36; INT msg_lengthbytes = getNumberoflengthThbytes (Message [1]); INT Encryptedmsg_offset = msg_tagandheaderLength MSG_LENGTHBYTES; BYTE [] EncryptedMessage = new byte [message.Length - EncryptedMsg_offset]; System.Arraycopy (Message, Encryptedmsg_offset, EncryptedMessage, 0, EncryptedMessage.Length; BYTE [] msg_checksum = new byte [8]; System.Arraycopy (Message, Encryptedmsg_offset-8), MSG_Checksum, 0, MSG_Checksum.length; Byte [] Decryptedmsg = Decrypt (DecryptionKey, EncryptedMessage, New Byte [8]); Byte [] tokenheader = { (Byte) 0x2, (Byte) 0x1, (Byte) 0x0, (Byte) 0x0, (Byte) 0x0, (Byte) 0x0, (Byte) 0xFF, (Byte) 0xFF } Byte [] msg_digest = getmd5digestValue (ConcatenateBytes (Tokenheader, DecryptedMSG); BYTE [] decmsg_checksum = new byte [8]; Try { CbcBlockCipher Cipher = New CbcblockCIpher (New desengine ()); KeyParameter Kp = New KeyParameter (GetContextKey); Parameterswithiv IV = New Parameterswithiv (KP, DECMSG_CHECKSUM); Cipher.init (TRUE, IV); Byte [] processedblock = new byte [msg_digest.length]; For (int x = 0; x Cipher.ProcessBlock (msg_digest, x * 8, processedblock, x * 8); System.ArrayCopy (Processedblock, x * 8, DECMSG_CHECKSUM, 0, 8); IV = New Parameterswithiv (KP, DECMSG_CHECKSUM); Cipher.init (TRUE, IV); } } catch (java.lang.illegalargumentException il) { Il.printStackTrace (); } For (int x = 0; x IF (! (msg_checksum [x] == decmsg_checksum [x]))) Return NULL; } Return New String (DecryptedMSG, MSG_Checksum.length, Decryptedmsg.length - msg_checksum.length; } // decodeSecureMessage () Public Byte [] getContextKey (Byte KeyValue " { For (INT i = 0; i KeyValue [i] ^ = 0xF0; Return KeyValue; } // getContextKey Example The mobile banking application has completed all the phases of the security Kerberos communication required for the sample mobile banking application. It is now possible to discuss how mobile bank MIDlet uses the Kerberos client function and communicates with the server of the e-banking. Listing 25 shows a simple MIDlet that simulates an example mobile bank application. Listing 25. A sample mobile bank MIDletimport java.io. *; Import java.util. *; Import javax.microedition.lcdui. *; Import javax.microedition.midlet. *; Import javax.microedition.io. *; Public Class J2MeclientMidlet Extends Midlet Immman, Runnable { Private command okcommand = NULL; Private command exitcommand = null; Private command sendmoneyCommand = NULL; Private Display Display = NULL DISPLAY; Private form transform; Private form transresformation; PRIVATE FORM PROGRESSFORM; Private textfield txt_username; Private textfield txt_password; PRIVATE TEXTFIELD TXT_AMOUNT; Private textfield txt_sendto; PRIVATE STRINGITEM SI_MESSAGE PRIVATE TEXTFIELD TXT_LABEL; PRIVATE SOCKETCONNECTION SC; PRIVATE DATAINPUTSTREAM IS; Private DataOutputstream OS; Private DataGramConnection DC; Private Kerberosclient KC; Private ticketandKey TK; Private string realmname = "ebank.local"; Private string kdcservername = "kRBTGT"; Private string kdcaddress = "localhost"; Private Int Kdcport = 8080; Private string e_bankname = "ebankserver"; Private string e_bankaddress = "localhost"; Private Int E_BANKPORT = 8000; Private INT i = 0; Private byte [] response Public J2meclientMidlet () { EXITCOMMAND = New Command ("EXIT", Command.exit, 0); SendMoneyCommand = New Command ("Pay", Command.Screen, 1); Okcommand = New Command ("Back", Command.exit, 2); Display = display.getdisplay (this); Transactionform (); } Public void startapp () { Thread T = New Thread (this); T.Start (); } // startApp () Public void pauseApp () { } // pauseApp () Public void destroyApp (boolean unconditional) { } // destroyApp Public void CommandAction (Command C, Displayable S) { IF (c == EXITCOMMAND) { DESTROYAPP (FALSE); NotifyDestroyed (); } else if (c == sendmoneyCommand) { SendMoney (); } else if (c == okcommand) { Transactionform (); } else if (c == exitcommand) { DESTROYAPP (TRUE); } } // CommandAction Public void sendmoney () { System.out.println ("MIDlet ... SendMoney () Starts"); String Username = txt_username.getstring (); String password = txt_password.getstring (); Kc.SetParameters (Username, Password, Realmname); System.out.println ("MIDlet ... getting tgt ticket"); Response = kc.getticketResponse Username, KdcServername, Realmname, NULL, NULL ); System.out.println ("MIDlet ... Getting Session Key from TGT Response"); TK = new ticketandKey (); TK = kc.getticketkerKey (response, kc.getsecretkey ()); System.out.println ("MIDlet ... Getting Service Ticket (TGS)"); Response = kc.getticketResponse Username, E_BANKNAME, Realmname, Tk.getticket (), Tk.getKey () ); System.out.Println ("MIDlet ... getting subs-session key from tgs response"); TK = kc.getticketAndKey (response, tk.getKey ()); i ; System.out.Println ("MIDlet ... Establishing Secure Context with e-bank"); Boolean ISESTABLISHED = kc.createkerberossession Tk.getticket (), Realmname, Username, i, Tk.getKey (), IS, OS ); ISESTABLISHED { System.out.Println ("MIDlet ... Sending Transactoin Message over Secure Context"); Byte [] rspMessage = kc.sendsecureMessage "Transaction of Amount:" txt_amount.getstring () "From:" Username "TO:" txt_sendto.getstring (), Tk.getKey (), i, IS, OS ); String decodedMessage = kc.decodeeSecureMessage (RspMessage, Tk.getKey ()); IF (decodedMessage! = null) ShowTransResult ("OK", DECODMESSAGE); Else ShowTransResult ("Error!", "Transaction Failed .."); Else System.out.println ("MIDlet ... context establishment failed .."); } // sendmoney () Public synchronized void run () { Try { DC = (DataGramConnection) Connector.open ("DataGram: //" Kdcaddress ":" Kdcport); KC = New Kerberosclient (DC); SC = (SocketConnection) Connector.open ("socket: //" e_bankaddress ":" E_BANKPORT); Sc.setsocketoption (SocketConnection.keepalive, 1); IS = sc.opendatainputstream (); Os = sc.opendataoutputstream (); } Catch (ConnectionNotFoundException CE) { System.out.Println ("Socket Connection To Server Not Found ...); } catch (ioexception ie) { IE.PrintStackTrace (); } catch (exception e) { E.PrintStackTrace (); } } //run Public void transactionform () { Transform = New Form ("eBank Transaction Form"); TXT_USERNAME = New TextField ("UserName", ", 10, TextField.Any); TXT_Password = New TextField ("Password", ", 10, TextField.password); TXT_AMOUNT = New TextField ("Amount", ", 4, TextField.Numeric); TXT_SENDTO = New TextField ("Pay to", ", 10, TextField.Any); Transform.Append (txt_username); Transform.Append (txt_password); Transform.Append (txt_amount); Transform.Append (txt_sendto); Transform.addcommand; Transform.AddCommand (EXITCOMMAND); Transform.SetCommandListener (this); Display.setcurrent (Transform); } Public void showTransResult (string info, string message) { Transres = New Form ("Transaction Result"); Si_MESSAGE = New StringItem ("status:", info); TXT_Label = New TextField ("Result:", Message, 150, TextField.Any); Transresmm.Append (Si_Message); Transresm.Append (txt_label); Transresmm.addcommand (exitcommand); Transresmm.addcommand (OkCommand); Transresform.setCommandListener (this); Display.SetCurrent (Transresform); } } // j2meclientmidlet Running this MIDlet gets the screen as shown in Figure 2. Figure 2. Figure 2 shows a very simple GUI for this example mobile bank application. Figure 2 also shows four data input fields: The "UserName" field takes the user name of the person who uses the financial services of the mobile bank MIDlet. "Password" field takes the user's password. The "Amount" field allows you to pay the amount to be paid to a payee. "Pay to" field contains the username of the payee. After entering the data, press the PAY button. The event processor of the PAY button (the sendMoney () method in Listing 25) Performs all seven stages of the Kerveros communication: Generate a TGT request, send the request to the server and receive the TGT response. Extract TGT and session keys from the TGT response. Generate a service ticket request, send the request to the KDC, and receive the service ticket response. Extract the service ticket and the subclile key from the service ticket response. Generate context establishment requests and sends a business logic server for the electronic bank to receive a response, and parse it to determine that the server agrees to establish a new security context. Generate a secure message, send this message to the server and receive the server's response. Decoding messages from the server. The MIDlet code of the list 25 is quite simple, and there is no need to explain. Just pay attention to the following: I use different threads (Run () methods in Listing 25 create DataGRAM connection (DC) and Socket connection data inputs and output streams. This is because MIDP 2.0 does not allow you to create a DataGram and Socket connection in the main execution thread of J2ME MIDlet. In the J2ME MIDET of Listing 25, I hardly encode the domain, server name, address, and port number of the KDC server and the name and address of the e-bank server. Note that hard coding in the MIDlet is for display purposes. On the other hand, KerberosClient is completely reusable. In order to test this application, a GSS server running as an e-bank server. The source code of this article download contains a server-side application and a readme.txt file, which describes how to run this server. Finally, note that I didn't design an electronic bank communication framework, I just designed a Kerberos-based security framework. You can design your own communication framework and provide safe support with Kerberosclient. For example, you can use an XML format to define a different message as a transfer instruction. Conclusion In this three-part series, I showed the security Kerberos communication in the J2ME application. It introduces Kerveros communication that is exchanged for a series of encryption keys. Also introduced the J2ME application how to use the key to establish a communication context and securely exchange messages in a remote electronic bank server. I also provided J2ME code for all the concepts discussed in the article. Referring Abstract You can see this article in our entire website in our entire website. Read the first one of this series and the second article. Download the source code included in this article. Read the official RFC 1510 of Kerberos (5th Edition). Download the Bounce Castle encryption library. The code in this article is tested with the 1.19 version of Bouncy Castle. Read the official DES and DES MODES OF OPERATION (including the CBC mode) specification. Visit the Kerberos Working Group page on the IETF website. In the article "SIMPLIFY Enterprise Java Authentication with Single Sign-on" (developerWorks, Sep 2003), a single sign-on (SSO) using Kerberos and Java GSS API is shown. Here you can find a good set of links on Kerberos. Download a complete ASN.1 document and encoding rules. Read Jason Garman's Kerberos: The Definitive Guide (O'Reilly Associates, 2003) to learn Kerberos. Access the GSS page. Take a look at the MIT's KDC implementation. Both CSG group and Heimdal provide free Kerberos implementation. Find more information about all content of Kerberos on the developer bookstore. About the author Faheem Khan is an independent software consultant, expertise is an enterprise application integration (EAI) and B2B solution. Readers can contact Faheem via fkhan872@yahoo.com.