LTPA Cookie原理

1. 什么是LTPA?

Lightweight Third-Party Authentication (LTPA)是IBM Websphere和Domino产品中使用单点登录技术。当服务器配置好LTPA认证方式,用户通过浏览器成功登录后,服务器会自动发送一个session cookie给浏览器;此cookie中包含一个LTPA Token。

2. WebSphere部分

本部分描述适用于已实施WebSphere系列产品应用和Domino平台应用,或WebSphere与Domino之间已完成单点登录。在这样的环境中与构异系统实现单点登录。

一个通过有效的LTPA Cookie能够在同一个认证域中所有服务器被自动认证。此Cookie中包含认证信息和时间戳。这些信息通过共享的3DES Key进行了bis 加密。使用公共密钥/私有密钥进行签名。
LTPA Cookie通过3DES密钥使用DESede/ECB/PKCS5P进行加密。此密钥也是采用DESede/ECB/PKCS5P进行加密,加密后再使用提供的密码再进行SHA Hash,生成24个字节的密钥,再进行Base64编码。
 
如果你要解析LTPA Token,先得使用Key的密码,生成3DES密钥;再使用3DES密钥解密Token Cookie。也可以使用公共/私有密钥来签名或验证LTPA Cookie。

2.1 WebSphere LTPA 生成原理

首先,这个 cookie 由以下部分组成,以%进行分隔:
  • 用户信息,格式为u:user\:<RealmName>/<UserDN>,如:u:user\:VGOLiveRealm/CN=squallzhong,O=VGOLive Technology
  • 过期时间
  • 签名信息,如:
    u:user\:VGOLiveRealm/CN=squallzhong,O=VGOLive Technology%1301558320666%Cy2CAeru5kEElGj0hrvYsKW2ZVsvvcu6Un573aeX55OO4G3EMYWc0e/ZbqDp1z7MS+dLzniuUH4sYWCMpnKdm7ZGabwmV+WcraBl+y+yzwcl722gHVMOnDZAW7U3jEay9Tk2yG4yXkMWU+617xndpVxke2jtS5wIyVVM3q7UDPw=

2.2 异构系统所需信息

从WebSphere系统中导出ltpa的key文件,使用文本文件打开,如:

com.ibm.websphere.CreationDate=Thu Mar 31 11\:08\:09 GMT+08\:00 2011 com.ibm.websphere.ltpa.version=1.0 com.ibm.websphere.ltpa.3DESKey=7dH4i81YepbVe+gF9XVUzE4C1Ca5g6A4Q69OFobJV9g\= com.ibm.websphere.CreationHost=wasserver com.ibm.websphere.ltpa.PrivateKey=N3bnOE1IbiXNsHXxxemC98iiCnmtw3JUuQvdFjEyh9r2gu+FlQRmG8xp5RBltqc6raI4EgYFhTr+t5/tmRQrFqfNKgvujeJZODeCspohi1V4C0qit7DOoqD9xOOn9Rzdb4PIuJM3ekwuBiZZYTYu7q0TANDygc7VbmwoD3xMPCk5svyvFJ/VshPyg5f7Q+VNM8dlIitU4gK9Qp8VZEqjGoXsYYzYYTQgnwAVtR2GfZtXKlf24EPXSkgUz9j8FwTvcylcKwjS22d6eVjciyAzInnxPqxE2iMRPEFDatHZFox3flsqBswmeDQrAGv8zIiffgP1DLKdjozUyAG+50v97xx7u1RtIrB4B01ik8DuLhw\= com.ibm.websphere.ltpa.Realm=VGOLiveRealm com.ibm.websphere.ltpa.PublicKey=AM04If2+ElGSyVRF0ZEesgvC59vGw8gSIfptjfoXj8iz4C7Ip/KVAu2PDkpQi3LUN/FgVF696tmsegBThks9rmMMHzOix/vGP2721dQZKbD7plOLdWtiY2AYZChsBVkOF26DfiWJ6euxD+a+KNcrfDnu2AXRC/tKncIUJV4LbeJdAQAB
  • 所使用的DNS域,如:vgolive.com
  • 过期时间(分钟),如:30
  • LTPA 3DESKey 密钥及密钥的保护密码
  • Base DN,如:O=VGOLive Technology或DC=vgolive,DC=com
注:
  • 在3DESKey中的反斜杠只是为了在JAVA中可解释等于号,所以正确的3DESKey为 7dH4i81YepbVe+gF9XVUzE4C1Ca5g6A4Q69OFobJV9g=
  • 用户名可以通过Base Dn验证字进行拼接,如异构系统中用户名为SquallZhong,相应在Domino中的DN就是CN=SquallZhong,O=DigiWin。此类情况仅限于LDAP中的CN与异构系统中的用户名一致。如果不一致,需要异构系统做用户对应

2.3 实现

Base64解码/编码所需Jar包: apache-commons-codec-1.3.jar以上
SHA-1的校验使用 java.security.MessageDigest

2.3.1 解析

以下代码为解析从WebSphere或Domino发送过来的LTPAToken Cookie以Java为例:

 
01
02         // LTPA 3DES 密钥
03         String ltpa3DESKey = "7dH4i81YepbVe+gF9XVUzE4C1Ca5g6A4Q69OFobJV9g=" ;
04         // LTPA 密钥密码
05         String ltpaPassword = "Passw0rd" ;
06         try  {
07             // 获得加密key
08             byte [] secretKey = getSecretKey(ltpa3DESKey, ltpaPassword);
09             // 使用加密key解密ltpa Cookie
10             String ltpaPlaintext = new  String(decryptLtpaToken(tokenCipher,
11                     secretKey));
12             displayTokenData(ltpaPlaintext);
13         } catch  (Exception e) {
14             System.out.println( "Caught inner: "  + e);
15         }
16
17     //获得安全Key
18     private  static  byte [] getSecretKey(String ltpa3DESKey, String password)
19             throws  Exception {
20         // 使用SHA获得key密码的hash值
21         MessageDigest md = MessageDigest.getInstance( "SHA" );
22         md.update(password.getBytes());
23         byte [] hash3DES = new  byte [ 24 ];
24         System.arraycopy(md.digest(), 0 , hash3DES, 0 , 20 );
25         // 使用0替换后4个字节
26         Arrays.fill(hash3DES, 20 , 24 , ( byte ) 0 );
27         // BASE64解码 ltpa3DESKey
28         byte [] decode3DES = Base64.decodeBase64(ltpa3DESKey.getBytes());
29         // 使用key密码hash值解密已Base64解码的ltpa3DESKey
30         return  decrypt(decode3DES, hash3DES);
31     }
32     //解密LtpaToken
33     public  static  byte [] decryptLtpaToken(String encryptedLtpaToken, byte [] key)
34             throws  Exception {
35         // Base64解码LTPAToken
36         final  byte [] ltpaByteArray = Base64.decodeBase64(encryptedLtpaToken
37                 .getBytes());
38         // 使用key解密已Base64解码的LTPAToken
39         return  decrypt(ltpaByteArray, key);
40     }
41     // DESede/ECB/PKC5Padding解方法
42     public  static  byte [] decrypt( byte [] ciphertext, byte [] key)
43             throws  Exception {
44         final  Cipher cipher = Cipher.getInstance( "DESede/ECB/PKCS5Padding" );
45         final  KeySpec keySpec = new  DESedeKeySpec(key);
46         final  Key secretKey = SecretKeyFactory.getInstance( "TripleDES" )
47                 .generateSecret(keySpec);
48         cipher.init(Cipher.DECRYPT_MODE, secretKey);
49         return  cipher.doFinal(ciphertext);
50     }
51

解析出来的LTPAToken信息以%分隔

2.3.2 生成

Websphere LTPA生成时的签名信息是由用户DN和一些用户其他信息组成字符串,使用私有密钥进行签名,由于不清楚这些信息的组成,故无法产生正确的LTPA。

3. Domino部分

本部分的描述仅适用于单一的Domino平台应用与构异系统实现单点登录。

3.1 Domino LTPA Cookie 生成原理

在与 Domino 做 SSO 的时候,会使用 LTPA Token的认证方式,本文描述它的生成原理,通过它我们可以自己编码生成身份认证的 cookie,实现 SSO。
首先,这个 cookie 由以下部分组成:
  • LTPA token 版本(4字节)
  • 创建时间(8字节)
  • 过期时间(8字节)
  • 用户名(可变长度)
  • Domino LTPA 密钥(20字节)
接下来分别说明各部分的具体内容:
  • LTPA token 版本目前 Domino 只有一种值:0x0001
  • 创建时间为以十六进制方式表示的Unix time,例如:2009-04-09 13:52:42 (GMT +8) = 1239256362 = 49DD8D2A。
  • 过期时间=创建时间 + SSO 配置文档的过期时间(LTPA_TokenExpiration域)
  • 用户名为 Names 中用户文档的FullName域值;如:Squall Zhong/Digiwin
  • Domino LTPA 密钥通过 Base64编码后,保存在 SSO 配置文档的LTPA_DominoSecret域中

当然不能将密钥直接发送给浏览器,所以将上述部分合并起来(如上图),计算 SHA-1 校验和。
然后用 SHA-1 校验和替换掉 Domino LTPA 密钥,最后再将内容通过 Base64 编码,形成最终的 cookie 发送给浏览器(如上图)。这样如果 cookie 中的任何内容被修改,校验和就不对了,达到了防篡改的效果。所以最终LTPA Cookie所得到的值为以下公式组成:
SHA-1=LTPA版本号+创建时间+过期时间+用户名+Domino LTPA 密钥
LTPA Cookie= Base64(LTPA版本号+创建时间+过期时间+用户名+SHA-1)

3.2 异构系统所需信息

  • Domino 所使用的DNS域,如:vgolive.com
  • 过期时间(分钟),如:30
  • Domino LTPA 密钥
  • Domino验证字名称,如:/O=VGOLive Technology
注:用户名可以通过Domino验证字进行拼接,如异构系统中用户名为SquallZhong,相应在Domino中的DN就是CN=SquallZhong/O=VGOLive Technology。此类情况仅限于Domino中的CN与异构系统中的用户名一致。如果不一致,需要异构系统做用户对应

3.3 实现

Base64解码/编码所需Jar包: apache-commons-codec-1.3.jar以上

SHA-1的校验使用 java.security.MessageDigest
转换字符集使用: Cp850

3.3.1 解析

 
01     import  org.apache.commons.codec.binary.Base64;
02 …...
03 final  String CHARSET = "Cp850" ;
04 byte [] dominoSecret = Base64.decodeBase64(ltpaDominoSecret.getBytes());
05 byte [] ltpa = Base64.decodeBase64(ltpaToken.getBytes());
06 ByteArrayInputStream stream = new  ByteArrayInputStream(ltpa);
07 int  usernameLength = ltpa.length – 40 ;
08 byte  header[] = new  byte [ 4 ];
09 byte  creation[] = new  byte [ 8 ];
10 byte  expires[] = new  byte [ 8 ];
11 byte  username[] = new  byte [usernameLength];
12 byte [] sha = new  byte [ 20 ];
13 // 读取LTPAToken版本号
14 stream.read(header, 0 , 4 );
15 if  (header[ 0 ] != 0  || header[ 1 ] != 1  || header[ 2 ] != 2 || header[ 3 ] != 3 )
16         throw  new  IllegalArgumentException( "Invalid ltpaToken format" );
17     // 读取开始时间
18    stream.read(creation, 0 , 8 );
19    // 读取到期时间
20    stream.read(expires, 0 , 8 );
21    // 读取Domino用户DN
22    stream.read(username, 0 , usernameLength);
23    // 读取SHA校验和
24    stream.read(sha, 0 , 20 );
25     // 转换用户名
26    char  characters[] = new  char [usernameLength];
27    try  {
28         InputStreamReader isr = new  InputStreamReader(
29             new  ByteArrayInputStream(username),
30             CHARSET);
31         isr.read(characters);
32     } catch  (Exception e) {
33     }
34     // 获得Domino用户DN
35     String dn = new  String(characters);
36     // 获得创建时间
37    Date creationDate = new  Date(
38         Long.parseLong( new  String(creation), 16 ) * 1000 );
39     // 获得到期时间
40    Date expiresDate = new  Date(
41         Long.parseLong( new  String(expires), 16 ) * 1000 );
42 …...
43 // 创建LTPA Token
44     ByteArrayOutputStream ostream = new  ByteArrayOutputStream();
45     try  {
46         // LTPA Token版本号
47         ostream.write(header);
48         // 创建时间
49         ostream.write(creation);
50         // 过期时间
51         ostream.write(expires);
52         // Domino用户DN,如CN=SquallZhong/O=DigiWin
53         ostream.write(username);
54         // Domino LTPA 密钥
55         ostream.write(dominoSecret);
56         ostream.close();
57     } catch  (IOException e) {
58         throw  new  RuntimeException(e);
59     }
60     // 进行 SHA-1 校验和
61     MessageDigest md;
62     try  {
63         md = MessageDigest.getInstance( "SHA-1" );
64         md.reset();
65     } catch  (NoSuchAlgorithmException e) {
66         throw  new  RuntimeException(e);
67     }
68     byte [] digest = md.digest(ostream.toByteArray());
69     // 完成 SHA-1 校验和,digest长度为20
70     boolean  valid = MessageDigest.isEqual(digest, sha);

3.3.2 生成

 
01     /**
02      * 为指定用户创建有效的LTPA Token.创建时间为<tt>now</tt>.
03      *
04      * @param username
05      *            - 用户名,注:使用用户全称,如:CN=SquallZhong/O=VGOLive Technology
06      * @param creationTime
07      *            - 创建时间
08      * @param durationMinutes
09      *            - 到期时间,单位:分钟
10 @param ltpaSecretStr
11      *            - Domino Ltpa 加密字符串
12      * @return - 返回已Base64编码的Ltpa Cookie.
13      * @throws NoSuchAlgorithmException
14      * @throws Base64DecodeException
15      */
16     public  static  String createLtpaToken(String username,
17             GregorianCalendar creationTime, int  durationMinutes,
18             String ltpaSecretStr) throws  NoSuchAlgorithmException {
19         // Base64解码ltpaSecretStr
20         byte [] ltpaSecret = Base64.decodeBase64(ltpaSecretStr.getBytes());
21         // 用户名字节数组
22         byte [] usernameArray = username.getBytes();
23         byte [] workingBuffer = new  byte [preUserDataLength
24                 + usernameArray.length + ltpaSecret.length];
25  
26         // 设置ltpaToken版本至workingBuffer
27         System.arraycopy(ltpaTokenVersion, 0 , workingBuffer, 0 ,
28                 ltpaTokenVersion.length);
29         // 获得过期时间,过期时间=当前时间+到期时间(分钟)
30         GregorianCalendar expirationDate = (GregorianCalendar) creationTime
31                 .clone();
32         expirationDate.add(Calendar.MINUTE, durationMinutes);
33  
34         // 转换创建时间至16进制字符串
35         String hex = dateStringFiller
36                 + Integer.toHexString(
37                         ( int ) (creationTime.getTimeInMillis() / 1000 ))
38                         .toUpperCase();
39         // 设置创建时间至workingBuffer
40         System.arraycopy(hex.getBytes(), hex.getBytes().length
41                 - dateStringLength, workingBuffer, creationDatePosition,
42                 dateStringLength);
43  
44         // 转换过期时间至16进制字符串
45         hex = dateStringFiller
46                 + Integer.toHexString(
47                         ( int ) (expirationDate.getTimeInMillis() / 1000 ))
48                         .toUpperCase();
49         // 设置过期时间至workingBuffer
50         System.arraycopy(hex.getBytes(), hex.getBytes().length
51                 - dateStringLength, workingBuffer, expirationDatePosition,
52                 dateStringLength);
53  
54         // 设置用户全称至workingBuffer
55         System.arraycopy(usernameArray, 0 , workingBuffer, preUserDataLength,
56                 usernameArray.length);
57  
58         // 设置已Base64解码ltpaSecret至workingBuffer
59         System.arraycopy(ltpaSecret, 0 , workingBuffer, preUserDataLength
60                 + usernameArray.length, ltpaSecret.length);
61         // 创建Hash字符串
62         byte [] hash = createHash(workingBuffer);
63  
64         // ltpaToken版本+开始时间(16进制)+到期时间(16进制)+用户全名+SHA-1(ltpaToken版本+开始时间(16进制)+到期时间(16进制)+用户全名)
65         byte [] outputBuffer = new  byte [preUserDataLength + usernameArray.length
66                 + hashLength];
67         System.arraycopy(workingBuffer, 0 , outputBuffer, 0 , preUserDataLength
68                 + usernameArray.length);
69         System.arraycopy(hash, 0 , outputBuffer, preUserDataLength
70                 + usernameArray.length, hashLength);
71         // 返回已Base64编码的outputBuffer
72         return  new  String(Base64.encodeBase64(outputBuffer));
73     }
74 …...

4. 通过F5 BIG-IP创建Domino LTPAToken

F5 iRule代码如下:

when RULE_INIT {

 
01 set  cookie_name "LtpaToken"            # 不更改
02  set  ltpa_version "\x00\x01\x02\x03"    # 不更改
03  set  ltpa_secret "b64encodedsecretkey"  # 从Domino SSO文档获得ltpa密钥
04  set  ltpa_timeout "1800"                # 从Domino SSO文档中获得过期时间,单位:秒
05 }
06  
07 when HTTP_REQUEST {
08  #
09  # Do your usual F5 HTTP authentication here
10  #
11  # Initial values
12  set  creation_time_temp [clock seconds]
13  set  creation_time [ format  % X $creation_time_temp]
14  set  expr_time_temp [expr { $creation_time_temp +  $::ltpa_timeout}]
15  set  expr_time [ format  % X $expr_time_temp]
16  set  username [HTTP::username]
17  set  ltpa_secret_decode [b64decode $::ltpa_secret]
18  # First part of token
19  set  cookie_data_raw {}
20  append cookie_data_raw $::ltpa_version
21  append cookie_data_raw $creation_time
22  append cookie_data_raw $expr_time
23  append cookie_data_raw $username
24  append cookie_data_raw $ltpa_secret_decode
25  # SHA1 of first part of token
26  set  sha_cookie_raw [sha1 $cookie_data_raw]
27  # Final not yet encoded token
28  set  ltpa_token_raw {}
29  append ltpa_token_raw $::ltpa_version
30  append ltpa_token_raw $creation_time
31  append ltpa_token_raw $expr_time
32  append ltpa_token_raw $username
33  append ltpa_token_raw $sha_cookie_raw
34  # Final Base64 encoded token
35  set  ltpa_token_final [b64encode $ltpa_token_raw]
36  # Insert the cookie
37  HTTP::cookie insert name $::cookie_name value $ltpa_token_final
38  }
39  # Remove Authorization HTTP header to avoid using basic authentication
40  if  { [HTTP::header exists "Authorization" ] } {
41  HTTP::header remove "Authorization"
42  }
43 }

 

 

 

来源连接 http://www.cnblogs.com/hannover/archive/2011/05/29/2061798.html



已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐
  • —软件人才免语言低担保 赴美带薪读研!—



你可能感兴趣的:(原理,cookie,ltpa)