盘点认证协议 : 普及篇之LTPA

1,610 阅读11分钟

总文档 :文章目录
Github : github.com/black-ant

  • 纯约束型协议 : OAuth , SAML , OIDC , CAS ,LTPA
  • 服务器类协议 : RADIUS , Kerberos , ADFS
  • 认证方式类 : OTP , 生物认证 (人脸 , 声纹 , 指纹)
  • 认证服务器(附带) : AD , LDAP , ADFS

这一篇聊一聊 LTPA 这个协议 , 这个算是一个很少见的协议 , 专属于 IBM , 我们只是简单的说说它...

一 . 前言

ltpa 全称 Lightweight Third-Party Authentication , 即轻量级第三方认证 . 这个协议看起来很复杂 , 其实使用的时候会感觉很简单 , 用法和 JWT 很类似.

LTPA 是 一项 IBM 协议 ,用于 在 WebSphere®Application Server 中提供基于 cookie 或二进制安全性令牌的认证机制 ,其支持 单点登录 SSO .

整个流程中包括多个服务器 , 例如WebSphere® 和 DataPower®。要对其中一个或多个服务器实现单点登录解决方案,您可以将 WebSEAL 配置为支持 LTPA 认证 .

目的 : LTPA 令牌认证的目的是将 LTPA 令牌从第一个 Web Service(其认证生成客户机)流动到下游 Web Service , 简单点说就是 IDP Server 生成令牌 , 下发到下游服务 .

Cookie 作用 : 具有有效 LTPA cookie 的用户可以访问与第一个服务器属于同一身份验证域的服务器,并将自动进行身份验证。

Cookie 本身包含有关已经验证的用户、用户要验证的领域(例如 LDAP 服务器)和时间戳的信息。所有这些信息之二用共享的3DES 密钥加密,并由公/私密密钥对签名。这一切都很好,直到您试图执行一些故障排除,并意识到无法查看这些 cookie 的内部。

二 . 深入知识点

2.1 流程分析

宏观流程: User ---> ISAM SSO (WebSeal) - LTPA goes here ----> backend server WAS

宏观解释 :

  • 后端WAS服务器本身不做任何身份验证。
  • 用户对SSO服务器进行身份验证,SSO服务器将加密的LTPA令牌发送到后端服务器,该服务器包含用户名(通常只有用 户名、组成员,但从不包含密码)。
  • 后端服务器解密LTPA并将其作为LTPA并信任它。
  • 然后后端应用服务器(WAS)将此身份验证详细信息传递给应用程序。

ltpa_simple_client_server.jpg

流程详情:

  1. 当未经认证的用户对 WebSEAL 受保护资源发出请求时,WebSEAL 将首先确定是否提供了 LTPA Cookie。
    • 如果提供了 LTPA Cookie,那么它将验证此 Cookie 的内容,并在验证成功后根据此 Cookie 中包含的用户名和到期时间创建新会话。
    • 如果未提供 LTPA Cookie,那么 WebSEAL 将继续使用其他已配置的认证机制对用户进行认证。
  2. 完成认证操作后,将在 HTTP 响应中插入新的 LTPA Cookie,并将其传递回客户机以供其他支持 LTPA 的认证服务器使用。

2.2 LTPA 架构

LTPA 协议是基于 WebSphere 实现的 !

WebSphere 是什么 : WebSphere 是一个IBM 产品 , 它支持在一个因特网域中的一组web 服务器间使用单一登录的认证策略 ,通过密码术 可支持分布式环境的安全性 ,web 用户只需对 WebSphere Application Server 或 Domino 服务器认证一次 ,认证将会通过服务器进行共享.

**PS: 和联合认证有相同的思想 .. **

一个通过有效的LTPA Cookie能够在同一个认证域中所有服务器自动认证。此Cookie中包含认证信息和时间戳。这些信息通过共享的3DES Key进行了bis 加密。使用公共密钥/私有密钥进行签名。

2.3 LTPA Token 令牌

2.3.1 令牌的属性

LTPA 令牌包含如下属性 :

  • title : 必须 ,策略的标题 ,字符串
  • description : 非必须 , 对策略的描述 ,字符串
  • key : LTPA 秘钥 ,必须 ,用于生成 LTPA 令牌 的LTPA 秘钥名称 - 包括 :
    • my-ltpa-key (策略默认为 1.0),my-ltpa-key:2.0.0 (使用特定版本),my-ltpa-key:latest ( 最新版本 )
    • authenticatedUserName : 已认证用户名
    • tokenVersion : 令牌版本
    • tokenOutput : 令牌用于放置已生成令牌的位置 (cookie 头 ,WSSec 头 )
    • tokenExpiry : 整数 ,令牌过期时间

2.3.2 令牌的案例

首先,这个 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.3.3 令牌的区别

LTPA (Version 1): www.ibm.com/websphere/a…
LTPA2: www.ibm.com/websphere/a…

LtpaToken LtpaToken 用于与 WebSphere Application Server 的前发行版进行互操作。此令牌仅包含认证身份属性。 LtpaToken 针对 WebSphere Application Server V5.1.0.2 之前的发行版(对于 z/OS®)或 V5.1.1(对于分布式系统)生成。

LtpaToken2 LtpaToken2 包含更强的加密功能,并且您能够向令牌添加多个属性。此令牌包含认证身份和其他信息(例如,属性)。属性用于联系原始登录服务器和唯一高速缓存密钥。如果在确定唯一性时要考虑除身份以外的其他内容,还将使用属性来查找主题。

注意 : 为了允许运行不同版本WebSphere Application Server的服务器之间的互操作性,默认情况下,在将绑定配置为期望LTPA2令牌时,Version 7.0及更高版本的JAX-WS web服务安全运行时可以成功地使用LTPA Version 1令牌。但是,您可以将JAX-WS运行时的绑定配置为只接受LTPA2令牌。有关更多信息,请参阅有关身份验证生成器或使用者令牌设置的文档。

IBM LTPA 文档支持

2.4 LTPA Cookie

Cookie 加密方式

LTPA cookie 在 DESede/ECB/PKCS5Padding 模式下使用3DES 密钥进行加密。真正的密钥也是在 DESede/ECB/PKCS5Padding 模式下使用3DES 加密的,其中使用用0X0最多24字节填充的所提供密码的 SHA 散列。要解密实际令牌,可以获取密码,生成一个3DES 密钥,解密加密密钥,然后解密 cookie 数据。还有一个公钥/私钥对用于对 cookie 进行签名。

  • 使用 3DES 秘钥 进行 DESede/ECB/PKCS5P 加密
  • 秘钥采用 DESede/ECB/PKCS5P进行加密
  • 通过提供的密码进行 SHA Hash
  • 对生成的24字节秘钥 进行 Base64 编码

Cookie 的元素 @ my.oschina.net/psuyun/blog…

  • LTPA token 版本(4字节)
  • 创建时间(8字节)
  • 过期时间(8字节)
  • 用户名(可变长度)
  • Domino LTPA 密钥(20字节)

在与 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.1 一个 LTPA 的格式

[token] = BASE64([header][creation time][expiration time][username][SHA-1 hash])
    
Header:  LtpaToken 版本(长度4),Domino的固定为[0x00][0x01][0x02][0x03]
Creation time: 创建时间戳(长度8),格式为Unix time比如[2010-03-12 00:21:49]为4B99189D
expiration time:过期时间戳(长度8) 同上
username: 用户名(长度不定)
SHA-1 hash:SHA-1校验和(长度20)
	- 由前面所说的密钥和其余的Token资料合并而成,合成公式如下
	- [SHA-1 hash] = SHA-1([header][creation time][expiration time][username][shared secret]) 

001.gif

002.gif

3.2 Domain 解析 Token

  1. Base64解码LtpaToken。
  2. 截取最前面20字节,最后面20字节,中间部分就是用户名。如果用户名在本系统中不正确,返回无效的LtpaToken。
  3. 截取最后面20字节,是SHA-1校验和。用Token中的其余部分和本系统中的密钥生成新的SHA-1校验和,如果2个校验和不匹配。返回无效的LtpaToken。
  4. 当前服务器时间必须大于创建时间,小于失效时间。否则返回无效的LtpaToken。
  5. 最后解析通过了,完成用户的登录

3.3 Java 实现 LTPA Cookie 解析

转载自 @ www.cnblogs.com/cmt/p/14580…

解析方法


 // LTPA 3DES 密钥 
        String ltpa3DESKey = "7dH4i81YepbVe+gF9XVUzE4C1Ca5g6A4Q69OFobJV9g="; 
        // LTPA 密钥密码 
        String ltpaPassword = "Passw0rd"; 
        try { 
			// 获得加密key 
            byte[] secretKey = getSecretKey(ltpa3DESKey, ltpaPassword); 
            // 使用加密key解密ltpa Cookie 
            String ltpaPlaintext = new String(decryptLtpaToken(tokenCipher, 
                    secretKey)); 
            displayTokenData(ltpaPlaintext); 
        } catch (Exception e) { 
            System.out.println("Caught inner: " + e); 
        } 
 
    //获得安全Key 
    private static byte[] getSecretKey(String ltpa3DESKey, String password) 
            throws Exception { 
        // 使用SHA获得key密码的hash值 
        MessageDigest md = MessageDigest.getInstance("SHA"); 
        md.update(password.getBytes()); 
        byte[] hash3DES = new byte[24]; 
        System.arraycopy(md.digest(), 0, hash3DES, 0, 20); 
        // 使用0替换后4个字节 
        Arrays.fill(hash3DES, 20, 24, (byte) 0); 
        // BASE64解码 ltpa3DESKey 
        byte[] decode3DES = Base64.decodeBase64(ltpa3DESKey.getBytes()); 
        // 使用key密码hash值解密已Base64解码的ltpa3DESKey 
        return decrypt(decode3DES, hash3DES); 
    } 
    //解密LtpaToken 
    public static byte[] decryptLtpaToken(String encryptedLtpaToken, byte[] key) 
            throws Exception { 
        // Base64解码LTPAToken 
        final byte[] ltpaByteArray = Base64.decodeBase64(encryptedLtpaToken 
                .getBytes()); 
        // 使用key解密已Base64解码的LTPAToken 
        return decrypt(ltpaByteArray, key); 
    } 
    // DESede/ECB/PKC5Padding解方法 
    public static byte[] decrypt(byte[] ciphertext, byte[] key) 
            throws Exception { 
        final Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding"); 
        final KeySpec keySpec = new DESedeKeySpec(key); 
        final Key secretKey = SecretKeyFactory.getInstance("TripleDES") 
                .generateSecret(keySpec); 
        cipher.init(Cipher.DECRYPT_MODE, secretKey); 
        return cipher.doFinal(ciphertext); 
    } 



生成 LTPA Token


 /** 
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…... 

通过F5 BIG-IP创建Domino LTPAToken


when RULE_INIT {
 set cookie_name "LtpaToken"           # Don't change this
 set ltpa_version "\x00\x01\x02\x03"   # Don't change this
 set ltpa_secret "b64encodedsecretkey" # Set this to the LTPA secrey key from your Lotus Domino LTPA configuration
 set ltpa_timeout "1800"               # Set this to the timeout value from your Lotus Domino LTPA configuration
}

when HTTP_REQUEST {
 #
 # Do your usual F5 HTTP authentication here
 #

 # Initial values
 set creation_time_temp [clock seconds]
 set creation_time [format %X $creation_time_temp]
 set expr_time_temp [expr { $creation_time_temp + $::ltpa_timeout}]
 set expr_time [format %X $expr_time_temp]
 set username [HTTP::username]
 set ltpa_secret_decode [b64decode $::ltpa_secret]

 # First part of token
 set cookie_data_raw {}
 append cookie_data_raw $::ltpa_version
 append cookie_data_raw $creation_time
 append cookie_data_raw $expr_time
 append cookie_data_raw $username
 append cookie_data_raw $ltpa_secret_decode

 # SHA1 of first part of token
 set sha_cookie_raw [sha1 $cookie_data_raw]

 # Final not yet encoded token
 set ltpa_token_raw {}
 append ltpa_token_raw $::ltpa_version
 append ltpa_token_raw $creation_time
 append ltpa_token_raw $expr_time
 append ltpa_token_raw $username
 append ltpa_token_raw $sha_cookie_raw

 # Final Base64 encoded token
 set ltpa_token_final [b64encode $ltpa_token_raw]

 # Insert the cookie
 HTTP::cookie insert name $::cookie_name value $ltpa_token_final
 }

 # Remove Authorization HTTP header to avoid using basic authentication
 if { [HTTP::header exists "Authorization"] } {
 HTTP::header remove "Authorization"
 }
}

总结

LTPA 是一个企业痕迹很重的协议 ,它基本上归属于 IBM , 这就意味着使用如果有困难 , 文档不一定能解决 , 而请求服务支持可不是一个简单的事情 .

当然 ,在使用中 , ltpa cookie 也可以被当成一种 JWT Token 的一种生成方式 , 将其放在Cookie 中 ,再基于 SSO 认证 , 这不算一个 LTPA 体系 , 仅仅是使用了 LTPA 的 Cookie 生成方式 .

感谢

LTPA Cookie原理