You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
我贴下原文,D_i 代表第 i 轮,每一轮的计算为:[上次计算的结果 + data + salt],直到 digest 之后的结果符合key长度要求
KEY DERIVATION ALGORITHM
The key and IV is derived by concatenating D_1, D_2, etc until enough data is available for the key and IV. D_i is defined as:
D_i = HASH^count(D_(i-1) || data || salt)
where || denotes concatentaion, D_0 is empty, HASH is the digest algorithm in use, HASH^1(data) is simply HASH(data), HASH^2(data) is HASH(HASH(data)) and so on.
小明 => 小红,小明用原文 A 做hash后得到 tag A 发给小红
hacker 劫持后,用伪造的原文B做hash后得到 tag B,同时将 原文A,tagA替换掉发给小红,小红获取 原文A(实际被替换成原文B)计算后与 tagA(实际被替换为tagB)比较,发现相同,认为数据没有篡改 !!
所以必须引入一个只有小红,小明才知道的key
// https://datatracker.ietf.org/doc/html/rfc5869
A key derivation function (KDF) is a basic and essential component of
cryptographic systems. Its goal is to take some source of initial
keying material and derive from it one or more cryptographically
strong secret keys.
KDF,即密钥派生算法,旨在通过一些初始的密码材料,派生出一个至多个密码学意义上,足够健壮的 密钥
HKDF 包含两个阶段,extract, expand
extract(salt, user_weak_password) => HKDF_struct
// If you don't have any info to pass, use an empty slice.
HKDF_struct.expand(info) => out_key
In many applications, the input keying material is not necessarily
distributed uniformly, and the attacker may have some partial
knowledge about it (for example, a Diffie-Hellman value computed by a
key exchange protocol) or even partial control of it (as in some
entropy-gathering applications). Thus, the goal of the "extract"
stage is to "concentrate" the possibly dispersed entropy of the input
keying material into a short, but cryptographically strong,
pseudorandom key. In some applications, the input may already be a
good pseudorandom key; in these cases, the "extract" stage is not
necessary, and the "expand" part can be used alone.
The second stage "expands" the pseudorandom key to the desired
length; the number and lengths of the output keys depend on the
specific cryptographic algorithms for which the keys are needed.
2.2. Step 1: Extract
HKDF-Extract(salt, IKM) -> PRK
Options:
Hash a hash function; HashLen denotes the length of the
hash function output in octets
Inputs:
salt optional salt value (a non-secret random value);
if not provided, it is set to a string of HashLen zeros.
IKM input keying material
Output:
PRK a pseudorandom key (of HashLen octets)
The output PRK is calculated as follows:
PRK = HMAC-Hash(salt, IKM)
2.3. Step 2: Expand
HKDF-Expand(PRK, info, L) -> OKM
Options:
Hash a hash function; HashLen denotes the length of the
hash function output in octets
Krawczyk & Eronen Informational [Page 3]
RFC 5869 Extract-and-Expand HKDF May 2010
Inputs:
PRK a pseudorandom key of at least HashLen octets
(usually, the output from the extract step)
info optional context and application specific information
(can be a zero-length string)
L length of output keying material in octets
(<= 255*HashLen)
Output:
OKM output keying material (of L octets)
The output OKM is calculated as follows:
N = ceil(L/HashLen)
T = T(1) | T(2) | T(3) | ... | T(N)
OKM = first L octets of T
where:
T(0) = empty string (zero length)
T(1) = HMAC-Hash(PRK, T(0) | info | 0x01)
T(2) = HMAC-Hash(PRK, T(1) | info | 0x02)
T(3) = HMAC-Hash(PRK, T(2) | info | 0x03)
...
The text was updated successfully, but these errors were encountered:
taikulawo
changed the title
shadowsocks 协议分析,及密码学基础
shadowsocks 协议分析及密码学基础
May 25, 2022
shadowsocks 协议分析
基础知识
nonce 和 salt 区别
salt 只用一次,用于生成 cipher,和 salt 类似的概念叫initial vector(初始化向量),可认为 salt 和 iv 是等价的概念。
nonce 每次 encrypt,decrypt都需要,而且 decrypt 时使用的nonce必须和那次encrypt的nonce一致。由于加解密的nonce必须一一对应,所以nonce往往采用双方提前约定的生成规则,一般都是每次使用完自加1,为下次使用做准备
信息完整性校验
针对 AEAD 加密,密文加密生成 HMAC tag。解密需提供 校验tag 用于数据完整性校验。所以加密方负责将 tag 传给解密方。一般来说,tag会附加到密文之后。
shadowsocks 协议格式
解密的首个数据包头会携带 salt 用于接下来计算解密key
之后的数据流格式为
由于key创建时需 salt,所以 shadowsocks-server 的 decryptor 的 key 是在收到首次数据包后才能创建出来的
加解密的函数式表达
psk 的推导需要 user_password,核心是OpenSSL的 EVP_BytesToKey
我贴下原文,D_i 代表第 i 轮,每一轮的计算为:
[上次计算的结果 + data + salt]
,直到 digest 之后的结果符合key长度要求以下是其他语言针对 psk 计算的实现
对于 shadowsocks#inbound, 第一个 encrypted payload 是目标地址,之后的则是 要转发的数据
对于 shadowsocks#outbound
首次数据包构造中,第一个 encrypted payload 必须是
target_address
,之后则是要转发的数据加解密只是真实的传递双方原始的数据,针对 shadowsocks#inbound
我们设定 EncryptedChunk 格式如下
则原始协议对应如下
加密后的 shadowsocks 数据流格式为
target_address 为 socks5的格式,定义如下
shadowsocks-client => shadowsocks-server
shadowsocks-client <= shadowsocks-server
连接都以 salt 开头
client => server 在 salt 之后的第一个 EncryptedChunk 为 target_address
server => client 在 salt 之后的 EncryptedChunk 则都为要转发的数据
密钥导出
pre shared master key(PSK) 生成
user passwd 加 md5计算而成
https://github.com/shadowsocks/shadowsocks-windows/blob/b52472ebd7f519032cbf8a0ed688d090eeb75b2e/Shadowsocks.Net/Crypto/Stream/StreamCrypto.cs#L54
session key 生成(真正用于加解密的key)
整个流程可简述为
HKDF-SHA1(key, salt, info) => subkey
HKDF 包含两个阶段,extract,expand
https://github.com/shadowsocks/shadowsocks-windows/blob/8fafef7c5751809751d8eef361e3c39caef75261/Shadowsocks.Net/Crypto/AEAD/AEADCrypto.cs#L90
上面的
HKDF.DeriveKey
同时包含extract, expand如果是分两步进行,大致下面模样
这里要说明下,由于用户输入的密码往往是简单的字符串,所以大多数实现并不直接使用 password 作为 key,而是先进行 KDF(key derivation function),将KDF得到的值作为key,
常见的 KDF 算法有 PBKDF2, HKDF,这些算法的特点是,都可以生成指定长度的 key
拿
AES-128-GCM
举个例子128 bit 的 key 长度,也就是 16 字节
其他的 salt, nonce 长度并未有强制性要求,有些实现库支持 variable length
http://shadowsocks.org/en/wiki/AEAD-Ciphers.html
具体长度则由应用自行决定
shadowsocks 要求如下
不同的加密算法所要求的 key size 不同,这就要求我们能任意生成不同长度的key
举个例子,HKDF 分extract,expand 两个阶段。extract 之后会得到一串伪随机数,expand 阶段可以指定长度,从而得到任意长度的 key。
密码学基础
MAC (Message authentication code)
消息验证码,是附加到原文之后的数据,用于持有密钥的接收方进行校验,可察觉数据是否被篡改,只是一个概念,并不对应具体的实现算法。
为什么 mac 需要一个key呢?直接做一边hash当成tag不就可以了吗?
假设
新阶段为
HMAC (hash based message authentication code)
是 MAC 的一种实现算法,底层使用 hash 函数生成 mac
所以HMAC也需要一个key
HMAC(key, text) => tag
HKDF (HMAC-based Extract-and-Expand Key Derivation Function)
将 HMAC 作为底层实现的密钥派生算法
KDF,即密钥派生算法,旨在通过一些初始的密码材料,派生出一个至多个密码学意义上,足够健壮的 密钥
HKDF 包含两个阶段,extract, expand
extract(salt, user_weak_password) => HKDF_struct
// If you don't have any info to pass, use an empty slice.
HKDF_struct.expand(info) => out_key
以下引用自 https://datatracker.ietf.org/doc/html/rfc5869, 我非常建议阅读,十分简单明了的RFC文档,介绍了每个数据的作用
The text was updated successfully, but these errors were encountered: