快捷搜索:

用 Kerberos 为 J2ME 应用程序上锁,第 2 部分: 生成

在本系列的 上一篇文章 中,我先容了一个应用 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)

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 >> (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 >> (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

清单 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

清单 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

清单 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

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

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

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

至此就完成了对 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>> (7-2*z);

tempbyte2 = 0;

}

for (int z=4; z>> 1) & 0xff);

System.arraycopy(blockValue2, 0, blockValue1, 0, blockValue2.length);

}//if(x % 2 == 1)

for (int a = 0; a  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 发送哀求、获得相应并对它进行处置惩罚的步骤。

在本系列的 第一篇文章 对图 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 指定客户机支持的

您可能还会对下面的文章感兴趣: