PAKE: Password-authenticated key agreement

1. 引言

PAKE,即Password-authenticated key agreement,密码认证秘钥协商。
在密码学中,PAKE是指:

  • 两方或多方进行交互
  • 基于一方或多方知悉某一共享密码建立的密码学秘钥

PAKE的一个重要属性是:

  • 窃听者或者中间人无法在不与其他方互动的情况下暴力破解密码

这也就意味着可基于弱密码获得强安全性。

PAKE相关算法主要有:

  • 平衡密码认证秘钥交换
  • 增强密码认证秘钥交换
  • 密码认证秘钥检索
  • 多server PAKE
  • 多方PAKE

在最严格的只使用密码的安全模型中,以上算法的用户不需要记住密码以外的任何秘密或公共数据。

相关代码实现参见:

  • https://github.com/RustCrypto/PAKEs

2. 平衡密码认证秘钥交换

平衡PAKE假设双方为client-client或client-server关系,使用相同的密码来协商认证一个共享秘钥。

平衡PAKE的例子主要有:

  • Encrypted Key Exchange (EKE):第一个成功的PAKE算法,由Bellovin和Merritt于1992发明,有缺陷。2000年Bellare等人和Boyko等人发明了第一个provably-secure PAKE协议,在random oracle model下被证明是安全的。第一个在standard assumption下的协议由Goldreich等人与2001年发明,但是效率不够高。第一个实用且安全的算法由M. Yung于2001年提出。
  • PAK and PPK
  • SPEK (Simple password exponential key exchange)
  • 基于椭圆曲线的Secure Remote Password protocol (EC-SRP or SRP5)。https://github.com/mobilesec/secure-channel-ec-srp-applet 为Java card实现。
  • Dragonfly-IEEE std 802.11-2012,RFC 5931, RFC 6617
  • SPAKE1 and SPAKE2
  • SESPAKE-RFC 8133
  • J-PAKE (Password Authenticated Key Exchange by Juggling) - ISO/IEC 11770-4(2017), RFC 8236
  • ITU-T Recommendation X.1035

3. 增强密码认证秘钥交换

增强PAKE用于client-server场景,其中server端不存储与密码等效的数据。这就意味着窃取服务器数据的攻击者仍然不能伪装成客户机,除非他们首先密码执行暴力搜索。

一些增强PAKE系统使用 不经意伪随机数函数 将用户的密码和服务端的密码salt值 混合,使得用户永远不知道服务端 的秘密salt值,而服务端也永远不知道用户的密码或最终秘钥。

增强PAKE例子有:

  • AMP
  • 增强EKE
  • B-SPEKE
  • PAK-X
  • SRP
  • AugPAKE
  • OPAQUE
  • SPAKE2+

4. 密码认证秘钥检索

密码认证秘钥检索 是指:

  • 客户端 通过与服务端基于密码协商获得一个静态秘钥
  • 服务端知道该密码的相关数据,如the Ford and Kaliski算法。在最严格的设置中,一方将密码与N(2个或多个)服务器结合使用来检索静态秘钥。即使N台服务器中的N-1台被破解了,仍能保护密码(和秘钥)的安全。

第一个密码认证秘钥检索算法由Ford和Kaliski于2000年提出。

5. 何为SPAKE?

以下内容主要摘自 Andalla等人2015年论文Simple Password-Based Encrypted Key Exchange Protocols。

password-based encrypted key exchange设计为:

  • 为一对用户在不可信通道上构建secure session key,即使两者之间共享的密码或secret key源自很小范围的一组值。

不同的秘钥交换协议各有利弊,如SIGMA协议[18]用于 基于签名模式的Internet Key Exchange (IKE)协议中。

Password-based key exchange 协议针对的是更实际的场景,即:

  • secret key并不是均匀分布在一个大的空间,而是来自于小的范围(如4-digit pin)。即人类容易记住的密码通常会更简单。
  • 应可抗字典攻击。
  • 仅限制对手的在线猜测攻击。即对手必须在线且必须与系统交互来验证其猜测是否正确。这类系统的安全性,通常依赖于设计一种当错误超过一定次数时,使密码失效或阻碍使用的策略。
  • 应可抗被动攻击。
  • 应可抗主动攻击。
  • 应可抗中间人攻击。

Encrypted key exchange:password-based key exchange领域的开创性工作来自于Bellovin和Merrit[7]的encrypted key exchange (EKE)协议。

尽可能少的使用random oracles。理论上,可如KOY 协议[6] 移除所有的random oracles,但是那样的效率要低于EKE协议。

5.1 存在中间人攻击的SPAKE设计

PAKE: Password-authenticated key agreement_第1张图片
以上设计存在中间人攻击问题:

  • 敌手选择其已知的 r r r,将User A的 X ∗ X^* X替换为 X ∗ ⋅ g r X^*\cdot g^r Xgr,将 X ∗ ⋅ g r X^*\cdot g^r Xgr发送给User B。
  • User A和User B之间创建的session key变为了 S K A SK_A SKA S K B = S K A ⋅ Y r SK_B=SK_A\cdot Y^r SKB=SKAYr
  • 一旦敌手知道了这2个session key,则可使用 Y = ( S K B / S K A ) − r Y=(SK_B/SK_A)^{-r} Y=(SKB/SKA)r进行离线字典攻击,同时敌手知道 Y ∗ Y^* Y,根据 Y ∗ = Y ⋅ N p w Y^*=Y\cdot N^{pw} Y=YNpw,于是可计算出秘密 p w pw pw

5.2 SPAKE1:为simple non-concurrent password-based encrypted key exchange

PAKE: Password-authenticated key agreement_第2张图片

5.2 SPAKE2:为simple concurrent password-based encrypted exchange

与SPAKE1相比,将session key的算法修改为:
S K ← H ( A , B , X ∗ , Y ∗ , p w , K ) SK\leftarrow H(A,B,X^*,Y^*,pw,K) SKH(A,B,X,Y,pw,K)

这样就支持参与者之间根据不同的秘密并行建立多个session key。

在https://github.com/RustCrypto/PAKEs/blob/master/spake2/src/lib.rs 中对SPAKE2 基于curve25519做了相应的实现。
其中ed25519_hash_to_scalar将bytes转换为scalar值,使用了 HMAC-based Extract-and-Expand Key Derivation Function (HKDF) 中的sha256算法:

fn ed25519_hash_to_scalar(s: &[u8]) -> c2_Scalar {
    //c2_Scalar::hash_from_bytes::(&s)
    // spake2.py does:
    //  h = HKDF(salt=b"", ikm=s, hash=SHA256, info=b"SPAKE2 pw", len=32+16)
    //  i = int(h, 16)
    //  i % q

    let mut okm = [0u8; 32 + 16];
    Hkdf::::new(Some(b""), s)
        .expand(b"SPAKE2 pw", &mut okm)
        .unwrap();
    //println!("expanded:   {}{}", "................................", okm.iter().to_hex()); // ok

    let mut reducible = [0u8; 64]; // little-endian
    for (i, x) in okm.iter().enumerate().take(32 + 16) {
        reducible[32 + 16 - 1 - i] = *x;
    }
    //println!("reducible:  {}", reducible.iter().to_hex());
    c2_Scalar::from_bytes_mod_order_wide(&reducible)
    //let reduced = c2_Scalar::reduce(&reducible);
    //println!("reduced:    {}", reduced.as_bytes().to_hex());
    //println!("done");
    //reduced
}

详细SPAKE2示例为:

// we implement a custom Debug below, to avoid revealing secrets in a dump
#[derive(PartialEq, Eq)]
pub struct SPAKE2 {
    //where &G::Scalar: Neg {
    side: Side,
    xy_scalar: G::Scalar,
    password_vec: Vec,
    id_a: Vec,
    id_b: Vec,
    id_s: Vec,
    msg1: Vec,
    password_scalar: G::Scalar,
}

#[test]
fn test_asymmetric() {
    let scalar_a = decimal_to_scalar(
        b"2611694063369306139794446498317402240796898290761098242657700742213257926693",
    );
    let scalar_b = decimal_to_scalar(
        b"7002393159576182977806091886122272758628412261510164356026361256515836884383",
    );
    let expected_pw_scalar = decimal_to_scalar(
        b"3515301705789368674385125653994241092664323519848410154015274772661223168839",
    );

    println!("scalar_a is {}", hex::encode(scalar_a.as_bytes()));

    let (s1, msg1) = SPAKE2::::start_a_internal(
        &Password::new(b"password"),
        &Identity::new(b"idA"),
        &Identity::new(b"idB"),
        scalar_a,
    );
    let expected_msg1 = "416fc960df73c9cf8ed7198b0c9534e2e96a5984bfc5edc023fd24dacf371f2af9";

    println!();
    println!("xys1: {:?}", hex::encode(s1.xy_scalar.as_bytes()));
    println!();
    println!("pws1: {:?}", hex::encode(s1.password_scalar.as_bytes()));
    println!("exp : {:?}", hex::encode(expected_pw_scalar.as_bytes()));
    println!();
    println!("msg1: {:?}", hex::encode(&msg1));
    println!("exp : {:?}", expected_msg1);
    println!();

    assert_eq!(
        hex::encode(expected_pw_scalar.as_bytes()),
        hex::encode(s1.password_scalar.as_bytes())
    );
    assert_eq!(hex::encode(&msg1), expected_msg1);

    let (s2, msg2) = SPAKE2::::start_b_internal(
        &Password::new(b"password"),
        &Identity::new(b"idA"),
        &Identity::new(b"idB"),
        scalar_b,
    );
    assert_eq!(expected_pw_scalar, s2.password_scalar);
    assert_eq!(
        hex::encode(&msg2),
        "42354e97b88406922b1df4bea1d7870f17aed3dba7c720b313edae315b00959309"
    );

    let key1 = s1.finish(&msg2).unwrap();
    let key2 = s2.finish(&msg1).unwrap();
    assert_eq!(key1, key2);
    assert_eq!(
        hex::encode(key1),
        "712295de7219c675ddd31942184aa26e0a957cf216bc230d165b215047b520c1"
    );
}

参考资料

[1] Wikipedia Password-authenticated key agreement
[2] Andalla等人2015年论文Simple Password-Based Encrypted Key Exchange Protocols
[3] SPAKE2, a PAKE draft

你可能感兴趣的:(基础理论)