Try   HackMD

密碼學數論 / 筆記

tags: 資安

費馬小定理 a^(p-1) = 1 (mod p)

p is prime

a(p1)1 (mod p)

延伸:

a^p = a (mod p)

apap1×a(mod p)1×a(mod p)a(mod p)

a^(p-2) = a^(-1) (mod p)

ap2ap1×a1(mod p)1×a1(mod p)a1(mod p)

(a+b)^p = a^p + b^p (mod p)

(a+b)pa+b(mod p)ap+bp(mod p)

尤拉定理

aϕ(n)1 (mod n)

ϕ(n): 在
n 的數中與 n 互質 (
) 的數

phi(n)

  1. p is prime:
    ϕ(p)=p1
  2. ϕ(pk)=pk1×(p1)
  3. m, n 互質:
    ϕ(m×n)=ϕ(m)×ϕ(n)

二次殘差 Quadratic Residues

定義: 如存在一數 a 使

a2n (mod p),則 n 為二次殘差 QR,否則為非二次殘差 QNR

性質:

QR * QR = QR
QR * QNR = QNR
QNR * QNR = QR

QR ^ e = QR

如何判斷一數是否為 QR:

勒讓德符號 Legendre Symbol

當 p 為奇數質數

(np)n(p1)/2 (mod p)={+1, n  QR1, n  QNR0, n0 (mod p)

尋找 a 的方式:

p = 3 (mod 4)

a=±n(p+1)/4

proof:

(±n(p+1)/4)2a(p+1)/2(mod p)a(p1+2)/2(mod p)a(p1)/2+1(mod p)a(p1)/2×a(mod p)1×a(mod p)a(mod p)

Tonelli-Shanks algorithm (p = 1 (mod 4))

wikipedia: Tonelli–Shanks algorithm

當 p 為質數

def tonelli_shanks(y2:int, p:int) -> int: assert pow(y2, (p-1)//2, p) == 1, "Not quadratic residues" assert p%2 == 1, 'p is not prime' if(p%4 == 3): y = pow(y2, (p+1)//4, p) return y # 1 S = 0 Q = p-1 while(Q % 2 == 0): Q //= 2 S += 1 assert Q*(2**S) == p-1 # 2 z = 2 while(pow(z, (p-1)//2, p) != p-1): z += 1 assert pow(z, (p-1)//2, p) == p-1 # 3 M = S c = pow(z, Q, p) t = pow(y2, Q, p) R = pow(y2, (Q+1)//2, p) # 4 while(True): if(t == 0): return 0 elif(t == 1): return R else: i = 1 t2 = pow(t, 2, p) for i in range(1, M): if((t2-1) % p == 0): break t2 = pow(t2, 2, p) b = pow(c, pow(2, M-i-1), p) M = i c = pow(b, 2, p) t = (t * c) % p R = (R * b) % p if __name__ == "__main__": y2 = 5 p = 41 y = tonelli_shanks(y2, p) assert pow(y,2,p) == y2 assert pow(-y,2,p) == y2

中國餘式定理 CRT

wikipedia: 中國剩餘定理

import numpy as np ai = [2, 3, 5] ni = [5, 11, 17] N = np.prod(ni, dtype=np.int) Mi = [int(N//x) for x in ni] ti = [pow(m,-1,n) for m, n in zip(Mi, ni)] ans = sum([a*t*m for a, t, m in zip(ai, ti, Mi)]) print(ans % N)

線性同餘方法 LCG

wikipedia: 線性同餘方法

一種產生偽隨機數的方法

公式:

ni+1(a×ni+b) (mod p)

特性: 當

nib×(1a)1 (mod p) 時,
ni=ni+1=ni+2=...

proof:

x(a×x+b)(mod p)(xa×x)b(mod p)(1a)×xb(mod p)xb×(1a)1(mod p)b×(1a)1(a×(b×(1a)1)+b)(mod p)

以下破解參考這篇文章

破解 LCG 1: 不知加數 (b)

假設已知

n0
n1
a
p
,不知
b

公式:

bn1a×n0 (mod p)

proof:

n1a×n0+b(mod p)bn1a×n0(mod p)

破解 LCG 2: 不知加數及乘數 (a, b)

假設已知

n0
n1
n2
p
,不知
a
b

公式:

a(n2n1)×(n1n0)1(mod p)
退化回上一題目

proof:

n1a×n0+b(mod p)n2a×n1+b(mod p)n2n1a×(n1n0)(mod p)21a(n2n1)×(n1n0)1(mod p)

破解 LCG 3: 不知加數、乘數及模數 (a, b, p)

假設已知

n0
n1
nk
(
k4
),不知
a
b
p


di=ni+1ni(0ik1)gi=di+1×di1di×di(1ik2)
公式:
p=GCD(g1,g2,...gk2)

退化回上一題目

proof:

d0n1n0(mod p)d1n2n1(n1×a+b)(n0×a+b)a×(n1n0)a×d0(mod p)d2n3n2(n2×a+b)(n1×a+b)a×(n2n1)a×d1(mod p)d3n4n3(n3×a+b)(n2×a+b)a×(n3n2)a×d2(mod p)...

g1d2×d0d1×d1((a×a×d0)×d0)((a×d0)×(a×d0))0(mod p)g2d3×d1d2×d2((a×a×d1)×d1)((a×d1)×(a×d1))0(mod p)...

g1=u1×p
g2=u2×p

GCD(g1,g2,...)=p

線性 DES/AES

塊加密不能是線性的,否則可以利用一些公式直接破解,通常相關題目設計時會在 sbox 上搞鬼

檢測線性

雖然以下圖片是 DES,但 AES 也是異曲同工之妙


[source]

DES(k,m1)DES(k,m2)DES(k,m3)=DES(k,m1m2m3)

import os from pwn import * def test_aes(cipher: AES) -> bool: key = os.urandom(16) m1 = os.urandom(16) m2 = os.urandom(16) m3 = os.urandom(16) res1 = xor(xor(cipher.encrypt(m1), cipher.encrypt(m2)), cipher.encrypt(m3)) res2 = cipher.encrypt(xor(xor(m1, m2), m3)) return res1 == res2 if __name__ == "__main__": cipher = AES(key, Sbox=sbox) isLinear = test_aes(cipher)

利用線性特性

參考這篇文章,可知線性 AES 可被 model 成

ct=A×pt+k 格式,只要求出 A 和 k 的值代表即可任意加解密

  • ct
    ,
    pt
    屬 GF(2) (i.e. bits)
  • A
    為一
    128×128
    的 GF(2) 矩陣,與 key 無關但與結構有關
  • k
    為一長度 128 的 GF(2) 向量,與 key 相關

求 A

p0=[000]
p1=[100]

p2=[0100]

pn=

p128=[001]

c0=A×p0+k

c1=A×p1+k

...

c128=A×p128+k

C=[c1c0,c2c0,,c128c0]=[(A×p1+k)(A×p0+k),,(A×p128+k)(A×p0+k)]=[A×(p1p0),,A×(p128p0)]=A×[(p1p0),,(p128p0)]=A×[p1,,p128]=A×P

求 k

c=A×p+k
k=cA×p

腳本

from pwn import xor from sage.all import GF, matrix, vector def bits2bytes(x: list) -> bytes: return bytes([int("".join(map(str, x[i:i+8])), 2) for i in range(0, len(x), 8)]) def bytes2bits(x: bytes) -> list: return list(map(int, "".join(map(lambda x: f"{x:08b}", x)))) def get_A(cipher: AES) -> matrix: # c = Ax+k # c-c0 = A(x-x0) # C = AX X = [] C = [] base = cipher.encrypt(bits2bytes([0]*128))[:16] # we don't need the cipher of padding for i in range(128): pt = [0]*i + [1] + [0]*(127-i) ct = cipher.encrypt(bits2bytes(pt))[:16] # we don't need the cipher of padding X.append(pt) C.append(bytes2bits(xor(ct,base))) mat_X = matrix(GF(2), X).transpose() mat_C = matrix(GF(2), C).transpose() mat_A = mat_X.solve_left(mat_C) return mat_A def get_k(cipher: AES, known_pt: bytes, known_ct: bytes) -> vector: vec_c = vector(bytes2bits(known_ct)) vec_x = vector(bytes2bits(known_pt)) vec_k = vec_c - mat_A * vec_x return vec_k def break_AES(cipher: AES, known_pt: bytes, known_ct: bytes, unknown_ct: bytes) -> bytes: pt = b"" mat_A = get_A(cipher) vec_k = get_k(cipher, known_pt, known_ct) # c = Ax+k # c-k = Ax for i in range(0, len(unknown_ct), 16): curr_ct = vector(bytes2bits(unknown_ct[i:i+16])) curr_pt = mat_A.solve_right(curr_ct - vec_k) pt += bits2bytes(curr_pt) return pt if __name__ == "__main__": cipher = AES(key, Sbox=sbox) plainText = bytes.fromhex(input("Input plaintext(hex): "))[:16] cipherText = bytes.fromhex(input("Input ciphertext(hex): "))[:16] ct_flag = bytes.fromhex(input("Input cipherflag(hex): ")) pt_flag = break_AES(cipher, plainText, cipherText, ct_flag) print(pt_flag)

DES 特殊性質

補數

Ek(P)=C
Ek¯(P¯)=C¯

Data Encryption Standard - Wikipedia

Weak key

在某些 key 的情況下,

Ek1(Ek2(P))=P

Weak key - Wikipedia
Recommendation for the Triple Data Encryption Algorithm (TDEA) Block Cipher

§ 3.4.2

single key

即在

k1=k2 的情況下有前者的特性

  • 0x0000000000000000
  • 0x0101010101010101
  • 0x1E1E1E1E0F0F0F0F
  • 0x1F1F1F1F0E0E0E0E
  • 0xE0E0E0E0F1F1F1F1
  • 0xE1E1E1E1F0F0F0F0
  • 0xFEFEFEFEFEFEFEFE
  • 0xFFFFFFFFFFFFFFFF

semi-weak key

k1k2 的情況

  • 0x011F011F010E010E + 0x1F011F010E010E01
  • 0x01E001E001F101F1 + 0xE001E001F101F101
  • 0x01FE01FE01FE01FE + 0xFE01FE01FE01FE01
  • 0x1FE01FE00EF10EF1 + 0xE01FE01FF10EF10E
  • 0x1FFE1FFE0EFE0EFE + 0xFE1FFE1FFE0EFE0E
  • 0xE0FEE0FEF1FEF1FE + 0xFEE0FEE0FEF1FEF1

possibly weak key

在某些 key 的情況下,切分出來的 subkey 只會產生 4 個 (而非 16 個)

以下為部分範例,詳細部分請參考Recommendation for the Triple Data Encryption Algorithm (TDEA) Block Cipher

§ 3.4.2

  • 0x01011F1F01010E0E
  • 0x1F01FEE00E01FEF1
  • 0x1F1F01010E0E0101
  • 0xE0E00101F1F10101
  • 0xE0E01F1FF1F10E0E
  • 0xFEFEE0E0FEFEF1F1

同態加密 (Paillier cryptosystem)

特性

encryption

n=p×q
g=n+1

r=rand(1,n)

encgm×rnmodn2

相當於是

memodn,此處
e
為自然指數

decyption

n=p×q
ϕ=(p1)×(q1)

μ=ϕ1modn

msg=cϕmodn21n×μmodn

相當於是

log(c)modn,此處
log
為以自然指數為基底的 log

additive homomorphic

同態加密有此特性:

f(x+y)=f(x)f(y)

因此可得出

f(x+1)=f(x)f(1)=f(x)g

有時可搭配 RSA 的 Related Message Attack 進行破解

example: prsa