はまやんはまやんはまやん

hamayanhamayan's blog

CTFのCryptoにおける格子まとめ

CTFにおけるCrypto入門とまとめの1つです。

格子とLLL

LLLに関連する重要な問題

SVP: Shortest Vector Problem

  • SVP: Shortest Vector Problem, 格子Lの中で最も短い非零のベクトルを探す問題
    • 次元が4以下であれば多項式時間で解くことができる -> Gaussian Lattice Reduction
    • それ以上の場合は近似アルゴリズムを使う -> LLL(多項式時間ではある)
    • c=sum(a[i]x[i])、つまり、f(x) = a[1]x[1] + a[2]x[2] + ... + a[n]x[n] - c = 0みたいな形で、また、結果が(xの値が)小さいときに使えるかも
      • この情報だけを格子に入れ込むと、xが大きい解も出てきてしまうので、xの値を小さくするような制約を入れる必要がある
    • xの範囲は大きな値を入れると、f(x)がより小さくなるxを見つけることができるらしい
  • SVPを近似的に解くアルゴリズムがLLL
  • 似たような名前の難しい問題がいくつかあるみたい https://ctf-wiki.mahaloz.re/crypto/asymmetric/lattice/introduction/
    • γ-Approximate Shortest Vector Problem (SVP-γ)
    • Successive Minima Problem (SMP)
    • Shortest Independent Vector Problem (SIVP)
    • Unique Shortest Vector Problem (uSVP-γ)
  • 問題

CVP: Closest Vector Problem

LWE: Learning with Errors

LLLが使える問題

多変数1次方程式を解く

  • 特に解に制約が無い場合、LLLを使えば何かしらの整数解が出てくるので、それを答えとして採用できる
    • a[1] * x[1] + a[2] * x[2] + ... + a[n] * x[n] = yで、x[i]yが不明みたいな問題が解けるが、LLLでよく出てくる、パラメタに対して答えが結構小さいことが前提としてないと厳しいそうなので注意
      • TCP1P CTF 2024 - TCP51Primeだとa51のように51乗されていたので、既知のパラメタは3225bitだったが、求めたい答えは64bitとかになっていた
  • 結構時間がかかる?
    • a[1] x[1] + a[2] x[2] = yで停止するまで2分とかかかった。気長に待つ
  • "十分に小さい"
    • 全体が512bitsのとき、7個が128bits, 1個が256bitsが変数であれば解けた SECCON 2022 Qual - insufficient
    • 「不明変数のビット数 < mod pのpのビット数」である方が良い?分からん
  • メモ
    • x[1] + x[2] + ... + a[n] * x[n] = yみたいな感じで、係数が1の変数が複数(x[1]とx[2]みたいな)あると、求まった値が不定になる。値をどう分配するかみたいな感じになって、x[1]+x[2]は固定だけど、x[1]+δ, x[2]-δでどうとってもいいみたいな状態になる
    • bit感覚
      • SECCON CTF 2020 sharsable e1 * d1 + e2 * d2 = 1 mod phi e1 * d1 + e2 * d2 = 1 + k(n - p - q + 1) この時、e1, e2はnと同じbitで、d1,d2は163bitsだったとする このとき、(n - p - q + 1)はnと同じbitなので、kは等式であるため、163bitsくらいであると見積もれる。
    • 二次元とかであっても、その部分のbit長が大丈夫ならば1変数として変換してやれば1次方程式になる
  • 特殊なLLLを使ってbytes_to_int(m) mod pが分かっているときにbytes列mを復元する
    • https://adib.au/2023/onelinecrypto/
    • bytes列mは印字可能文字で構成されているときに使えるテク。一意には定まらず、複数候補が出てくるので目grepして選別する
  • 資料
  • 問題
  • 資料 https://www.math.fsu.edu/~hoeij/spring2018/compalg/LLL.pdf

連立方程式を解くとき / Inequality Solving with CVP

ナップサック問題 / Merkle-Hellmanナップサック暗号の解読

  • Merkle-Hellmanナップサック暗号
  • 低密度攻撃
    • 密度 = 平文のビット数/暗号文の平均ビット数、平文空間と暗号文空間の大きさの違い。密度 d = n/(log2 max a[i])
    • 密度が小さい場合、部分和問題を(近似)最短ベクトル問題として解読可能
      • LO法:d < 0.64 のナップサック暗号を解読
      • CLOS法:LO法で解読できる密度を d < 0.94 まで改良
  • 高密度攻撃
    • 密度が1を超える場合。この場合は、平文空間 > 暗号文空間であり、1つの暗号文を生成する平文が複数存在し、つまり、一意に平文を復元できない

LO法

  • 改善策
    • 右端の列の各要素にsqrt(n)よりも大きな値をかける → 最後の項の非零を全体のノルムに対して支配的に影響させることで、最後の項を必ず0にしようとする
    • 全てのxの値を1-xになるようにする -> もし答えに1が多い場合、0と1が反転することでベクトルが小さくなり、正答が出やすくなるかも
  • 密度
    • 全てのナップサック問題が解けるわけではなく、入力の要素数n、要素のビット幅をbとしてn/bが0.645...未満のほとんどすべての問題が解ける

CLOS法

  • 最小化したい答えを0,1で出すのではなく、-1/2、1/2で出すように変更したもの
    • 元々の方程式を考えると答えの最後の列よりx[n+1]=-1となるため、他の列の部分はx[i]-1/2が入っていることになる
    • つまり、答えは0,1ではなく、0ならば-1/2、1ならば1/2が入ることになり、よりノルムを最小化することができる
      • 単純に考えると0,1だとノルムが1で、-1/2,1/2だとノルムが1/2になるので半分になる
  • これで密度0.9408...まで解ける

実際やってみると、x[1] + x[n+1]/2くらいの傾斜じゃうまくいかなかったので、もっと傾きを大きくするとうまくいった。

Hidden Number Problem

AGCD: Approximate Greatest Common Divisors

Hidden Subset Sum Problem

  • h=xA でxがGF(p)nであり、Aがn×m行列で値は0か1であり、h,pが与えられているときに行列Aを復元する問題
  • 解き方
    • Nguyen-Stern algorithmで解ける
      • Orthogonal Lattice Attackという格子基底簡約が出てくる
    • FastICAと呼ばれる独立主成分分析の手法でも解ける
  • 問題

他、解ける問題

  • Coppersmith's Attack 小さい解をもつ合同方程式の解法
  • truncated LCGs
  • RSA with small public exponent
  • RSA with small private exponent
  • (EC)DSA with bad nonces

格子暗号

  • 格子暗号: 格子問題の困難性を安全の根拠とする暗号方式。実際に方式を構成するときはLWE問題などを利用
    • SVP/CVPベース
      • GGH暗号(1997): CVPの困難性に依存
      • NTRU: SVPの困難性に関連(やや複雑)
    • LWEベース: 今はこっちが主流で、NIST PQC標準候補の多くがLWEベース(Kyber, Dilithiumなど)
      • Regev暗号(2005): LWE問題に基づく
      • 多くの現代的な格子暗号: LWEやその変種(Ring-LWE, Module-LWEなど)
      • FHE(完全準同型暗号)の多くもLWEベース
  • 様々な格子暗号
    • 公開鍵暗号
      • Regev暗号
        • LWE問題を安全性の根拠とする(ちなみに、LWE問題は格子問題を安全性の根拠としている)
        • CRYSTALS-KYBER方式の(かなり大雑把な意味で)ベースとなる暗号方式
    • ディジタル署名
    • IDベース、属性ベース暗号
    • (量子)完全準同型暗号: Fully Homomorphic Encryption, FHE
      • Gentry-Sahai-Waters FHE: LWEを安全性の根拠としている。暗号文のまま、NANDが計算可能
        • Bootstrapping: 準同型演算をすると暗号文に含まれる(多分LWEにおける)ノイズが大きくなり、暗号文を正しく復号できなくなるので、暗号文内のノイズを適宜減らす必要があり、その減らす処理のこと
          • NANDの準同型演算毎にbootstrappingすることで無制限に準同型暗号できる
    • 量子性検証、量子計算検証プロトコル
      • 典型的には,LWE問題の困難性の下で構成されるclaw-free functionを用いて構成される
      • Noisy trapdoor claw-free function (NTCF)
  • 格子暗号への平文・鍵確認オラクルを用いた鍵回復攻撃とサイドチャネル攻撃・故障利用攻撃 https://joint.imi.kyushu-u.ac.jp/wp-content/uploads/2021/11/903a57de196bb2ff6b2f45e9fb460029-1.pdf
  • https://joint.imi.kyushu-u.ac.jp/wp-content/uploads/2022/08/220803_01yasuda.pdf
  • 格子暗号をベースにした準同型型暗号公式
  • 概要
    • LWE: Learning With Errors
      • (A, <A,S>+e)
        • Sは秘密要素であり、線形関数を定義する
        • eは良く既知の分布を持つ、小さいエラー
        • Aは既知の要素で<A,S>は行列AとベクタSを積を意味する
      • 特徴
        • 平文係数と暗号文係数という2つの異なる係数の下でのモジュラー演算を使用する
        • 秘密鍵はnを法とするベクトル空間の要素
        • メッセージは、エンコードされたノイズの多いメッセージを大きなドット席に追加することによって暗号化される
    • LWEを暗号化に活用する場合はエラー部分に埋め込む
      • (A,<A,S>+encoded(m,e))のようにする
      • Aはランダムな要素を持つベクトル空間で、それを秘密鍵のSと積を取って、メッセージを埋め込んだエラーと和を取る
      • Aは共有されるので(多分)、秘密鍵Sが分かっていれば、<A,S>を引いてエンコードされた平文とエラーが得られる
      • 特殊なエンコード方式によってエラーのみ削除することができるので平文のみ取得することができる
    • 方式は2つある
      • high-bitsに平文を埋め込む方式: Regev09, BFV
      • low-bitsに平文を埋め込む方式: BGV
  • 資料

NTRU, SVPをベースとした耐量子暗号

Regev暗号, LWEをベースとした暗号

  • LWE High Bits Message: こっちの方が復号がより安定し、より一般的
    • パラメタ(共有)
      • ベクトル空間の次元: n
      • 暗号文の剰余: q
      • 平文の剰余: p (平文m < pである場合に暗号化可能。RSAと同じ感じね)
      • scaling factor: Δ=round(q/p)
    • 鍵生成
      • 秘密鍵Sをqを法とした有限体の上でn元のベクトルとして作成
    • 暗号化
      1. qを法とした有限体の上でAを生成する
      2. エラーeを[-Δ/2,Δ/2]の範囲の整数として生成する
        • 離散ガウス分布上でサンプリングするのが一般的だが、一様分布でもいいらしい
      3. b = <A,S>+Δ*m+eを計算する
        • Δ*m+eがencoded部分で、パラメタを良い感じにやらないとちゃんと復元できなかったりするらしい。Δ⋅m+e<q/2である必要がある
      4. (A,b)が公開鍵
    • 復号化
      1. x = b - <A,S> mod qを計算し、xを得る(qを法として計算しているが計算後のxではmod qは無視する)
      2. m = round(x/Δ)でメッセージを取得
  • LWE Low Bits Message
    • パラメタ(共有)
      • ベクトル空間の次元: n
      • 暗号文の剰余: q
      • 平文の剰余: p (平文m < pである場合に暗号化可能)
      • p,qは互いに素
    • 鍵生成
      • 秘密鍵Sをqを法とした有限体の上でn元のベクトルとして作成
    • 暗号化
      1. qを法とした有限体の上でAを生成する
      2. エラーeを[-(q/p)/2,(q/p)/2]の範囲の整数として生成する
        • 離散ガウス分布上でサンプリングするのが一般的だが、一様分布でもいいらしい
      3. b = <A,S>+m+p*eを計算する
      4. (A,b)が公開鍵
    • 復号化
      1. x = b - <A,S>を"centered modulo qで"計算し、xを得る
        • 通常の[0,q)ではなく(−q/2,q/2]でmodを取る
      2. m = x mod pでメッセージを取得
        • m+p⋅e<q/2でないと復元できない