簡介: 在本系列的上一篇文章中,您看到了對可以安全地連接到支持 Kerveros 的服務器的 J2ME 應 用程序的描述,還可了解在字節水平上 Kerberos 加密的細節問題。本文則深入到應用程序自身內部。您 將看到如何使用 J2ME 的工具程序以及一些開放源代碼庫完成異常強大的加密任務。
在本系列的 上一篇文章 中,我介紹了一個使用 Kerberos 與電子銀行服務器進行安全通信的移動銀 行 MIDlet 應用程序。我還解釋了基於 J2ME 的 Kerveros 客戶機應用程序與遠程服務器交換 Kerberos 票據和密鑰時所交換的數據格式和消息序列。
在本文中,我將開始實現生成並處理這些消息的 J2ME 類。我將首先簡單描述構成這個基於 J2ME 的 Kerveros 客戶機的主要類的作用,然後我將解釋並展示這些類如何生成在第一篇文章中討論過的基本 ASN.1 數據類型。在第三節中,我將展示如何生成一個用於在 Kerveros 通信中進行加密和解密的密鑰。 最後一節將展示 J2ME 客戶機如何生成對 Kerveros 票據的請求。
基於 J2ME 的 Kerveros 客戶機中的類
在本文中,將要討論三個 J2ME 類的操作:
ASN1DataTypes
KerberosClient
KerberosKey
ASN1DataTypes
類將包裝所有一般性的 ASN.1 功能,如發布像 INTEGER 和 STRING 這 樣的通用數據類型。 KerberosClient 類擴展 ASN1DataTypes 類,使用它的底層功能,並提供所有特定 於 Kerveros 的功能。因此,可以說我將所需要的功能簡單地分為兩組:所有一般性的 ASN.1 功能都在 ASN1DataTypes 類中,而所有特定於 Kerveros 的功能都在 KerberosClient 類中。這提高了代碼的重用 性。如果您希望構建自己的、使用 ASN.1 功能的非 Kerveros 應用程序,那麼您可以使用 ASN1DataTypes 類。
Kerberos 定義了一種利用用戶的密碼生成密鑰的算法。 KerberosKey 類實現了這種算法 。在 Kerveros 通信中您將需要這個密鑰。
我將在本文分別展示這些類中的每個方法。我還在一個單獨的 源代碼下載中加入了這些類。這個包將 所有東西放到一組類中,可以將它們編譯為一個 J2ME 項目。這個下載包含以下文件:
ReadMe.txt ,它包含描述如何根據本文的需要練習這些代碼的指導。
ASN1DataTypes.java ,它實現了 ASN1DataTypes 類。
KerberosClient.java ,它實現了 KerberosClient 類。
KerberosKey.java ,它實現了 KerberosKey 類。
J2MEClientMIDlet.java ,它提供了可以用來測試這些代碼的一個非常簡單的 MIDlet 包裝器。
現在,我將進一步探討這些類的細節。
生成基本 ASN.1 數據類型
清單 1 中顯示的 ASN1DataTypes 類處理生成和處理 ASN.1 數據結構所需要的所有底層功能。這個類 包含兩種方法: 生成(authoring) 方法負責生成 ASN.1 數據結構,而 處理(processing) 方法負責 處理已生成的或者從遠程應用程序收到的消息。我將在本文中解釋並實現生成方法,在本系列的下一篇文 章中討論處理方法。
清單 1 只包含 ASN.1 類中不同方法的聲明。我將在後面的幾節中用單獨的清單展示每一個方法的實 現。
清單 1. ASN1DataTypes 類
public class ASN1DataTypes
{
public byte[] getLengthBytes(int length){}
public byte[] getIntegerBytes (int integerContents){}
public byte[] getGeneralStringBytes (String generalStringContent){}
public byte[] getOctetStringBytes (byte[] octetStringContents){}
public byte[] getBitStringBytes (byte[] content){}
public byte[] getGeneralizedTimeBytes (byte[] generalizedTimeContent){}
public byte[] concatenateBytes (byte[] array1, byte[] array2){}
public byte[] getSequenceBytes (byte[] sequenceContents){}
public byte[] getTagAndLengthBytes (int tagType, int tagNumber, byte[] tagContents){}
}//ASN1DataTypes
getLengthBytes()
(在清單 2 中顯示的)這個方法將一個整數值( length )作為參數。它生成一個該長度的 ASN.1 表示,並返回一個符合 ASN.1 長度格式的字節數組。
清單 2. getLengthBytes() 方法
public byte[] getLengthBytes(int length)
{
if (length < 0)
return null;
byte lengthBytes[];
if (length <= 127)
{
lengthBytes = new byte[1];
lengthBytes[0] = (byte)(length & 0xff);
}
else
{
int tempLength = length;
int bytesRequired = 2;
do
{
tempLength = tempLength / 256;
if (tempLength > 0)
bytesRequired ++;
}while (tempLength > 0);
lengthBytes = new byte[bytesRequired];
byte firstLengthByte = (byte) (bytesRequired -1);
firstLengthByte |= 0x80;
lengthBytes[0] = firstLengthByte;
int j = bytesRequired - 1;
for (int i=1; i < bytesRequired; i++) {
j--;
lengthBytes[i] = (byte)(length >>> (j*8) & 0xff);
}//for
}//else
return lengthBytes;
}//getLengthBytes
回想一下在本系列的 第一篇文章 中對表 2 的討論,有兩種表示字節長度的方法:單字節表示法和多 字節表示法。單字節長度表示法用於表示小於或者等於 127 的長度值,而當長度值大於 127 時使用多字 節長度表示法。
getLengthBytes() 方法首先檢查長度值是否為負。如果為負,則只是返回 null,因為我不能處理負 值。
然後這個方法檢查長度值是否小於或者等於 127。如果是,就需要使用單字節長度表示法。
注意在 J2ME 中一個整數是 4 字節數據,而單字節長度表示法只需要 1 個字節。如果長度參數是 0 到 127 之間(包括這個兩數)的一個值,那麼其字節表達就在 0x00000000 與 0x0000007f 之間(意味 著只有最低有效位字節包含有用的數據)。將這個整數造型為一個單字節時,只有最低有效位字節( 0x00 到 0x7f )會作為十六進制值拷貝到單字節數組。因此,如果長度值在 0 到 127 之間,那麼我可 以只執行該長度與 0xff 之間的一個按位 AND 操作。這個操作會得到一個整數,它有效的最高 3 個字節 都將填入零。因此,我可以將按位操作的結果造型為一個字節,將這個字節放入一個單字節數組,並將這 個數組返回給調用應用程序。
如果長度值大於 127,那麼我必須使用多字節長度表示法,它至少使用 2 字節數據。第一個字節表明 長度字節的字節數,後面是實際的長度字節(有關這種格式的詳細解釋請參閱 第一篇文章)。
如果長度值小於 256,那麼就需要總共 2 個長度字節 ―― 1 個字節表明還有一個長度字節,1 個字 節包含實際的長度值。如果長度值至少為 256 並小於 65536(256 乘 256),那麼就需要總共 3 個長度 字節 ―― 1 個字節表明還有 2 個長度字節,兩個字節包含實際的長度值。
因此,在多字節格式中所需要的字節數取決於長度值。這就是為什麼在 getLengthBytes() 的 else 塊的 do - while 循環中要計算長度字節所需要的字節數。
確定所需要字節數的方法很簡單。我聲明了一個名為 bytesRequired 的字節計數器,從 2 開始計數 (所需要的最少字節數),將長度值除以 256,並檢查商是否大於或者等於 1。如果是,那麼就表明原始 長度值大於 256,因而需要至少 3 個字節,所以我增加計數器( bytesRequired )。
我繼續將長度值除以 256 並增加字節計數器,直到除得的值小於 1。這時,我就知道找到了在多字節 整數格式中需要的字節數。
知道了所需要的字節數後,我就實例化一個具有適當大小的字節數組。自然,長度字節中的第一個字 節將表明還有多少個長度字節。因此,我只是將所需要的字節數減 1( bytesRequired-1 ),並拷貝到 一個名為 firstLengthByte 的字節中。
看一下清單 2 中 getLengthBytes() 方法中的 firstLengthByte |= 0x80 這一行代碼。這一行代碼 對 firstLengthByte 和 0x80 ( 1000 0000 )進行按拉 OR 操作,並將結果儲存到 firstLengthByte 中。這種邏輯 OR 操作會將 firstLengthByte 的最左邊(最高有效)位設置為 1。回想在本系列 第一篇 文章 中的討論,在希望使用多字節整數格式的時候,必須將第一個長度字節的最左邊一位設置為 1。
下一行( lengthBytes[0]=firstLengthByte )只是拷貝在包含長度字節的數組的開始位置上的 firstLengthByte 。然後,有一個 for 循環,它將長度字節從長度參數中拷貝到在 lengthBytes 數組中 它們的正確位置上。當 for 循環退出時,就得到了符合 ASN.1 格式的這個 lengthBytes 數組。清單 2 中 getLengthBytes() 方法的最後一行返回這個數組。
getIntegerBytes()
這個方法取一個整數( value )作為參數並返回以 ASN.1 INTEGER 表達的這個整數值。回想一下在 本系列 第一篇文章 的表 1 中曾提到,在ASN.1 中 INTEGER 是一種通用數據類型。
清單 3 中顯示了 getIntegerBytes() 方法的實現。
清單 3. getIntegerBytes() 方法
public byte[] getIntegerBytes (int integerContents)
{
//1. Declare a byte array named finalBytes, which will
// hold all the bytes of the ASN.1 byte array representation.
byte finalBytes[];
//2. Calculate the number of bytes required to hold the
// contents part of the ASN.1 byte array representation.
int tempValue = integerContents;
int contentBytesCount = 1;
do {
tempValue = tempValue / 256;
if (tempValue >0)
contentBytesCount ++;
} while (tempValue > 0);
//3. Use the getLengthBytes() method of Listing 3 to author
// the length bytes. Store the length bytes in an array named lengthBytes.
byte lengthBytes[] = getLengthBytes(contentBytesCount );
//4. Get the number of bytes in the lengthBytes array.
int lengthBytesCount = lengthBytes.length;
//5. Calculate the number of bytes required to hold the
// complete ASN.1 byte array representation
// (the sum total of the number of tag bytes, length bytes, and content bytes).
// Store the number of bytes in a variable named totalBytesCount.
int totalBytesCount = 1 + lengthBytesCount + contentBytesCount ;
//6. Instantiate the finalBytes array to totalBytesCount size.
finalBytes = new byte[totalBytesCount];
//7. Copy the tag byte at the start of the finalBytes array.
finalBytes[0] = (byte)0x02;
//8. Copy the length bytes from the lengthBytes array
// to the finalBytes array just after the tag byte.
for (int i=0; i < lengthBytes.length; i++)
finalBytes[i+1] = lengthBytes[i];
//9. Copy the content bytes to the finalBytes array
// just after the length bytes.
int k = totalBytesCount - lengthBytesCount - 1;
for (int j=lengthBytesCount+1; j<totalBytesCount; j++){
k--;
finalBytes[j] = (byte) (integerContents >>> (k*8) & 255);
}//for
//10. Return the finalBytes array.
return finalBytes;
}//getIntegerBytes
這個方法首先聲明一個名為 finalBytes 的字節數組。這個字節數組包含 INTEGER 數據類型結構的所 有字節。不過,我還不知道 finalBytes 數組的大小。我首先需要計算 INTEGER 結構中的字節數,這種 計算由幾步組成:
第一步是計算容納這個整數值( INTEGER 結構的內容部分)所需要的字節數。為此,我使用了一個 do - while 循環,它不斷地將 value 整數除以 256,直到得到的值小於1。當這個循環退出時,容納內 容部分所需要的字節數就儲存在一個名為 contentBytesCount 的變量中。
這個方法再將所需要的長度作為一個整數傳遞給 getLengthBytes() 方法,這個方法返回以 ASN.1 表 達的長度字節。我將長度字節數儲存到一個名為 lengthBytesCount 的變量中。
回想一下在 本系列第一篇文章中討論過,所有 ASN.1 數據類型表達的字節數組都包含三個部分:標 簽字節、長度字節和內容字節。因此,ASN.1 字節數組表達需要包含所有這三部分的足夠空間。
下一步是計算將要包含 INTEGER 結構的所有字節的數組的大小。我是通過將標簽字節長度(對於 INTEGER 和所有其他在 Kerberos 中使用的標簽來說是 1)、長度字節數和內容字節數相加進行這種計算 的。 int totalBytesCount = 1 + lengthBytesCount + contentBytesCount; 一行進行的就是這種計算 ,並將所需要的字節數儲存到一個名為 totalBytesCount 的變量中。
下面,我實例化一個大小為 totalBytesCount 的字節數組 finalBytes 。過程的其余部分很簡單,我 將標簽字節(對於 INTEGER 來說是 0x02 )儲存到 finalBytes 數組的開始處。然後,將長度字節拷貝 到 finalBytes 數組中標簽字節後面。最後,我將內容字節拷貝到長度字節後並返回 finalBytes 數組。
getGeneralStringBytes()、getOctetStringBytes()、getBitStringBytes() 和 getGeneralizedTimeBytes()
像 getIntegerBytes() 一樣,每一個方法返回一種 ASN.1 通用數據類型結構。
清單 4 中的 getGeneralStringBytes() 方法生成一個 ASN.1 GeneralString 的字節數組表達。類似 地,清單 5 中的 getOctetStringBytes() 方法返回 ASN.1 OctetString 的字節數組表達。清單 6 中的 getBitStringBytes() 方法返回 BitString 的 ASN.1 表達。最後,清單 7 中的 getGeneralizedTimeBytes() 方法返回 ASN.1 GeneralizedTime 值的字節數組表達。
所有這些方法遵循在前面對 getIntegerBytes() 方法的討論中見過的同樣實現邏輯:
聲明一個名為 finalBytes 的字節數組,它將包含 ASN.1 字節數組表達的所有字節。
計算容納 ASN.1 字節數組表達的內容所需要的字節數。
用清單 3 中的 getLengthBytes() 方法生成長度字節。將長度字節儲存到一個名為 lengthBytes 的 數組中。
得到 lengthBytes 數組中的字節數。
計算容納完整的 ASN.1 字節數組表達所需要的字節數(標簽字節、長度字節和內容字節的總和)。將 這個字節數儲存到一個名為 totalBytesCount 的變量中。
實例化一個具有 totalBytesCount 的值大小的 finalBytes 數組。
將標簽字節拷貝到 finalBytes 數組的開始處。
將 lengthBytes 數組中的長度字節拷貝到 finalBytes 數組中緊隨標簽字節的位置。
將內容字節拷貝到 finalBytes 數組中緊隨長度字節的位置。
返回 finalBytes 數組。
清單 4、清單 5、清單 6 和清單 7 帶有幫助您跟蹤和對照上述 10 步中每一步與 J2ME 代碼中相應 行的注釋。
清單 4. getGeneralStringBytes() 方法
public byte[] getGeneralStringBytes (String generalStringContent)
{
//1. Declare a byte array named finalBytes, which will
// hold all the bytes of the ASN.1 byte array representation.
byte finalBytes[];
//2. Calculate the number of bytes required to hold the
// contents part of the ASN.1 byte array representation.
int contentBytesCount = generalStringContent.length();
//3. Use the getLengthBytes() method of Listing 3 to author
// the length bytes. Store the length bytes in
// an array named lengthBytes.
byte lengthBytes[] = getLengthBytes(contentBytesCount );
//4. Get the number of bytes in the lengthBytes array.
int lengthBytesCount = lengthBytes.length;
//5. Calculate the number of bytes required to hold the complete
// ASN.1 byte array representation (the sum total of the number
// of tag bytes, length bytes, and content bytes).
// Store the number of bytes in a variable named totalBytesCount.
int totalBytesCount = 1 + lengthBytesCount + contentBytesCount ;
//6. Instantiate the finalBytes array to totalBytesCount size.
finalBytes = new byte[totalBytesCount];
//7.Copy the tag byte at the start of the finalBytes array.
finalBytes[0] = (byte)0x1B;
//8. Copy the length bytes from the lengthBytes array
// to the finalBytes array just after the tag byte.
for (int i=0; i < lengthBytes.length; i++)
finalBytes[i+1] = lengthBytes[i];
//9. Copy the content bytes to the finalBytes array just after the length bytes.
byte tempString[] = generalStringContent.getBytes();
for (int j=lengthBytesCount+1; j<totalBytesCount; j++)
finalBytes[j] = tempString[j-(lengthBytesCount+1)];
//10. Return the finalBytes array.
return finalBytes;
}//getGeneralStringBytes
清單 5. getOctetStringBytes() 方法
public byte[] getOctetStringBytes (byte[] octetStringContents)
{
//1. Declare a byte array named finalBytes, which will
// hold all the bytes of the ASN.1 byte array representation.
byte finalBytes[];
//2. Calculate the number of bytes required to hold the
// contents part of the ASN.1 byte array representation.
int contentBytesCount = octetStringContents.length;
//3. Use the getLengthBytes() method of Listing 3 to author
// the length bytes. Store the length bytes in
// an array named lengthBytes.
byte lengthBytes[] = getLengthBytes(contentBytesCount );
//4. Get the number of bytes in the lengthBytes array.
int lengthBytesCount = lengthBytes.length;
//5. Calculate the number of bytes required to hold the complete
// ASN.1 byte array representation (the sum total of the number
// of tag bytes, length bytes, and content bytes).
// Store the number of bytes in a variable named totalBytesCount.
int totalBytesCount = 1 + lengthBytesCount + contentBytesCount ;
//6. Instantiate the finalBytes array to totalBytesCount size.
finalBytes = new byte[totalBytesCount];
//7. Copy the tag byte at the start of the finalBytes array.
finalBytes[0] = (byte)0x04;
//8. Copy the length bytes from the lengthBytes array to the
// finalBytes array just after the tag byte.
for (int i=0; i < lengthBytes.length; i++)
finalBytes[i+1] = lengthBytes[i];
//9. Copy the content bytes to the finalBytes array
// just after the length bytes.
for (int j=lengthBytesCount+1; j<totalBytesCount; j++)
finalBytes[j] = octetStringContents[j-(lengthBytesCount+1)];
//10. Return the finalBytes array.
return finalBytes;
}//getOctetStringBytes
清單 6. getBitStringBytes() 方法
public byte[] getBitStringBytes (byte[] content)
{
//1. Declare a byte array named finalBytes, which will
// hold all the bytes of the ASN.1 byte array representation.
byte finalBytes[];
//2. Calculate the number of bytes required to hold the
// contents part of the ASN.1 byte array representation.
int contentBytesCount = content.length;
//3. Use the getLengthBytes() method of Listing 3 to author
// the length bytes. Store the length bytes in
// an array named lengthBytes.
byte lengthBytes[] = getLengthBytes(contentBytesCount );
//4. Get the number of bytes in the lengthBytes array.
int lengthBytesCount = lengthBytes.length;
//5. Calculate the number of bytes required to hold the complete
// ASN.1 byte array representation (the sum total of the number
// of tag bytes, length bytes, and content bytes).
// Store the number of bytes in a variable named totalBytesCount.
int totalBytesCount = 1 + lengthBytesCount + contentBytesCount ;
//6. Instantiate the finalBytes array to totalBytesCount size.
finalBytes = new byte[totalBytesCount];
//7. Copy the tag byte at the start of the finalBytes array.
finalBytes[0] = (byte)0x03;
//8. Copy the length bytes from the lengthBytes array to the
// finalBytes array just after the tag byte.
for (int i=0; i < lengthBytes.length; i++)
finalBytes[i+1] = lengthBytes[i];
//9. Copy the content bytes to the finalBytes array
// just after the length bytes.
for (int j=lengthBytesCount+1; j<totalBytesCount; j++)
finalBytes[j] = content[j-(lengthBytesCount+1)];
//10. Return the finalBytes array.
return finalBytes;
}//getBitStringBytes
清單 7. getGeneralizedTimeBytes() 方法
public byte[] getGeneralizedTimeBytes (byte[] generalizedTimeContent)
{
//1. Declare a byte array named finalBytes, which will
// hold all the bytes of the ASN.1 byte array representation.
byte finalBytes[];
//2. Calculate the number of bytes required to hold the
// contents part of the ASN.1 byte array representation.
int contentBytesCount = generalizedTimeContent.length;
//3. Use the getLengthBytes() method of Listing 3 to author
// the length bytes. Store the length bytes in
// an array named lengthBytes.
byte lengthBytes[] = getLengthBytes(contentBytesCount );
//4. Get the number of bytes in the lengthBytes array.
int lengthBytesCount = lengthBytes.length;
//5. Calculate the number of bytes required to hold the complete
// ASN.1 byte array representation (the sum total of the number
// of tag bytes, length bytes, and content bytes).
// Store the number of bytes in a variable named totalBytesCount.
int totalBytesCount = 1 + lengthBytesCount + contentBytesCount ;
//6. Instantiate the finalBytes array to totalBytesCount size.
finalBytes = new byte[totalBytesCount];
//7. Copy the tag byte at the start of the finalBytes array.
finalBytes[0] = (byte)0x18;
//8. Copy the length bytes from the lengthBytes array to the
// finalBytes array just after the tag byte.
for (int i=0; i < lengthBytes.length; i++)
finalBytes[i+1] = lengthBytes[i];
//9. Copy the content bytes to the finalBytes array
// just after the length bytes.
for (int j=lengthBytesCount+1; j<totalBytesCount; j++)
finalBytes[j] = generalizedTimeContent[j-(lengthBytesCount+1)];
//10. Return the finalBytes array.
return finalBytes;
}//getGeneralizedTimeBytes
concatenateBytes()
這個方法(見清單 8)取兩個字節數組,將第二個數組串接到第一個之後,並返回串接的數組。
因為這個方法取兩個字節數組並返回另一個字節數組,所以它可以自身串聯任意次以串接任意數量的 字節數組。例如, concatenateBytes(byteArray1, concatenateBytes(byteArray2, byteArray3)) 會將 byteArray3 加在 byteArray2 後,再將結果加到 byteArray1 後。
清單 8. concatenateBytes() 方法
public byte[] concatenateBytes (byte[] array1, byte[] array2)
{
byte concatenatedBytes[] = new byte[array1.length + array2.length];
for (int i=0; i<array1.length; i++)
concatenatedBytes[i] = array1[i];
for (int j=array1.length; j<concatenatedBytes.length; j++)
concatenatedBytes[j] = array2[j-array1.length];
return concatenatedBytes;
}//concatenateBytes
getSequenceBytes()
這個方法(見清單 9)生成一個 ASN.1 SEQUENCE 的字節數組表達。它取一個字節數組作為輸入參數 ,將這個字節數組作為 SEQUENCE 的內容,在內容前面加上 SEQUENCE 標簽字節( 0x30 )和長度字節, 並返回完整的 SEQUENCE 結構。
通常, getSequenceBytes() 方法會與 concatenateBytes() 配合使用。一個應用程序將生成 SEQUENCE 中單獨的結構,將各個結構的字節數組表達串接在一起以構成一個數組,並將串接後的數組傳 遞給 getSequenceBytes() 方法,這個方法將返回 SEQUENCE 的完整字節數組表達。
清單 9. getSequenceBytes() 方法
public byte[] getSequenceBytes (byte[] sequenceContents)
{
//1. Declare a byte array named finalBytes, which will
// hold all the bytes of the ASN.1 byte array representation.
byte finalBytes[];
//2. Calculate the number of bytes required to hold the
// contents part of the ASN.1 byte array representation.
int contentBytesCount = sequenceContents.length;
//3. Use the getLengthBytes() method of Listing 3 to author
// the length bytes. Store the length bytes in
// an array named lengthBytes.
byte lengthBytes[] = getLengthBytes(contentBytesCount );
//4. Get the number of bytes in the lengthBytes array.
int lengthBytesCount = lengthBytes.length;
//5. Calculate the number of bytes required to hold the complete
// ASN.1 byte array representation (the sum total of the number
// of tag bytes, length bytes, and content bytes).
// Store the number of bytes in a variable named totalBytesCount.
int totalBytesCount = lengthBytesCount + 1;
//6. Instantiate the finalBytes array to totalBytesCount size.
finalBytes = new byte[totalBytesCount];
//7. Copy the tag byte at the start of the finalBytes array.
finalBytes[0] = (byte)0x30;
//8. Copy the length bytes from the lengthBytes array to the
// finalBytes array just after the tag byte.
for (int i=0; i < lengthBytes.length; i++)
finalBytes[i+1] = lengthBytes[i];
//9. Copy the content bytes to the finalBytes array
// just after the length bytes.
finalBytes = concatenateBytes (finalBytes, sequenceContents);
//10. Return the finalBytes array.
return finalBytes;
}//getsequenceBytes
getTagAndLengthBytes()
這個方法與所討論過的各種 getXXXBytes() 方法非常相象。不過,雖然其中每一個方法生成一個特定 的 ASN.1 通用數據類型,但是 getTagAndLengthBytes() 方法(見清單 10)生成應用程序級和上下文特 定的數據類型。
這個方法取三個參數。第一個參數( tagType )指定標簽類型。如果它的值等於靜態整數 ASN1DataTypes.Context_Specific ,那麼它指定的是一個上下文特定標簽,如果它的值等於 ASN1DataTypes.Application_Type ,那麼它指定的是一個應用程序級標簽。
第二個參數( tagNumber )指定標簽數,而第三個( tagContents )包含了內容字節數組。
getTagAndLengthBytes() 根據輸入參數計算標簽和長度字節的值,將標簽和長度字節加到內容字節前 面,並返回應用程序級或者上下文特定的 ASN.1 結構的完整字節數組表達。
清單 10. getTagAndLengthBytes() 方法
public byte[] getTagAndLengthBytes (int tagType, int tagNumber, byte[] tagContents)
{
//1. Declare a byte array named finalBytes,
// which will hold all the bytes of the ASN.1 byte array representation.
byte finalBytes[];
//2. Declare a byte array named tagAndLengthBytes,
// which will hold the tag and length bytes.
byte tagAndLengthBytes[];
//3. Now calculate the value of the tag byte.
int tag = tagType + tagNumber;
//4. Calculate the number of bytes required to hold
// the contents part of the ASN.1 byte array representation.
int contentBytesCount = tagContents.length;
//5. Use the getLengthBytes() method of Listing 3
// to author the length bytes.
// Store the length bytes in an array named lengthBytes.
byte lengthBytes[] = getLengthBytes (contentBytesCount);
//6. Get the number of bytes in the lengthBytes array.
int lengthBytesCount = lengthBytes.length;
//7. Calculate the number of bytes required to hold
// the tag byte and length bytes
// (the sum total of the number of tag bytes and length bytes).
// Store the number of bytes in a variable named tagBytesCount.
int tagAndLengthBytesCount = 1 + lengthBytesCount;
//8. Instantiate the finalBytes array to tagAndLengthBytesCount size.
tagAndLengthBytes = new byte[tagAndLengthBytesCount];
//9. Copy the tag byte at the start of the tagAndLengthBytes array.
tagAndLengthBytes[0] = (byte)tag;
//10. Copy the length bytes from the lengthBytes array
// to the tagAndLengthBytes array just after the tag byte.
for (int i=0; i < lengthBytes.length; i++)
tagAndLengthBytes[i+1] = lengthBytes[i];
//11. Now instansiate the finalBytes array of size equal to
// the sum total of the number of tag bytes,
// length bytes and content bytes.
finalBytes = new byte [1 + tagAndLengthBytesCount + contentBytesCount ];
//12. Copy the content bytes to the finalBytes array
// just after the length bytes.
finalBytes = concatenateBytes(tagAndLengthBytes, tagContents);
//13. Return the finalBytes array.
return finalBytes;
}//getTagAndLengthBytes
至此就完成了對 ASN1DataTypes 類的生成方法的討論。不過,在開始討論 KerberosClient 如何使用 ASN1DataTypes 方法生成一個 TGT 請求之前,我需要討論如何利用用戶的密碼生成密鑰。在與 Kerberos 服務器進行通信時,會在幾個地方需要這個密鑰。
利用用戶密碼生成密鑰
Kerberos 定義了一種對用戶密碼進行處理以生成一個 密鑰的算法。在獲得 TGT 的過程中 Kerberos 客戶機將用這個密鑰進行解密
對這個基於 J2ME 的 Kerberos 客戶機,我將只支持一種加密算法,即 CBC(密碼分組鏈接 cipher block chaining)模式下的 DES(數據加密標准)。DES 是一個 FIPS(聯邦信息處理標准 Federal Information Processing Standards)發表,它描述了一種將要加密的數據(純文本)和密鑰作為輸入傳 遞給加密過程的加密算法。根據 DES 算法對密鑰和純文本統一處理以生成一個加密的(密文)形式的純 文本數據。
CBC 是一種加密操作模式,其中純文本數據分為同樣大小的數據塊。例如,在 64 位 DES-CBC 加密中 ,數據會分為 8 字節的塊。如果純文數據中的字節數不是您希望每一個塊所具有的字節數的整數倍,就 要在最後一塊中加上適當的數量的字節以使它的大小與其他的塊相同。
然後創建一個與您的塊具有同樣大小的字節數組。這個字節數組稱為 初始矢量(IV)。 Kerveros 規范 定義了所有基於 Kerberos 的應用程序的初始矢量(類似地,其他使用 DES-CBC 的規范定義了它們使用 的 IV 值)。之後,取這個 IV、純文數據的第一塊以及密鑰並根據 DES 算法對它們共同進行處理,以構 成對應於純文本數據第一個數據塊的密文。然後取第一個數據塊的密文形式作為第二個塊的初始矢量並進 行同樣的 DES 加密過程以生成第二個純文本數據塊的密文形式。以這種方式繼續一塊接一塊地生成每一 個塊的密文形式。最後,串接所有密文塊以得到全部純文本數據的密文形式。
因為我只打算在這個 Kerberos 客戶機中支持 DES-CBC,所以我將只討論 DES-CBC 所使用的密鑰的生 成過程,如下所示:
將用戶密碼、KDC 域名和用戶的用戶名串接到一起以構成一個字符串。Kerberos 利用這個串接的字符 串而不僅僅是密碼生成密鑰。為什麼要在密鑰生成中加入域名和用戶名呢?許多用戶會在不同的服務器上 使用同樣的密碼。如果我只使用密碼生成密鑰,那麼一個給定的密碼在所有 Kerberos 服務器上總是會生 成同樣的密鑰。因而,如果一個黑客可以取得用戶在一台 Kerberos 服務器上的密鑰,那麼,他就可以在 所有 Kerberos 服務器上使用同一個密鑰。另一方面,如果我加入了域名和用戶名,那麼一個受到這種攻 擊的密鑰將只會侵害特定的域。
得到第 1 步中串接的字符串的字節數組表達。
統計第 2 步中字節數組中的字節數。在這個字節串的後面附加適當數量的零字節以使它成為 8 的整 數倍。例如,如果這個字節數組包含 53 個字節,那麼就在這個字節數組的最後附加三個字節使它具有 56 個字節。
將第 3 步中附加了字節後的字節數組分為大小相同的塊,每一塊有 8 個字節。
每隔一個塊倒轉塊的位順序。換句話說,第一塊保持不變,第二塊的位順序應該倒轉,第三塊應保持 不變,第中塊的位順序應倒轉,以此類推。
取第一個(未改變的)塊並與第二個(倒轉的)塊進行每一位的 exclusive OR 。然後將第一次 exclusive OR 操作得到的結果與第三個(未改變的)塊進行另一次 exclusive OR 操作。繼續 exclusive OR 操作直到完成了所有塊。所有 exclusive OR 操作的最後結果是一個 8 字節長的塊。
修正在第 6 步中得到的 8 字節塊的奇偶性。每一塊的最低有效位保留為奇偶位。統計 8 字節塊中每 字節中的 1 的個數,如果 1 的個數為偶數,那麼就設置最低位為 1 使它成為奇數。例如,如果一個字 節的值為 00000000 ,那麼就要將它改為 00000001 。如果一個字節中 1 的個數已經為奇數,那麼就將 它的最低位設置為零。例如,如果一個字節為 00000010 ,那麼就不需要為修正其奇偶性做任何改變。
DES 定義了一些弱的、因而不適合用於加密的密鑰。我們的密鑰生成過程的第八步是要檢查奇偶修正 後的字節數組是否是一個弱的密鑰。如果是的話,就要用 0xf0 ( 11110000 )與奇偶修正過的 8 字節 塊進行 exclusive OR 。如果奇偶修正得到的不是弱密鑰,那麼就不需要進行這種 exclusive OR 操作。 經過這種弱密鑰處理的字節數組是一個臨時密鑰。
現在我要使用這個臨時密鑰以 DES-CBC 算法加密第 3 步中得到的附加後的字節數組。這個臨時密鑰 同時作為密鑰的值和 DES-CBC 加密的初始矢量的值。回想在前面的討論中說過,CBC 要求密文塊鏈接。 第 9 步的結果是最後 8 字節塊的加密結果(放棄所以以前的密文塊)。因此,這一步的結果是另一個 8 字節塊。
現在我修正第 9 步產生的 8 字節塊中的每一個字節的奇偶性。在上面第 7 步中我解釋了奇偶性修正 。
現在再次檢查第 10 步得到的經過奇偶修正的 8 字節塊是不是弱密鑰(就像在第 8 步中所做的那樣 )。
第 11 步的結果是一個 Kerveros 客戶機可以用來與 Kerberos 服務器進行通信的密鑰。
現在看一下清單 11 中的 KerberosKey 類。這個類的 generateKey() 方法實現了上面描述的 11 步 密鑰生成算法。
清單 11. KerberosKey 類
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.generators.DESKeyGenerator;
import org.bouncycastle.crypto.params.DESParameters;
import org.bouncycastle.crypto.engines.DESEngine;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
public class KerberosKey
{
private CBCBlockCipher cipher;
private KeyParameter kp;
private ParametersWithIV iv;
private byte kerberosKey[];
private ASN1DataTypes asn1;
private String principalID;
public KerberosKey(String userName, String password, String realmName)
{
kerberosKey = new byte[8];
kerberosKey = generateKey (password, realmName, userName);
}//KerberosKey
public byte[] generateKey (String password, String realmName, String userName)
{
//Step 1:
String str = new String (password + realmName + userName);
byte secretKey [] = new byte[8];
//Step 2:
byte encodedByteArray[] = encodeString(str);
//Step 3:
byte paddedByteArray[] = padString(encodedByteArray);
//Step 4:
int i = paddedByteArray.length / 8;
//Step 5:
for(int x=0; x<i; x++)
{
byte blockValue1[] = new byte [8];
System.arraycopy (paddedByteArray, x*8, blockValue1, 0, 8);
if(x % 2 == 1)
{
byte tempbyte1 = 0;
byte tempbyte2 = 0;
byte blockValue2[] = new byte [8];
for (int y=0; y<8; y++)
{
tempbyte2 = 0;
for (int z=0; z<4; z++)
{
tempbyte2 = (byte) ((1<<(7-z)) & 0xff);
tempbyte1 |= (blockValue1[y] & tempbyte2) >>> (7-2*z);
tempbyte2 = 0;
}
for (int z=4; z<8; z++)
{
tempbyte2 = (byte) ((1<<(7-z)) & 0xff);
tempbyte1 |= (blockValue1[y] & tempbyte2) << (2*z-7);
tempbyte2 = 0;
}
blockValue2 [7-y] = tempbyte1;
tempbyte1 = 0;
}//outer for
for (int a = 0; a <8; a ++)
blockValue2[a] = (byte) ((((byte)blockValue2[a] & 0xff) >>> 1) & 0xff);
System.arraycopy(blockValue2, 0, blockValue1, 0, blockValue2.length);
}//if(x % 2 == 1)
for (int a = 0; a <8; a ++)
blockValue1[a] = (byte) ((((byte)blockValue1[a] & 0xff) << 1) & 0xff);
//Step 6:
for (int b = 0; b <8; b ++)
secretKey[b] ^= blockValue1[b];
}// for
//Step 7:
secretKey= setParity(secretKey);
//Step 8:
if (isWeakKey(secretKey))
secretKey = getStrongKey(secretKey);
//Step 9:
secretKey = getFinalKey(paddedByteArray, secretKey);
//Step 10:
secretKey = setParity(secretKey);
if (isWeakKey(secretKey))
secretKey = getStrongKey(secretKey);
return secretKey;
}//generateKey
public byte[] getFinalKey (byte data[], byte key[])
{
//The cipher instance with DES algo and CBC mode.
cipher = new CBCBlockCipher( new DESEngine());
kp = new KeyParameter(key);
iv = new ParametersWithIV (kp, key);
cipher.init(true, iv);
byte encKey[] = new byte[data.length];
byte ivBytes[] = new byte[8];
for(int x = 0; x < data.length / 8; x ++)
{
cipher.processBlock(data, x*8, encKey, x*8);
System.arraycopy(encKey, x*8, ivBytes, 0, 8);
iv = new ParametersWithIV (kp, ivBytes);
cipher.init (true, iv);
}
return ivBytes;
}//getFinalKey
public byte[] setParity (byte byteValue[])
{
for(int x=0; x<8; x++)
byteValue[x] = parityValues[byteValue[x] & 0xff];
return byteValue;
}
// Checks weak key
public boolean isWeakKey (byte keyValue[])
{
byte weakKeyValue[];
for(int x = 0; x < weakKeyByteValues.length; x++)
{
weakKeyValue = weakKeyByteValues[x];
if(weakKeyValue.equals(keyValue))
return true;
}
return false;
}//isWeakKey
// Corrects the weak key by exclusive OR with 0xf0 constant.
public byte[] getStrongKey(byte keyValue[])
{
keyValue[7] ^= 0xf0;
return keyValue;
}//checkWeakKey
// Encodes string with ISO-Lation encodings
public byte[] encodeString (String str)
{
byte encodedByteArray[] = new byte[str.length()];
try
{
encodedByteArray = str.getBytes("8859_1");
}
catch(java.io.UnsupportedEncodingException ue)
{
}
return encodedByteArray;
}//encodeString
//This method pads the byte[] with ASCII nulls to an 8 byte boundary.
public byte[] padString (byte encodedString[])
{
int x;
if(encodedString.length < 8)
x = encodedString.length;
else
x = encodedString.length % 8;
if(x == 0)
return encodedString;
byte paddedByteArray[] = new byte[(8 - x) + encodedString.length];
for(int y = paddedByteArray.length - 1; y > encodedString.length - 1; y--)
paddedByteArray[y] = 0;
System.arraycopy(encodedString, 0, paddedByteArray, 0, encodedString.length);
return paddedByteArray;
}//padString
//returns the secret key bytes.
public byte[] getKey()
{
return this.kerberosKey;
}//getKey()
private byte weakKeyByteValues[][] = {
{(byte)0x10, (byte)0x10, (byte)0x10, (byte)0x10,
(byte)0x10, (byte)0x10, (byte)0x10, (byte)0x1},
{(byte)0xfe, (byte)0xfe, (byte)0xfe, (byte)0xfe,
(byte)0xfe, (byte)0xfe, (byte)0xfe, (byte)0xfe},
{(byte)0x1f, (byte)0x1f, (byte)0x1f, (byte)0x1f,
(byte)0x1f, (byte)0x1f, (byte)0x1f, (byte)0x1f},
{(byte)0xe0, (byte)0xe0, (byte)0xe0, (byte)0xe0,
(byte)0xe0, (byte)0xe0, (byte)0xe0, (byte)0xe0},
{(byte)0x1f, (byte)0xe0, (byte)0x1f, (byte)0xe0,
(byte)0x1f, (byte)0xe, (byte)0x01, (byte)0xfe},
{(byte)0xfe, (byte)0x01, (byte)0xfe, (byte)0x01,
(byte)0xfe, (byte)0x01, (byte)0xfe, (byte)0x01},
{(byte)0x1f, (byte)0xe0, (byte)0x1f, (byte)0xe0,
(byte)0x0e, (byte)0xf1, (byte)0x0e, (byte)0xf1},
{(byte)0xe0, (byte)0x1f, (byte)0xe0, (byte)0x1f,
(byte)0xf1, (byte)0x0e, (byte)0xf1, (byte)0x0e},
{(byte)0x1e, (byte)0x00, (byte)0x1e, (byte)0x00,
(byte)0x1f, (byte)0x10, (byte)0x1f, (byte)0x1},
{(byte)0xe0, (byte)0x01, (byte)0xe0, (byte)0x01,
(byte)0xf1, (byte)0x01, (byte)0xf1, (byte)0x01},
{(byte)0x1f, (byte)0xfe, (byte)0x1f, (byte)0xfe,
(byte)0x0e, (byte)0xfe, (byte)0x0e, (byte)0xfe},
{(byte)0xfe, (byte)0x1f, (byte)0xfe, (byte)0x1f,
(byte)0xfe, (byte)0x0e, (byte)0xfe, (byte)0x0e},
{(byte)0x11, (byte)0xf0, (byte)0x11, (byte)0xf0,
(byte)0x10, (byte)0xe0, (byte)0x10, (byte)0xe},
{(byte)0x1f, (byte)0x01, (byte)0x1f, (byte)0x01,
(byte)0x0e, (byte)0x01, (byte)0x0e, (byte)0x01},
{(byte)0xe0, (byte)0xfe, (byte)0xe0, (byte)0xfe,
(byte)0xf1, (byte)0xfe, (byte)0xf1, (byte)0xfe},
{(byte)0xfe, (byte)0xe0, (byte)0xfe, (byte)0xe0,
(byte)0xfe, (byte)0xf1, (byte)0xfe, (byte)0xf1}
};
//Parity values for all possible combinations
//256 entries
private byte parityValues[] = {
1, 1, 2, 2, 4, 4, 7, 7, 8, 8,
11, 11, 13, 13, 14, 14, 16, 16, 19, 19,
21, 21, 22, 22, 25, 25, 26, 26, 28, 28,
31, 31, 32, 32, 35, 35, 37, 37, 38, 38,
41, 41, 42, 42, 44, 44, 47, 47, 49, 49,
50, 50, 52, 52, 55, 55, 56, 56, 59, 59,
61, 61, 62, 62, 64, 64, 67, 67, 69, 69,
70, 70, 73, 73, 74, 74, 76, 76, 79, 79,
81, 81, 82, 82, 84, 84, 87, 87, 88, 88,
91, 91, 93, 93, 94, 94, 97, 97, 98, 98,
100, 100, 103, 103, 104, 104, 107, 107, 109, 109,
110, 110, 112, 112, 115, 115, 117, 117, 118, 118,
121, 121, 122, 122, 124, 124, 127, 127, -128, -128,
-125, -125, -123, -123, -122, -122, -119, -119, -118, -118,
-116, -116, -113, -113, -111, -111, -110, -110, -108, -108,
-105, -105, -104, -104, -101, -101, -99, -99, -98, -98,
-95, -95, -94, -94, -92, -92, -89, -89, -88, -88,
-85, -85, -83, -83, -82, -82, -80, -80, -77, -77,
-75, -75, -74, -74, -71, -71, -70, -70, -68, -68,
-65, -65, -63, -63, -62, -62, -60, -60, -57, -57,
-56, -56, -53, -53, -51, -51, -50, -50, -48, -48,
-45, -45, -43, -43, -42, -42, -39, -39, -38, -38,
-36, -36, -33, -33, -32, -32, -29, -29, -27, -27,
-26, -26, -23, -23, -22, -22, -20, -20, -17, -17,
-15, -15, -14, -14, -12, -12, -9, -9, -8, -8,
-5, -5, -3, -3, -2, -2
};
}//KerberosKey class
我已經用注釋標記了清單 11 中 generateKey() 方法中那些代碼行,以幫助您將算法的各個步驟與 J2ME 代碼中的相應行對應起來。編碼細節中真正需要解釋的一點是第 9 步,在這裡我實際執行了 DES- CBC 加密。
看一下清單 11 中 generateKey() 方法中的那些行代碼,它們用注釋標記為第 9 步。它是一個對名 為 getFinalKey() 的方法的調用,這個方法實現了第九步並取兩個參數。第一個參數( data )是第 3 步的附加操作得到的字節數組,而第二個參數( key )是作為第 8 步的結果得到的臨時密鑰。
DESEngine 和 CBCBlockCipher 類進行實際的加密操作。這些類是 Bouncy Castle 組的 J2ME 平台開 放源代碼加密實現的一部分。Bouncy Castle 的實現可以免費得到,並可用於任何目的,只要您在發布時 加入許可證信息。您將需要下載 Bouncy Castle 類並遵照它所附帶的設置指示才能使用本文的示例代碼 。清單 11 中的 KerberosKey 類包含在 Kerberos 類中使用 Bouncy Castle 類時需要的所有 import 語 句。
現在看一下在清單 11 中的 getFinalKey() 方法中發生了什麼事情。我首先實例化了 DESEngine 類 ,這個類實現了 DES 加密算法。然後,我將這個 DESEngine 對象傳遞給構造函數 CBCBlockCipher 以創 建一個名為 cipher 的 CBCBlockCipher 對象。這個 cipher 對象將執行實際的 DES-CBC 操作。
然後我通過向名為 KeyParameter 的類的構造函數傳遞一個 key 參數創建一個名為 kp 的對象。這個 KeyParameter 類也是 Bouncy Castle 的加密庫的一部分。 kp 對象現在包裝了密鑰,所以在需要指定密 鑰時我將傳遞這個對象。
下一步是創建另一個名為 iv 的對象。這個對象是另一個名為 ParameterWithIV 的 Bouncy Castle 類的實例。 ParameterWithIV 構造函數取兩個參數。第一個是包裝了密鑰的 kp 對象。第二個是初始矢 量字節數組。因為我必須用密鑰作為初始矢量,所以將密鑰作為初始矢量字節數組傳遞。
iv 對象現在包裝了密鑰以及初始矢量,所以我在需要指定密鑰和初始矢量時傳遞這個對象。
下一步是調用 cipher 對象的 init() 方法初始化這個對象。這個方法取兩個參數。第一個是布爾類 型,在需要初始化一個密碼進行加密時傳遞 true ,在希望進行解碼時傳遞 false 。第二個是包裝了密 鑰和初始矢量的 iv 對象,
現在可以進行密文塊鏈接了。我聲明了一個名為 ivBytes 的字節數組,它將包含密碼塊鏈接每一步的 初始矢量字節。一個 for 循環將連續調用 cipher 對象的 processBlock() 方法。 processBlock() 方 法一次處理一個數據塊。
processBlock() 方法取四個參數。第一個是輸入數組( data ),第二個是字節數組中的偏移。 processBlock() 方法從這個偏移值開始處理塊輸入。第三個參數是輸出數組的名字,第四個是輸出數組 中的偏移。
for 循環調用 processBlock() 方法一次處理一個塊。這個方法一次處理一塊並將輸出(加密的結果 )儲存在 ivBytes 數組中。之後,我通過向 ParametersWithIV 構造函數傳遞 ivBytes 數組創建一個新 的 iv 對象( ParametersWithIV 類的一個實例)。然後我用新的 iv 對象重新初始化這個密碼。於是循 環可以用與第一塊的結果相等的初始矢量處理下一塊。
循環退出時,我只是返回最後一個數據塊的加密結果,這就是密鑰生成過程第 9 步的結果。
生成 TGT 請求
到目前為止,我討論了 ASN1DataTypes 類的底層方法並實現了利用用戶的密碼生成密鑰的算法。現在 可以展示 KerberosClient 類如何利用這些底層細節了。
看一下清單 12,它顯示了 getTicketResponse() 方法的實現。這個方法屬於 KerberosClient 類。
getTicketResponse() 方法的基本目的是生成一個對 Kerberos 票據(一個 TGT 或者服務票據)的請 求、向 Kerberos 服務器發送票據請求、從服務器得到響應、並將響應返回給調用應用程序。在本文中, 我將只描述生成 TGT 請求的過程。本系列的下一篇文章將展示設置 KDC 服務器、向 KDC 發送請求、得 到響應並對它進行處理的步驟。
清單 12. getTicketResponse() 方法
import org.bouncycastle.crypto.digests.MD5Digest;
public class KerberosClient extends ASN1DataTypes
{
static long seed = System.currentTimeMillis();
private String kdcServiceName = "krbtgt";
private KerberosKey krbKey;
private String userName;
private String password;
private String realmName;
public KerberosClient(String userName, String password, String realmName)
{
krbKey = new KerberosKey(userName, password, realmName);
this.userName = userName;
this.password = password;
this.realmName = realmName;
}//KerberosClient
public byte[] getTicketResponse ()
{
byte pvno[] = getTagAndLengthBytes(ASN1DataTypes.Context_Specific,
1, getIntegerBytes(5));
byte msg_type[] = getTagAndLengthBytes(ASN1DataTypes.Context_Specific,
2, getIntegerBytes(10));
byte noOptions[] = new byte [5];
byte kdc_options[] = getTagAndLengthBytes(ASN1DataTypes.Context_Specific,
0, getBitStringBytes(noOptions));
byte generalStringSequence[] = getSequenceBytes(
getGeneralStringBytes (userName));
byte name_string[] = getTagAndLengthBytes(ASN1DataTypes.Context_Specific,
1, generalStringSequence);
byte name_type[] = getTagAndLengthBytes(ASN1DataTypes.Context_Specific,
0, getIntegerBytes(ASN1DataTypes.NT_PRINCIPAL));
byte principalNameSequence [] = getSequenceBytes
(concatenateBytes (name_type, name_string));
byte cname[] = getTagAndLengthBytes (ASN1DataTypes.Context_Specific,
1, principalNameSequence);
byte realm[] = getTagAndLengthBytes (ASN1DataTypes.Context_Specific,
2, getGeneralStringBytes (realmName));
byte sgeneralStringSequence[] =
concatenateBytes(getGeneralStringBytes(kdcServiceName),
getGeneralStringBytes (realmName));
byte sname_string[] = getTagAndLengthBytes(ASN1DataTypes.Context_Specific,
1, getSequenceBytes(sgeneralStringSequence));
byte sname_type[] = getTagAndLengthBytes(ASN1DataTypes.Context_Specific,
0, getIntegerBytes(ASN1DataTypes.NT_UNKNOWN));
byte sprincipalNameSequence [] = getSequenceBytes
(concatenateBytes (sname_type, sname_string));
byte sname[] = getTagAndLengthBytes (ASN1DataTypes.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)
)
)
)
)
)
)
);
byte ticketRequest[] = getTagAndLengthBytes(
ASN1DataTypes.Application_Type,
10,
getSequenceBytes(
concatenateBytes(
pvno,
concatenateBytes
(msg_type,req_body)
)
)
);
return ticketRequest;
}
public byte[] getRandomNumber ()
{
String userData = userName + password;
byte secretKey[] = getByteArray(System.currentTimeMillis() * 6 + seed);
seed = seed / 5;
int userDataHash = userData.hashCode() * 5;
byte numData[] = new String(String.valueOf(userDataHash)).getBytes();
byte numBytes[]= krbKey.getFinalKey(numData, secretKey);
byte randomNum []= new byte[4];
int j=1;
for (int i=0; i<4; i++)
{
randomNum[i]= numBytes[i+j];
j++;
}
return randomNum;
}//getRandomNumber
//It is a helper method used to generate the random number bytes structure.
public byte[] getIntegerBytes (byte[] byteContent)
{
byte finalBytes[];
int contentBytesCount = byteContent.length;
byte lengthBytes[] = getLengthBytes(contentBytesCount );
int lengthBytesCount = lengthBytes.length;
int integerBytesCount = lengthBytesCount + contentBytesCount + 1;
finalBytes = new byte[integerBytesCount];
finalBytes[0] = (byte)0x02;
for (int i=0; i < lengthBytes.length; i++)
finalBytes[i+1] = lengthBytes[i];
for (int j=lengthBytesCount+1; j<integerBytesCount; j++)
finalBytes[j] = byteContent[j-(lengthBytesCount+1)];
return finalBytes;
}//getIntegerBytes
// Converts a long into a byte array.
public byte[] getByteArray (long l)
{
byte byteValue[] = new byte[8];
for(int x=0; x<8; x++)
byteValue[x] = (byte)(int)(l >>> (7 - x) * 8 & 255L);
return byteValue;
}
}//KerberosClient class
在本系列的 第一篇文章 對圖 2、清單 1 和表 2 的討論中我討論過 TGT 請求的結構。回想在那裡的 討論中,TGT 請求包含四個數據字段: pvno 、 msg-type 、 padata 和 req-body 。生成 pvno 和 msg-type 字段非常簡單,因為這兩個字段分別只包含一個整數(如在 第一篇文章 中“請求 TGT”一節 中提到的, pvno 為 5, msg-type 為 10)。
您只需要調用 getIntegerBytes() 方法,向這個方法傳遞這個整數值。 getIntegerBytes() 方法返 回以 ASN.1 字節數組表達的 INTEGER 結構,您將它傳遞給 getTagAndLengthBytes() 方法。這個方法將 返回 pvno 或者 msg-type 字段的完整 ASN.1 表達。這就是我在清單 12 中的 getTicketResponse() 方 法的開始時生成 pvno 和 msg-type 字段的方法。
在生成 pvno 和 msg-type 字段後,下一步就是生成 padata 字段。這個字段是可選的。大多數 KDC 服務器有一個設置選項,可以對單獨的客戶機進行配置。系統管理員可以將 Kerberos 服務器設置為特定 客戶可以發送不包括 padata 字段的 TGT 請求。
為了減輕在資源有限的 J2ME 設備上的處理負擔,我假定電子銀行有一個允許無線移動用戶發送不帶 padata 字段的 TGT 請求的 Kerberos 服務器(並且我將在本系列的下一篇文章中展示如何設置 Keberos 服務器使它具有這種行為)。因此我將在要生成的 TGT 請求中略去 padata 字段。所以,在生成 pvno 和 msg-type 字段後,我就直接開始生成 req-body 結構,這需要幾步。
生成請求正文
在清單 12 的 getTicketResponse() 方法中,我的請求正文( req-body 結構)生成策略是生成結構 的所有單獨的子字段,然後將它們串接到一起並包裝到一個 SEQUENCE 中以構成請求正文。
回想在 第一篇文章 圖 2 的討論中, req-body 的子字段有(去掉了一些可選字段):
kdc-options
cname
realm
sname
till
nonce
etype
我將按它們在上面列表中的順序生成這些字段。因此,第一項任務是生成 kdc-options 字段。
因為我不想使用任何 KDC 選項,所以我不需要對生成 kdc-options 字段進行任何邏輯處理。我只是 使用一個全為零的 5 字節數組作為其內容。看一下清單 12 的 getTicketResponse() 方法中 byte noOptions[] = new byte [5]; 這一行。這個方法實例化一個名為 noOptions 的 5 字節數組,它初始化 為五個零。
下一行( byte kdc_options[] = getTagAndLengthBytes (ASN1DataTypes.Context_Specific, 0, getBitStringBytes(noOptions)) )執行兩項任務:
它首先向 getBitStringBytes() 方法傳遞 noOptions 字節數組,它返回用 ASN.1 的位字符串表達的 5 個零。
然後它將位字符串傳遞給 getTagAndLengthBytes() 方法,這個方法返回 kdc-options 字段的完整 ASN.1 字節數組表達。
下一步是生成 cname 結構。在 第一篇文章 清單 1 的討論中說過, cname 字段的類型為 type cname 。這種數據類型是兩個字段 - 即 name-type 和 name-string ―― 的 SEQUENCE 。 name-type 字段是用一個 INTEGER 構造的。 name-string 字段是 GeneralString s 的一個 SEQUENCE 。
因此,為了生成 cname 結構,我必須遵循清單 12 的 getTicketResponse() 方法中的幾個步驟:
調用 getGeneralStringBytes() 方法,同時傳遞客戶的用戶名。 getGeneralStringBytes() 方法將 返回客戶的用戶名的 GeneralString 表達。
向 getSequenceBytes() 方法傳遞 GeneralString ,這個方法會在 GeneralString 前面附加 SEQUENCE 字節並返回包含客戶的用戶名字符串的 SEQUENCE 的 ASN.1 表達。
byte generalStringSequence[] = getSequenceBytes (getGeneralStringBytes (userName)); 這一 行執行這前兩步。
調用 getTagAndLengthBytes() 方法,傳遞 SEQUENCE 字節作為其內容。 getTagAndLengthBytes() 方法會在 SEQUENCE 前面附加 name-string 標簽字節(上下文特定的標簽數字 0)以及長度字節,並返 回完整的 name-string 結構。
byte name_string[] = getTagAndLengthBytes (ASN1DataTypes.Context_Specific, 1, generalStringSequence); 這一行執行這一步。
生成 PrincipalName 的 name-type 部分。 name-type 部分只包含一個 INTEGER ,它標識了用戶名 的類型。Kerbros 允許幾種類型的名字(用戶名、惟一標識等等)。對於這個基於 J2ME 的 Kerberos 客 戶機,我感興趣的惟一名稱類型是用戶名,它的名稱類型標識是 1。因此,我將首先構造一個 INTEGER ,然後向 getTagAndLengthBytes() 方法傳遞這個 INTEGER 字節。這個方法生成 PrincipalName 的完整 name-type 部分。清單 12 中 byte name_type[] = getTagAndLengthBytes (ASN1DataTypes.Context_Specific, 0, getIntegerBytes (ASN1DataTypes.NT_PRINCIPAL)); 這一行執 行這項任務。
將 PrincipalName 的 name-type 和 name-string 部分串接到一起,然後在串接字節數組前面附加 SEQUENCE 字節。 byte principalNameSequence [] = getSequenceBytes (concatenateBytes (name_type, name_string)); 一行執行這項任務。
在上面第 5 步的 SEQUENCE 前面附加 cname 標簽字節(上下文特定的標簽數 1)和長度字節。這樣 就得到了完整的 cname 結構。 byte cname[] = getTagAndLengthBytes (ASN1DataTypes.Context_Specific, 1, principalNameSequence); 一行執行這項任務。
上述 6 步策略就可以生成完整的 cname 結構。
我的下一步是生成 realm 字段,它的類型為 GeneralString 。生成 realm 字段的策略如下:
用 getGeneralStringBytes() 方法調用生成 GeneralString 。
連同 getTagAndLengthBytes() 方法一起傳遞 GeneralString 字節,它會返回 realm 字段的完整字 節字符串表達。
清單 12 中 byte realm[] = getTagAndLengthBytes (ASN1DataTypes.Context_Specific, 2, getGeneralStringBytes (realmName)); 這一行進行這兩個方法調用。
下一項任務是生成 sname 字段,它是 PrincipalName 數據類型。我已經在上面討論 cname 字段時描 述過了生成 PrincipalName 數據結構的策略。
在 sname 字段後,我需要生成 till 字段,它指定我所請求的票據的失效時間。對於這個基於 J2ME 的 Kerberos 客戶機,我不想指定票據的任何特定失效時間,我只希望由 KDC 服務器根據服務器的策略 發布具有標准失效時間的票據。因此,我總是發送硬編碼的日期(1970 年 1 月 1 日)作為 till 字段 的值。我所選擇的日期是過去日期,這表明我不希望為請求的票據指定一個失效時間。
till 字段為 KerberosTime 類型,它遵循 GeneralizedTime 通用數據類型。生成 KerberosTime 結 構的過程是首先調用 getGeneralizedTimeBytes() 方法並與方法調用同時傳遞時間字符串。例如, etGeneralizedTimeBytes(new String("19700101000000Z") 方法調用會返回 1970 年 1 月 1 日的 GeneralizedTime 結構。
有了 GeneralizedTime 字節數組後,我可以將它傳遞給 getTagAndLengthbytes() 方法調用,它會生 成 till 參數的完整字節數組。清單 12 中 getTicketResponse() 方法的 byte till[] = getTagAndLengthBytes (ASN1DataTypes.Context_Specific, 5, getGeneralizedTimeBytes (new String("19700101000000Z").getBytes())); 這一行生成完整的 till 結構。
下面,需要生成 nonce 字段,它包裝了一個隨機數作為一個整數。我首先生成一個隨機數,然後生成 這個隨機數的字節數組表達,最後調用 getTagAndLengthBytes() 方法,它生成 nonce 字段的完整結構 。
在 req-body 字段中,還必須生成的最後一個結構是 etype 字段,這是一個 INTEGER 序列。 SEQUENCE 中的每個 INTEGER 指定客戶機支持的。