本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,
1. 前言
MPPE(Microsoft Point-To-Point Encryption, 微软点对点加密)协议在RFC3078, 3079中定义, 描述
了在PPP协议中进行数据加密的方法,通常用其实现PPTP模式的×××。
MPPE中的加密算法是固定的,使用RC4加密算法而不能是其他算法。
2. CCP选项
是否支持MPPE是PPP通信双方在CCP(Compression Control Protocol)协商过程中确定的, CCP协商数
据格式如下:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length | Supported Bits |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Supported Bits |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Type
18
Length
6
Supported Bits
这是个32位数, 格式如下, 大头方式:
3 2 1
1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |H| |M|S|L|D| |C|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
各位说明如下:
C : 在MPPC中定义
D : 该位定义的功能已经作废
L : 使用40位加密
S : 使用128位加密
M : 使用56位加密
H : 表示使用无状态(stateless)模式, 每个包都是单独加密的
其他位必须为0, 在协商时, 发起方设置所有能支持的加密位数(M, S, L), 响应方则在其中选择一种
, 如果响应方支持的加密位数超过一种, 应该选择最强加密的那种(S > M > L)。
3. MPPE包
3.1 概述
MPPE包传输前,PPP必须已经进入网络层协议阶段,CPP控制协议必须是“Opened”状态。
每个PPP信息中只能携带一个MPPE包,对于加密了的PPP包,其PPP类型是0x00FD。
每个MPPE数据包的最大长度等于PPP所能封装的最大信息长度。
只对从0x0021到0x00FA类型的PPP包进行加密,加密后PPP包类型变为0x00FD,其他类型的PPP包不通
过MPPE处理。
3.2 数据包格式
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| PPP Protocol |A|B|C|D| Coherency Count |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Encrypted Data...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
参数说明如下:
PPP Protocol : 对MPPE来说是0x00FD,如果同时支持PPP压缩,该值是可以被压缩的;
Bit A :该位表示是否对加密表进行初始化,对于无状态模式,该位在每个包中都是置1的
Bit B :在MPPE中不考虑
Bit C :在MPPE中不考虑
Bit D :1表示该包是加密的,0表示是不加密的
Coherency Count : 12位数,表示PPP包的序号,单向增长,到0xfff后归0从新计数;
Encrypted Data :加密了的数据
在同时使用MPPE和MPPC(微软PPP压缩协议)的情况,对发出的数据,先进行压缩再加密,对于接收的
数据,先解密再解压。
注意包的最前4字节是明文,不加密的,否则密钥更新就是先有鸡还是先有蛋的问题了。
4. 加密处理
4.1 初始化会话密钥
一般情况下会话密钥初始化是使用双方的证书来进行协商的,当然也可以通过其他方法来生成,通过
预先共享的密钥PSK来生成。
4.2 用会话密钥初始化RC4算法
生成会话密钥后,使用rc4_set_key来设置RC4密钥:
rc4_set_key(RC4Key, Length_Of_Key, Initial_Session_Key)
该函数可从openssl中查到。
4.3 加密数据
加密解密数据可用rc4函数处理:
EncryptedData = rc4(RC4Key, Length_Of_Data, Data)
该函数可从openssl中查到。
5. 密钥处理
密钥处理永远是加密通信的核心部分,只有保证双方的密钥是同步的,接收方才能正确解密发送方的
数据,否则收到的只是一些垃圾数据。
5.1 密钥更改
如果使用了无状态模式,则对于每个包都需要重新交换一次会话密钥, 发送方必须在加密和发送数据
前改变会话密钥, 接收方必须在接收数据后, 解密数据前改变会话密钥.
对于状态保存模式, 发送方必须在包序号的后8位是0xff时更新会话密钥,同样是在加密和发送数据前
; 接收方接收到序号后8位是0xff的包后, 在解密前改变会话密钥.
MPPE密钥更改算法:
/*
* SessionKeyLength is 8 for 40-bit keys, 16 for 128-bit keys.
*
* SessionKey is the same as StartKey in the first call for
* a given session.
*/
需要3个参数作为输入: 初始密钥, 老会话密钥, 会话密钥长度
一个参数作为输出: 中间密钥
是使用SHA1算法对初始密钥, 当前会话密钥进行HASH后得到新的会话密钥
第一次使用时, 老会话密钥也用初始密钥
void
GetNewKeyFromSHA(
IN unsigned char *StartKey,
IN unsigned char *SessionKey,
IN unsigned long SessionKeyLength
OUT unsigned char *InterimKey )
{
// SHA1输出是160位, 即20字节
unsigned char Digest[20];
ZeroMemory(Digest, 20);
/*
* SHAInit(), SHAUpdate() and SHAFinal()
* are an implementation of the Secure
* Hash Algorithm [7]
*/
SHAInit(Context);
// SHA1(初始密钥)
SHAUpdate(Context, StartKey, SessionKeyLength);
// SHA1(40字节的填充数据1)
SHAUpdate(Context, SHApad1, 40);
// SHA1(原会话密钥)
SHAUpdate(Context, SessionKey, SessionKeyLength);
// SHA1(40字节的填充数据2)
SHAUpdate(Context, SHApad2, 40);
SHAFinal(Context, Digest);
// 复制最后的SH1结果到中间密钥空间
MoveMemory(InterimKey, Digest, SessionKeyLength);
}
得到中间密钥后, 再用中间密钥使用rc4算法加密中间密钥得到新的会话密钥:
// 用中间密钥设置RC4密钥
rc4_set_key(RC4Key, Length_Of_Key, InterimKey)
// 用RC4加密中间密钥
SessionKey = rc4(RC4Key, Length_Of_Key, InterimKey)
可见, 如果初始密钥固定, 以后每次更新计算出的会话密钥都是相同的.
如果不是128位加密, 会将新会话密钥的某些字节取固定值以减少强度:
对于40位加密, 生成的新会话密钥前3字节固定为: 0xD1, 0x26, 0x9E
对于56位加密, 生成的新会话密钥前1字节固定为: 0xD1
5.2 密钥同步
5.2.1 无状态模式
无状态模式下每个包加密的密钥都是不同的, 每个包都要重新计算会话密钥, 每个包都会设置“A”
标志。当接收方收到的包的序号(C1)大于上次接收包的序号(C2)时, 接收方在解密前必须执行N=C1-
C2次密钥更改计算, 一般情况下没丢包时N为1, 异常情况下N会大于1。
5.2.2 状态保持模式
在状态保持模式下,发送方发现序号的后8位已经为0xff时更新密钥,更新完再加密和发送,包中设
置“A”标志;接收方接收到“A”标志包时先更新密钥,再解密。
如果发生丢弃“A”标志包的情况,接收方会发现接收的数据包序号后8位比前面的包小,这时接收方
要进行密钥更新操作并发送一个CCP的复位请求包。如果丢失的是超过256个以上包的情况,发送方可
能已经更新了两次密钥了,接收方应该(SHOULD)能检测这种情况并进行相关密钥更新操作,但不是必
须的,RFC中没有规定如何检测,我想是根据是否能正确解密数据包来判断吧。
在状态保持模式下接收方检测到丢包情况时,接收方必须丢弃该包,并发送CCP的复位请求包,在收
到“A”标志的数据包前丢弃接收到的所有其他包。当收到“A”标志包时,将该包序号作为接收方新
的包序列号,并用当前的会话密钥更新RC4,如前所述,这个会话密钥已经是更新后的了,和发送方
保持相同。重新设置RC4密钥:
rc4_set_key(RC4Key, Length_Of_Key, SessionKey)
发送方接收到CCP复位请求包时,也要用当前的会话密钥(该会话密钥和接收方相同)更新RC4,然后在
下一个包中设置“A”标志,这样就可以继续保证密钥同步。
由于PPP都用在不是很可靠的通信线路上,因此最常用的还是无状态模式。
6. 实现
在Linux下提供了MPPE的实现,其中加密部分是内核中的处理,PPP协商是用户空间的处理,以前是需
要补丁才能实现,但2.6.14版本后已经将MPPE纳入内核中不需要补丁了,不过在2.6的MPPE实现中,
加密算法不是RC4而是ARC4,ARC4只是个非常简单的流异或处理,几乎说不上有什么强度,比RC4差很
多,不知道什么样的客户端能和其通;而原来2.4内核的MPPE补丁是支持RC4的,可以使用128位的高
强度加密,对于win2K,必须打了SP2后才能支持128位的PPTP,否则只支持56位。
7. 结论
MPPE提供了PPP通信的机密性,使用的固定的加密算法,没使用认证,因此比IPSEC简单了很多,通常
最好是使用无状态模式使每个包都由不同的密钥进行加密。
MPPE有Linux的实现补丁,新2.6内核也支持了MPPE,不过其加密算法有疑问。