RFC 4253 SSH传输层协议
RFC 4253 SSH传输层协议
协议版本交换
客户端和服务端各自发送自己的版本号信息,定义如下:
SSH-protoversion-softwareversion SP comments CR LF
其中 ‘protoversion’ 必须为2.0.‘comments’是可选的。如果存在’comments’,则必须使用一个空格(space)来分割’softwareversion’和’comments’。
服务端可能在发送版本号之前发送其他数据。客户端必须能够处理这种情况,可能忽略这些消息,也可能展示给终端用户。
'’protoversion’and 'oftwareversion’必须使用US-ASCII编码,而且不能包含-。
例子:H-2.0-billsSSH_3.6.3q3<CR><LF>
章节5 和旧本兼容
章节5.1 旧客户端,新服务端
章节5.2 旧客户端,旧务端
章节5.3 包大小和开销
二进制包协议结构
包结构如下:
uint32 packet_length
byte padding_length
byte[n1] payload; n1 = packet_length - padding_length - 1
byte[n2] random padding; n2 = padding_length
byte[m] mac (Message Authentication Code - MAC); m = mac_length
- packet_length:包结构大小,但不包含mac或者 packet_length本身占据的大小。
- padding_length:随机填充字节大小
- payload:负载数据,如果协商压缩算法,则数据会被压缩。默认初始压缩算法为"none"。
- random padding:随机填充的字节数组,但是长度必须为算法块大小或或者8的倍数。至少为4个字节。
- mac:消息认证码。如果协商了消息认证算法,则此字段应该包含MAC字段信息。默认初始消息摘要算法为"none"
在协商加密算法后,package_length也会被加密。
最大包大小
所有的SSH实现必须能够处理负载数据为32768字节或者总数据包大小为35000字节及以下大小的包。
压缩算法
当协商了压缩算法后,负载数据payload需要被压缩。’packet_length’ 和 ’mac’ 都应该基于被压缩后数据重新计算。推荐客户端和服务端采用协商一致的算法。
| 名称 | 可选 | 描述 |
| none | REQUIRED | no compression |
| zlib | OPTIONAL | ZLIB (LZ77) compression [RFC1950]和[RFC1951] |
加密算法
在秘钥交换过程中会协商出一个加密算法和一个key。当加密算法生效时, packet length, padding length, payload, and padding 等字段都会被加密。SSH实现必须允许客户端和服务端使用不同的加密算法。然而在实践中,推荐客户端和服务端使用一致的算法。
| 名称 | 可选 | 备注 |
|---|---|---|
| 3des-cbc | REQUIRED | three-key 3DES in CBC mode |
| blowfish-cbc | OPTIONAL | Blowfish in CBC mode |
| twofish256-cbc | OPTIONAL | Twofish in CBC mode,with a 256-bit key |
| twofish-cbc | OPTIONAL | alias for “twofish256-cbc”(this is being retained for historical reasons) |
| twofish192-cbc | OPTIONAL | Twofish with a 192-bit key |
| twofish128-cbc | OPTIONAL | Twofish with a 128-bit key |
| aes256-cbc | OPTIONAL | AES in CBC mode,with a 256-bit key |
| aes192-cbc | OPTIONAL | AES with a 192-bit key |
| aes128-cbc | RECOMMENDED | AES with a 128-bit key |
| serpent256-cbc | OPTIONAL | Serpent in CBC mode, with a 256-bit key |
| serpent192-cbc | OPTIONAL | Serpent with a 192-bit key |
| serpent128-cbc | OPTIONAL | Serpent with a 128-bit key |
| arcfour | OPTIONAL | the ARCFOUR stream cipher with a 128-bit key |
| idea-cbc | OPTIONAL | IDEA in CBC mode |
| cast128-cbc | OPTIONAL | CAST-128 in CBC mode |
| none | OPTIONAL | no encryption; NOT RECOMMENDED |
数据完整性
通过在每个包中包含mac字段来保证数据完整性。mac字段是根据私密共享的包序列顺序来计算出来的。
mac = MAC(key, sequence_number || unencrypted_packet)
unencrypted_packet是整个包(不包含mac字段)。sequence_number是一个隐式包序列数字(uint32类型)。sequence_number以0开始,从第一个包开始递增。它永远不会被重置。即使算法改变也不会。SSH实现必须允许客户端和服务端使用不同的摘要算法。然而在实践中,推荐客户端和服务端使用一致的算法。
mac字段必须附在包的最后一部分。mac的长度根据选择的算法而有所不同。
| 名称 | 可选 | 备注 |
| hmac-sha1 | REQUIRED | HMAC-SHA1 (digest length = key length = 20) |
| hmac-sha1-96 | RECOMMENDED | first 96 bits of HMAC-SHA1 (digest length = 12, key length = 20) |
| hmac-md5 | OPTIONAL | HMAC-MD5 (digest length = key length = 16) |
| hmac-md5-96 | OPTIONAL | first 96 bits of HMAC-MD5 (digest length = 12, key length = 16) |
| none | OPTIONAL | no MAC; NOT RECOMMENDED |
秘钥交换算法
秘钥交换算法定义如下:
| 名称 | 可选 | 备注 |
| diffie-hellman-group1-sha1 | REQUIRED | [RFC2409] [RFC2412] |
| diffie-hellman-group14-sha1 | REQUIRED | [RFC3526] |
公钥算法
定义公钥类型
- 密钥格式(Key Format):秘钥如何编码以及证书如何展示。协议中的二进制key字节可能包含证书。
- 签名或者加密算法:某些密钥类型可能不支持同时签名和加密。密钥的使用可能也受到策略限制。
- 编码格式或者加密后的数据:包含且不限于填充字节,字节顺序和数据格式。
公钥算法格式如下:
| 算法名称 | 可选 | 备注 |
| ssh-dss | REQUIRED | sign Raw DSS Key |
| ssh-rsa | RECOMMENDED | sign Raw RSA Key |
| pgp-sign-rsa | OPTIONAL | sign OpenPGP certificates (RSA key) |
| pgp-sign-dss | OPTIONAL | sign OpenPGP certificates (DSS key) |
认证机构和公钥报文结构定义如下:
string certificate or public key format identifier
byte[n] key/certificate data
公钥/认证 格式没有明确指出签名格式的必须使用公钥格式的签名格式。
签名报文定义如下:
string signature format identifier (as specified by the public key/certificate format)
byte[n] signature blob in format specific encoding.
| 公钥类型 | 请求报文结构 | 响应报文结构 |
|---|---|---|
| ssh-dss | text-plain<br>string “ssh-dss”<br>mpint p<br>mpint q<br>mpint g<br>mpint y<br> |
text-plain<br>string “ssh-dss”<br>string dss_signature_blob<br> |
| ssh-rsa | text-plain<br>string “ssh-rsa”<br>mpint e<br>mpint n<br> |
text-plain<br>string “ssh-rsa”<br>string rsa_signature_blob<br> |
密钥交换
客户端和服务端都发送优先级顺序的算法列表。两边都可能猜测另一边可能用哪种算法,并且可能根据选择的可能合适的方法发送一个初始化的密钥交换包。
算法协商
算法协商报文
byte SSH_MSG_KEXINIT
byte[16] cookie (random bytes)
name-list kex_algorithms
name-list server_host_key_algorithms
name-list encryption_algorithms_client_to_server
name-list encryption_algorithms_server_to_client
name-list mac_algorithms_client_to_server
name-list mac_algorithms_server_to_client
name-list compression_algorithms_client_to_server
name-list compression_algorithms_server_to_client
name-list languages_client_to_server
name-list languages_server_to_client
boolean first_kex_packet_follows
uint32 0 (reserved for future extension)
- cookie:随机字节数组
- kex_algorithms 密钥交换算法。双方必须判断双方共同拥有的优先级最高的算法。如果没有,则双方必须断开连接。
- server_host_key_algorithms:服务端列出它所拥有的host key,客户端列出它能够处理的host key类型。
- encryption_algorithms:加密算法列表。
- mac_algorithms:消息摘要算法列表
- compression_algorithms:压缩算法列表
- languages:语言优先级[RFC3066],但是双方都可忽略该字段。如果没有语言优先级,该字段应该为空。
- first_kex_packet_follows:标识是否随后会来一个猜测秘钥交换算法的包。
SSH_MSG_KEXINIT协议包标识开始协商算法到SSH_MSG_NEWKEYS协议包结束,在这期间只能发送如下包:
- 传输层协议包:1-19
- 算法协商包:20-29,但不再接收新的
SSH_MSG_KEXINIT协议包 - 特定的密钥交换方法消息:30-49
密钥交换输出
密钥交换产生两个值:共享密钥K和交换哈希值H。加密和认证密钥就是从这些纸推算出来。第一次密钥交换的H同时作为会话标识,在整个会话期间都是唯一的。一旦计算出来。会话标识就不会变动,即便后续密钥改变。
每个密钥交换方法指定了一个哈希方法在密钥交换工程中使用。双方必须使用相同的哈希算法。
加密密钥必须为计算为哈希。
K的计算方式过于复杂,放原文。
Each key exchange method specifies a hash function that is used in
the key exchange. The same hash algorithm MUST be used in key
derivation. Here, we’ll call it HASH.
Encryption keys MUST be computed as HASH, of a known value and K, as
follows:
o Initial IV client to server: HASH(K || H || "A" || session_id)
(Here K is encoded as mpint and "A" as byte and session_id as raw
data. "A" means the single character A, ASCII 65).
o Initial IV server to client: HASH(K || H || "B" || session_id)
o Encryption key client to server: HASH(K || H || "C" || session_id)
o Encryption key server to client: HASH(K || H || "D" || session_id)
o Integrity key client to server: HASH(K || H || "E" || session_id)
o Integrity key server to client: HASH(K || H || "F" || session_id)
Key data MUST be taken from the beginning of the hash output. As
many bytes as needed are taken from the beginning of the hash value.
If the key length needed is longer than the output of the HASH, the
key is extended by computing HASH of the concatenation of K and H and
the entire key so far, and appending the resulting bytes (as many as
HASH generates) to the key. This process is repeated until enough
key material is available; the key is taken from the beginning of
this value. In other words:
Ylonen & Lonvick Standards Track [Page 20]
RFC 4253 SSH Transport Layer Protocol January 2006
K1 = HASH(K || H || X || session_id) (X is e.g., "A")
K2 = HASH(K || H || K1)
K3 = HASH(K || H || K1 || K2)
...
key = K1 || K2 || K3 || ...
This process will lose entropy if the amount of entropy in K is
larger than the internal state size of HASH.
使用密钥
密钥协商阶段在双方各自发送SSH_MSG_NEWKEYS消息后结束。这个消息使用旧的key和算法发送。在这之后的消息必须使用新的key和算法。
哈夫曼霍尔曼 密钥交换
原文
1. C generates a random number x (1 < x < q) and computes
e = g^x mod p. C sends e to S.
Ylonen & Lonvick Standards Track [Page 21]
RFC 4253 SSH Transport Layer Protocol January 2006
2. S generates a random number y (0 < y < q) and computes
f = g^y mod p. S receives e. It computes K = e^y mod p,
H = hash(V_C || V_S || I_C || I_S || K_S || e || f || K)
(these elements are encoded according to their types; see below),
and signature s on H with its private host key. S sends
(K_S || f || s) to C. The signing operation may involve a
second hashing operation.
3. C verifies that K_S really is the host key for S (e.g., using
certificates or a local database). C is also allowed to accept
the key without verification; however, doing so will render the
protocol insecure against active attacks (but may be desirable for
practical reasons in the short term in many environments). C then
computes K = f^x mod p, H = hash(V_C || V_S || I_C || I_S || K_S
|| e || f || K), and verifies the signature s on H.
Values of ’e’ or ’f’ that are not in the range [1, p-1] MUST NOT be
sent or accepted by either side. If this condition is violated, the
key exchange fails.
This is implemented with the following messages. The hash algorithm
for computing the exchange hash is defined by the method name, and is
called HASH. The public key algorithm for signing is negotiated with
the SSH_MSG_KEXINIT messages.
客户端发送如下报文:
byte SSH_MSG_KEXDH_INIT mpint e
服务端回应如下报文:
byte SSH_MSG_KEXDH_REPLY
string server public host key and certificates (K_S)
mpint f
string signature of H
H是如下数据如下顺序的哈希值:
string V_C, the client’s identification string (CR and LF excluded)
string V_S, the server’s identification string (CR and LFexcluded)
string I_C, the payload of the client’s SSH_MSG_KEXINIT
string I_S, the payload of the server’s SSH_MSG_KEXINIT
string K_S, the host key
mpint e, exchange value sent by the client
mpint f, exchange value sent by the server
mpint K, the shared secret
请求服务
密钥交换完成后,客户端请求服务。目前定义了两种服务
ssh-userauth和ssh-connection
本地服务应该用 “servicename@domain”.这样的形式。 报文结构如下:
byte SSH_MSG_SERVICE_REQUEST
string service name
服务端回应:
byte SSH_MSG_SERVICE_ACCEPT
string service name
额外信息
断开连接消息
byte SSH_MSG_DISCONNECT
uint32 reason code
string description in ISO-10646 UTF-8 encoding [RFC3629]
string language tag [RFC3066]
| Symbolic name | reason code |
| SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT | 1 |
| SSH_DISCONNECT_PROTOCOL_ERROR | 2 |
| SSH_DISCONNECT_KEY_EXCHANGE_FAILED | 3 |
| SSH_DISCONNECT_RESERVED | 4 |
| SSH_DISCONNECT_MAC_ERROR | 5 |
| SSH_DISCONNECT_COMPRESSION_ERROR | 6 |
| SSH_DISCONNECT_SERVICE_NOT_AVAILABLE | 7 |
| SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED | 8 |
| SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE | 9 |
| SSH_DISCONNECT_CONNECTION_LOST | 10 |
| SSH_DISCONNECT_BY_APPLICATION | 11 |
| SSH_DISCONNECT_TOO_MANY_CONNECTIONS | 12 |
| SSH_DISCONNECT_AUTH_CANCELLED_BY_USER | 13 |
| SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE | 14 |
| SSH_DISCONNECT_ILLEGAL_USER_NAME | 15 |
reasonCode的含义
| 消息类型 | 报文 | 备注 |
|---|---|---|
| 忽略消息 | text-plain<br>byte SSH_MSG_IGNORE<br>string data<br> |
|
| 调试消息 | text-plain<br>byte SSH_MSG_DEBUG<br>boolean always_display<br>string message in ISO-10646 UTF-8 encoding [RFC3629]<br>string language tag [RFC3066]<br> |
如果 ’always_display’ 为true,则消息应该被显示 ,否则消息应该对用户透明。 |
| 保留消息 | text-plain<br>byte SSH_MSG_UNIMPLEMENTED<br>uint32 packet sequence number of rejected message<br> |
当客户端或者服务端发现无法识别的请求时,必须发送此报文 |
消息编码总结
| Symbolic name | value |
| SSH_MSG_DISCONNECT | 1 |
| SSH_MSG_IGNORE | 2 |
| SSH_MSG_UNIMPLEMENTED | 3 |
| SSH_MSG_DEBUG | 4 |
| SSH_MSG_SERVICE_REQUEST | 5 |
| SSH_MSG_SERVICE_ACCEPT | 6 |
| SSH_MSG_KEXINIT | 20 |
| SSH_MSG_NEWKEYS | 21 |