Crypto

저장을 안해두니 다시 짜기 귀찮음;;

예제는 noxCTF2018 JavaCorporation


https://ctftime.org/task/6542



서버

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import socket
import threading
import random
from Crypto.Cipher import AES
 
key = 'NotGonnaHappenAA'
 
class ThreadedServer(object):
    def __init__(self, host, port):
        self.host = host
        self.port = port
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.sock.bind((self.host, self.port))
 
    def listen(self):
        self.sock.listen(20)
        while True:
            client, address = self.sock.accept()
            client.settimeout(60)
            threading.Thread(target = self.listenToClient,args = (client,address)).start()
 
    def getIV(self):
        return ''.join([chr(random.randrange(0256)) for i in range(16)])
 
    def encrypt(self, plaintext):
        iv = self.getIV()
        aes = AES.new(key, AES.MODE_CBC, iv)
        return iv + aes.encrypt(plaintext)
 
    def decrypt(self, ciphertext):
        aes = AES.new(key, AES.MODE_CBC, ciphertext[:16])
        return aes.decrypt(ciphertext[16:])
 
    def pkcs5(self, s):
        pad_len = ((-len(s)) % 16)
        if pad_len == 0:
            pad_len = 16
 
        return s + chr(pad_len) * pad_len
 
    def check_pad(self, s):
        pad_len = ord(s[-1])
        if pad_len > 16 or pad_len == 0:
            return False
 
        pad = s[-pad_len:]
        for byte in pad:
            if ord(byte) != pad_len:
                return False
 
        return True
 
    def listenToClient(self, client, address):
        while True:
            try:
                length = int(client.recv(2))
                if (length % 16 != 0 or length <= 16):
                    client.close()
                    break
                else:
                    ciphertext = client.recv(length)
                    plaintext = self.decrypt(ciphertext)
                    if self.check_pad(plaintext):
                        client.send('1')
                    else:
                        client.send('0')
 
            except Exception as e:
                print e
                client.close()
                return False
 
if __name__ == "__main__":
    ThreadedServer('0.0.0.0'3141).listen()
cs



어택

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import socket
 
BLOCK_SIZE = 16
 
def check_pad(cipher):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect(('127.0.0.1'3141))
    sock.send(cipher)
    result = b""
    for i in range(0x20,0x40):
        result += sock.recv(1)
    sock.close()
    return result
    
def unpadding_pkcs7(block):
    len_b = len(block)
    n_padd = ord(block[-1])
    if(n_padd==0):
        raise ValueError('bad padding')
    for c in range(0, n_padd):
        if(n_padd != ord(block[len_b-1-c])):
            raise ValueError('bad padding')
            
    unpad_block = block[:-n_padd]
    return unpad_block
 
def get_nextBlock_decrypt(cipher, iv):
    knownP = b""
    for padding_len in range(1,BLOCK_SIZE+1):
        candiP = b""
        result = b""
        for startIdx in range(0x00x810x20):
            sendFeed = b""
            for i in range(startIdx,startIdx+0x20):
                makeIV = b"\x00"*(BLOCK_SIZE-padding_len) + bytes([i ^ (iv[-padding_len]) ^ padding_len])
                for j in range(0,len(knownP)):
                    makeIV += bytes([(iv[len(makeIV)]) ^ (knownP[j]) ^ padding_len])
                sendFeed += str(len(makeIV+cipher)).encode('utf-8')+makeIV+cipher # length send
            result += check_pad(sendFeed)
        print(result)
        candiP = result.decode().find("1")
        candiP= bytes([candiP])
        knownP = candiP + knownP
    return knownP
    
def OraclePaddingAttack(cipher):
    KnownP = b""
    ciphers = []
    for i in range(0len(cipher), BLOCK_SIZE):
        ciphers.append(cipher[i:i+BLOCK_SIZE])
    for i, c in enumerate(ciphers[1:]):
        preIV = ciphers[i]
        KnownP += get_nextBlock_decrypt(c, preIV)
        print(KnownP)
    return KnownP
 
cipher = open("Encrypted.txt","rb").read()
print("len : %d " % len(cipher))
plain = OraclePaddingAttack(cipher)
print(plain)
cs



https://github.com/mwielgoszewski/python-paddingoracle


from paddingoracle import BadPaddingException, PaddingOracle  
from pwn import *

r = remote('chal.noxale.com', 3141)

with open('Encrypted.txt', 'rb') as f:
    data = f.read()

iv = data[:16]
cipher = data[16:]

class PadBuster(PaddingOracle):  
    def __init__(self, **kwargs):
        super(PadBuster, self).__init__(**kwargs)

    def oracle(self, data, **kwargs):
        r.send(bytes(48))
        r.send(iv+data)
        if r.recv(1) == '0':
            raise BadPaddingException

padbuster = PadBuster()
value = padbuster.decrypt(cipher, block_size=16, iv=iv)
print('Decrypted: %r' % (value))     






ECC calculator

2019. 11. 2. 00:57

RSA LSB Oracle Attack에서 키값이 바뀌거나 고정되어있을때 공격할 수 있는 방법이다.

이전과 다른 점은 N값이 계속 바뀐다는 점인데, 이는 한 비트씩 추출하는 방법으로 풀 수 있다.


2의 제곱들의 역원을 이용해서 한비트씩 추출해내는 방법으로 문제를 푸는 것인데,

기존의 LSB Attack 공격에 쓰이는 방법과 비교했을때, (기존에 이 블로그 writeup에 쓰인 방법들은 이진탐색 방법이였다.)

한 bit씩 추출하여 flag를 찾아낸다는 점에서 더 괜찮은 공격방법일 수 있다.


생각해보니 이 방법 자체가 엄청 좋은 것같다. ㅎㅎ...



example server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
from Crypto.PublicKey import RSA
from Crypto.Util.number import long_to_bytes, bytes_to_long
 
menu = """
1. Get PublicKey
2. Encrypt
3. Decrypt
4. Print FLAG
"""
 
with open("./flag""r") as f:
    flag = f.read().strip()
    
privkey = RSA.generate(1024)
= privkey.n
= privkey.e
= privkey.d
 
def encrypt(p):
    return pow(p, e, N)
 
def decrypt(c):
    return pow(c, d, N)%2
    
def main():
    global N,e,d
    print(menu.strip())
    choice = int(input("> "))
    if choice == 1:
        print("n is "+str(N))
        print("e is "+str(e))
    elif choice == 2:
        data = raw_input("msg : ")
        print("enc : "+str(encrypt(bytes_to_long(data))))
    elif choice == 3:
        data = input("msg (demical number) : ")
        print("dec : "+str(decrypt(data)))
        privkey = RSA.generate(1024)
        N = privkey.n
        e = privkey.e
        d = privkey.d
    elif choice == 4:
        print(str(encrypt(bytes_to_long(flag))))
    else:
        print("invalid menu")
 
welcome = """
 /$$$$$$$   /$$$$$$   /$$$$$$         /$$$$$$  /$$     /$$ /$$$$$$  /$$$$$$$$ /$$$$$$$$ /$$      /$$
| $$__  $$ /$$__  $$ /$$__  $$       /$$__  $$|  $$   /$$//$$__  $$|__  $$__/| $$_____/| $$$    /$$$
| $$  \ $$| $$  \__/| $$  \ $$      | $$  \__/ \  $$ /$$/| $$  \__/   | $$   | $$      | $$$$  /$$$$
| $$$$$$$/|  $$$$$$ | $$$$$$$$      |  $$$$$$   \  $$$$/ |  $$$$$$    | $$   | $$$$$   | $$ $$/$$ $$
| $$__  $$ \____  $$| $$__  $$       \____  $$   \  $$/   \____  $$   | $$   | $$__/   | $$  $$$| $$
| $$  \ $$ /$$  \ $$| $$  | $$       /$$  \ $$    | $$    /$$  \ $$   | $$   | $$      | $$\  $ | $$
| $$  | $$|  $$$$$$/| $$  | $$      |  $$$$$$/    | $$   |  $$$$$$/   | $$   | $$$$$$$$| $$ \/  | $$
|__/  |__/ \______/ |__/  |__/       \______/     |__/    \______/    |__/   |________/|__/     |__/
version 3.2.7                                                    
"""
 
print(privkey.d)
 
if __name__ == '__main__':
    print(welcome)
    while True:
        try: main()
        except: break
cs






attack code


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
from Crypto.Util.number import *
from Crypto.PublicKey import RSA
from pwn import *
 
conn = process(["python""server.py"])
 
def get_key():
    conn.sendlineafter("4. Print FLAG""1")
    conn.recvuntil("n is ")
    return int(conn.recvline().strip())
    
def get_flag():
    conn.sendlineafter("4. Print FLAG""4")
    conn.recvuntil("> ")
    return int(conn.recvline().strip())
 
def decrypt(ct):
    conn.sendlineafter("4. Print FLAG""3")
    conn.sendline(str(ct))
    conn.recvuntil("dec : ")
    result = int(conn.recvline().strip())
    return result
    
flag_enc = get_flag()
init_N = get_key()
 
# Public exponent
= 65537
 
bits = "1"
flag = ""
= 1
while True:
    enc = get_flag()
    N = get_key()
 
    inv = inverse(2**i, N)
    chosen_ct = (enc*pow(inv, e, N))%N
    output = decrypt(chosen_ct)
    flag_char = (output - ((int(bits, 2)*inv) % N)) % 2
 
    bits = str(flag_char) + bits
    if len(bits) % 8 == 0:
        flag = long_to_bytes(int(bits, 2))
        print(flag)
        if flag_enc == pow(int(bits, 2), e,init_N):
            break
    i+=1
cs


시간이 없어 암호 공부를 얼마간 못하다가 다시 시작하였습니다. 이번에는 CTR모드에 대해, 그리고 고정된 nonce를 사용하는 CTR모드를 공격하는 방법에 대해 알아보겠습니다. 참고로 공격대상이 되는 데이터는 영어평문문장입니다.


먼저 CTR모드에 대해 알아보겠습니다.


CTR(Counter)


카운터(Counter, CTR) 방식은 블록 암호를 스트림 암호로 바꿔주는 구조를 가집니다. CTR모드에서는 각 블록마다 현재 블록이 몇 번째인지 값을 얻어, 그 숫자(count)와 nonce를 결합하여 블록 암호의 입력으로 사용합니다. 그렇게 각 암호화를 통해 연속적인 난수를 얻은 다음 암호화하려는 문자열과 XOR하는 방식입니다.


이렇게 되면 CTR모드는 각 블록의 암호화 및 복호화가 이전 블록에 의존하지 않으며, 따라서 병렬적으로 동작하는 것이 가능해집니다. 이것은 암호화 및 복호화의 속도가 타모드에 비해 매우 빠르다는 것을 의미합니다. 이전에 있던 모드들은(CBC, CFB, OFB와 같은) 블록의 암호화 및 복호화가 이전 블록에 의존되게 때문에 각 블록이 독립적으로 동작할 수 없고, 때문에 암호화와 복호화가 순차적으로 이루어져야했습니다. 또한 암호화된 문자열 중 원하는 부분만 골라 복호화하는 것이 불가능하였으나, CTF모드에서는 이것이 가능합니다.


* 정리

  • 블록마다 1씩 증가하는 counter와 nonce를 결합하여 암호화하여 키 스트림을 만든다.
  • 키 스트림과 평문 블록을 XOR한 결과가 암호문 블록이다.
  • 블록 암호를 스트림 암호구조로 바꿔준다.
  • 암호화와 복호화가 같은 구조다. (XOR암호)
  • 병렬처리가 가능하다. (임의의 블록만 암/복호화가 가능하다)
  • Error propagation : 각 블록이 독립적이므로, 에러가 다른 블록으로 전파되지않는다.


자. 이제 CTR모드에 대해 알아보았으니, 이 모드에서 고정된 nonce를 사용할 경우 어떻게 공격할 수 있는지에 대해 살펴보겠습니다.


먼저 위에서 살펴보았듯이 CTR모드를 사용하게되면 블록암호를 스트림암호방식으로 사용할 수 있습니다. 즉, 패딩이 불필요해지고, 키스트림을 생성하고 나면 XOR암호화 동일해집니다.


그럼 이제 남은 일은 이 XOR암호를 공격하는 것입니다. 고정된 nonce인 경우, 여러개의 데이터를 암호화하게 되면 같은 counter값을 가진 구간에 대해선 모두 같은 키스트림을 가지고 XOR암호화되게 됩니다.



위와 같이 여러개의 데이터가 있을 때, 각 데이터의 첫번째 블록은 고정된 nonce | counter으로 만들어진 키스트림과 XOR을 통해 암호화되게 됩니다. 이 때 nonce가 고정되어있고, counter값은 블록의 인덱스를 나타내므로 각 데이터의 블록들은 모두 각각 같은 키스트림을 가지게됩니다.


그렇다면 각 데이터에서 같은 인덱스의 블록들을 모은다면, 같은 키스트림을 가지는 암호문 리스트를 얻을 수 있습니다.



키스트림의 길이는 블록길이와 같으므로, XOR암호에서 키 사이즈는 BLOCKSIZE입니다. 여기서는 블록암호로 AES-128을 사용하였으므로, 키 사이즈는 16입니다. 이제 Muliple XOR Cipher을 풀 차례입니다. 그러나 키 길이를 알 고 있으므로 저희는 이것을 Single XOR Cipher로 바꿀 수 있습니다. 키길이가 16이면, XOR수행시 같은 바이트값, 즉 SingleXorByte가 16byte마다 나타나는 것을 알 수 있습니다. 그러므로 이번에는 바이트단위로 암호문을 나누어 다시 새롭게 배열합니다.


아래는 만약 KEY가 XOR인 keySize가 3일 때를 나타내는 예시입니다.


위의 같은 색으로 칠해진 부분은 같은 단일 KEYBYTE에 의해 암호화됩니다. 그러므로 위 Ciphertext를 아래와 같이 새롭게 정렬해줄 수 있습니다.



이제 새롭게 정렬된 Ciphertext를 Single XOR Cipher공격으로 풀면 됩니다. 공격은 기본 BruteForce로 정확도를 높이기 위해 AlphabetScoring방식으로 진행합니다.



이제 각각에 대해 AlphabetScoring을 통해 KEYSTEAM을 알아낼 수 있습니다.



그럼 이제 실전으로 들어가보겠습니다. 먼저 AES-CTR 암호화에 사용할 데이터를 40개 준비합니다.


SSBoYXZlIG1ldCB0aGVtIGF0IGNsb3NlIG9mIGRheQ==
Q29taW5nIHdpdGggdml2aWQgZmFjZXM=
RnJvbSBjb3VudGVyIG9yIGRlc2sgYW1vbmcgZ3JleQ==
RWlnaHRlZW50aC1jZW50dXJ5IGhvdXNlcy4=
SSBoYXZlIHBhc3NlZCB3aXRoIGEgbm9kIG9mIHRoZSBoZWFk
T3IgcG9saXRlIG1lYW5pbmdsZXNzIHdvcmRzLA==
T3IgaGF2ZSBsaW5nZXJlZCBhd2hpbGUgYW5kIHNhaWQ=
UG9saXRlIG1lYW5pbmdsZXNzIHdvcmRzLA==
QW5kIHRob3VnaHQgYmVmb3JlIEkgaGFkIGRvbmU=
T2YgYSBtb2NraW5nIHRhbGUgb3IgYSBnaWJl
VG8gcGxlYXNlIGEgY29tcGFuaW9u
QXJvdW5kIHRoZSBmaXJlIGF0IHRoZSBjbHViLA==
QmVpbmcgY2VydGFpbiB0aGF0IHRoZXkgYW5kIEk=
QnV0IGxpdmVkIHdoZXJlIG1vdGxleSBpcyB3b3JuOg==
QWxsIGNoYW5nZWQsIGNoYW5nZWQgdXR0ZXJseTo=
QSB0ZXJyaWJsZSBiZWF1dHkgaXMgYm9ybi4=
VGhhdCB3b21hbidzIGRheXMgd2VyZSBzcGVudA==
SW4gaWdub3JhbnQgZ29vZCB3aWxsLA==
SGVyIG5pZ2h0cyBpbiBhcmd1bWVudA==
VW50aWwgaGVyIHZvaWNlIGdyZXcgc2hyaWxsLg==
V2hhdCB2b2ljZSBtb3JlIHN3ZWV0IHRoYW4gaGVycw==
V2hlbiB5b3VuZyBhbmQgYmVhdXRpZnVsLA==
U2hlIHJvZGUgdG8gaGFycmllcnM/
VGhpcyBtYW4gaGFkIGtlcHQgYSBzY2hvb2w=
QW5kIHJvZGUgb3VyIHdpbmdlZCBob3JzZS4=
VGhpcyBvdGhlciBoaXMgaGVscGVyIGFuZCBmcmllbmQ=
V2FzIGNvbWluZyBpbnRvIGhpcyBmb3JjZTs=
SGUgbWlnaHQgaGF2ZSB3b24gZmFtZSBpbiB0aGUgZW5kLA==
U28gc2Vuc2l0aXZlIGhpcyBuYXR1cmUgc2VlbWVkLA==
U28gZGFyaW5nIGFuZCBzd2VldCBoaXMgdGhvdWdodC4=
VGhpcyBvdGhlciBtYW4gSSBoYWQgZHJlYW1lZA==
QSBkcnVua2VuLCB2YWluLWdsb3Jpb3VzIGxvdXQu
SGUgaGFkIGRvbmUgbW9zdCBiaXR0ZXIgd3Jvbmc=
VG8gc29tZSB3aG8gYXJlIG5lYXIgbXkgaGVhcnQs
WWV0IEkgbnVtYmVyIGhpbSBpbiB0aGUgc29uZzs=
SGUsIHRvbywgaGFzIHJlc2lnbmVkIGhpcyBwYXJ0
SW4gdGhlIGNhc3VhbCBjb21lZHk7
SGUsIHRvbywgaGFzIGJlZW4gY2hhbmdlZCBpbiBoaXMgdHVybiw=
VHJhbnNmb3JtZWQgdXR0ZXJseTo=
QSB0ZXJyaWJsZSBiZWF1dHkgaXMgYm9ybi4=


base64로 인코딩된 평문을 40개 준비하였습니다. 이제 base64로 디코딩하여 평문을 40개 리스트에 넣고, 이를 AES_CTR로 암호화시켜 암호문 배열을 만들겠습니다.


plainlist = open("data.txt", "r").readlines()
plainlist = [b64decode(x) for x in plainlist]

cipherlist = [AES_CTR(x, KEY, nonce) for x in plainlist]


이제 암호문을 Coulumn으로 나눠서 다시 정렬하여, 위에서 설명했던 대로 AlphabetScoring을 통해 키를 구합니다.


def get_keyString(ciphers):
	keyString = ""
	for nColumnCipher in ciphers:
		Max_score = 0.0
		key = 0
		for sbyte in range(0,256):
			nomal_string = XOR_singleByte(nColumnCipher, sbyte)
			score = alphabet_score(nomal_string)

			if score > Max_score:
				Max_score = score
				key = sbyte
		keyString += chr(key)
	return keyString

maxLen = max([len(x) for x in cipherlist])
print("maxLen : %d " % maxLen)

nColumnCipher = []
for n in range(0, maxLen):
  nColumn = ""
  for c in cipherlist:
  	if len(c) > n:
  		nColumn += c[n]
  nColumnCipher.append(nColumn)
keyString = get_keyString(nColumnCipher)


결과를 한번 확인해보겠습니다.


decrypt_list = [XOR_with_Key(ciph, keyString) for ciph in cipherlist]

	for i, decrpyt in enumerate(decrypt_list):
		print("%d : %s" %(i, decrpyt))



대체로 성공적으로 복호화 완료된 것을 볼 수 있습니다. 뒷 부분은 평문이 제대로 복호화가 되지않는 결과가 조금씩 보이는데, 이것은 각 Coulumn으로 정렬하는데 있어 데이터가 부족하여 생긴 결과입니다. 이 부분은 어쩔 수 없이 수동으로 조금 씩 맞춰주거나, 아니면 단어를 가지고 Scoring을 수행하던지 해야하는 부분입니다. 저는 이 부분을 Fix_keyString이란 함수를 만들어 처리하였지만, 여기서는 설명하지않도록 하겠습니다.


이렇게 CTR모드의 공격에 대한 설명을 마치겠습니다. 아래는 CTR모드의 공격을 테스트하는 전체코드입니다.


* 전체코드

from pwn import p64
from base64 import b64decode
from Crypto.Cipher import AES
import os

#Letter Distribution - Exterior Memory  http://www.macfreek.nl/memory/Letter_Distribution
freq = {
' ' : 18.28846265,
'E' : 10.26665037,
'T' : 7.51699827,
'A' : 6.53216702,
'O' : 6.15957725,
'N' : 5.71201113,
'I' : 5.66844326,
'S' : 5.31700534,
'R' : 4.98790855,
'H' : 4.97856396,
'L' : 3.31754796,
'D' : 3.28292310,
'U' : 2.27579536,
'C' : 2.23367596,
'M' : 2.02656783,
'F' : 1.98306716,
'W' : 1.70389377,
'G' : 1.62490441,
'P' : 1.50432428,
'Y' : 1.42766662,
'B' : 1.25888074,
'V' : 0.79611644,
'K' : 0.56096272,
'X' : 0.14092016,
'J' : 0.09752181,
'Q' : 0.08367550,
'Z' : 0.05128469,
}

def alphabet_score(stringA):
	score = 0.0
	for c in stringA:
		c=c.upper()
		if c in freq:
			score+=freq[c]
	return score

def XOR_singleByte(str1, sbyte):
	str1 = str1.encode("hex")
	result = ""
	for i in range(0,len(str1),2):
		h1 = int(str1[i:i+2],16)
		h2 = sbyte
		result+= '{:02x}'.format(h1^h2)
	return result.decode("hex")



KEY = os.urandom(16)
nonce = p64(0)

BLOCK_SIZE = 16



def XOR_with_Key(str1, Key):
	str1 = str1.encode("hex")
	result = ""
	for i in range(0,len(str1),2):
		h1 = int(str1[i:i+2],16)
		h2 = ord(Key[(i/2)%len(Key)])
		result+= '{:02x}'.format(h1^h2)
	return result.decode("hex")

def AES_ecb_encrypt(plain, key):
	obj = AES.new(key, AES.MODE_ECB)
	cipher = obj.encrypt(plain)
	return cipher

def AES_CTR(data, key, nonce):
	xor_data = ""
	cnt = 0
	for i in range(0, len(data), BLOCK_SIZE):
		input = nonce + p64(cnt)
		xor_data += AES_ecb_encrypt(input, key)
		cnt+=1
	processing_data = XOR_with_Key(data, xor_data)
	return processing_data


def get_keyString(ciphers):
	keyString = ""
	for nColumnCipher in ciphers:
		Max_score = 0.0
		key = 0
		for sbyte in range(0,256):
			nomal_string = XOR_singleByte(nColumnCipher, sbyte)
			score = alphabet_score(nomal_string)

			if score > Max_score:
				Max_score = score
				key = sbyte
		keyString += chr(key)
	return keyString

def Fix_keyString(keyString, cipher, fix_plain):
	decrypt = XOR_with_Key(cipher, keyString)
	fix = XOR_with_Key(decrypt, fix_plain)
	fix_keyString = XOR_with_Key(keyString, fix)
	return fix_keyString


def main():
	plainlist = open("data.txt", "r").readlines()
	plainlist = [b64decode(x) for x in plainlist]

	cipherlist = [AES_CTR(x, KEY, nonce) for x in plainlist]
	maxLen = max([len(x) for x in cipherlist])
	print("maxLen : %d " % maxLen)

	nColumnCipher = []
	for n in range(0, maxLen):
		nColumn = ""
		for c in cipherlist:
			if len(c) > n:
				nColumn += c[n]
		nColumnCipher.append(nColumn)

	keyString = get_keyString(nColumnCipher)
	keyString = Fix_keyString(keyString, cipherlist[0], "I have met them at close of day")
	keyString = Fix_keyString(keyString, cipherlist[25], "This other his helper and friend")
	decrypt_list = [XOR_with_Key(ciph, keyString) for ciph in cipherlist]

	for i, decrpyt in enumerate(decrypt_list):
		print("%d : %s" %(i, decrpyt))

	# compare to original_plain & decrypt_plain
	for original, decrpyt in zip(plainlist, decrypt_list):
		print("o : " + original)
		print("d : " + decrpyt)

if __name__ == '__main__':
    main()



JohnTheRipper를 이용해 password를 한번 크랙하고 나면, 그 zip파일의 password가 저장되기때문에, 다시 한번 크랙을 시도할 시 아래와 같이 뜬다.


$JohnTheRipper/run/john zip.hash


Using default input encoding: UTF-8

Loaded 1 password hash (ZIP, WinZip [PBKDF2-SHA1 256/256 AVX2 8x])

No password hashes left to crack (see FAQ)

 


이럴 때는 --show를 인자에 추가해서 넣거나, JohnTheRipper/run/john.pot 파일을 지워주면 다시 한번 크랙할 수 있다.


$ JohnTheRipper/run/john zip.hash --show


candle.zip:stegosaurus:::::candle.zip-flag.txt


1 password hash cracked, 0 left



'Crypto' 카테고리의 다른 글

RSA LSB Oracle Attack ( + 키 값 계속 바뀔 시도 가능)  (0) 2019.10.12
[Crypto] CTR 모드 공격 Break fixed-nonce CTR  (0) 2019.09.17
배낭암호(Knapshot)  (0) 2018.11.26
An ECB/CBC Mode and ECB Attack  (0) 2018.08.10
Breaking_XOR_Cipher  (0) 2018.08.03

배낭암호(Knapshot)

2018. 11. 26. 17:00

관련 개념블로그

http://ezbeat.tistory.com/332

http://jihwan4862.tistory.com/22


암호학 팀원이 쓴글

http://chieze.tistory.com/7


격자줄이기는 다음에 차차..

관련 문제로는 H3XOR CTF의 Cryingbag문제가 있다. 

출제자 writeup


대부분이 Bruteforcing으로 풀었다고 하더라... 심지어 출제자도... 참고로 필자는 (역연산+알려진 플래그앞부분)으로 풀었었다.

역연산이 됬다는게 신기..


아래는 암복호화 소스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import random
from binascii import hexlify, unhexlify
from fractions import gcd
 
def genRandomSINumber(arr):
    s=sum(arr)
    return random.randrange(s+1,(s+1)<<2)
 
def genCoprime(n):
    x=n
    while gcd(n,x)!=1:
        x=random.randrange(n>>1+1,n)
    return x
 
def keyGenerate(bits=1024):
    if bits%4!=0:
        raise Exception("Weird bits")
    w = []
    for i in range(bits):
        w.append(genRandomSINumber(w))
 
    q = genRandomSINumber(w)
    r = genCoprime(q)
    pubkey = [(r * i) % q for i in w]
    privkey = (w, q, r)
    return pubkey,privkey
 
def encrypt(pubkey,msg):
    msgbits = bin(msg)[2:]
    if len(pubkey)<len(msgbits):
        raise Exception("Msg is too big")
    plainbits=['0']*(4-len(msgbits)%4)
    plainbits+=msgbits
    c=0
    for a,b in zip(map(int,plainbits),pubkey):
        c+=a*b
    return c
 
def egcd(a, b):
    if a == 0:
        return (b, 01)
    else:
        g, y, x = egcd(b % a, a)
        return (g, x - (b // a) * y, y)
 
def modinv(a, m):
    g, x, y = egcd(a, m)
    if g != 1:
        raise Exception('modular inverse does not exist')
    else:
        return x % m    
    
def decrypt(privkey,enc):
    w = privkey[0]
    q = privkey[1]
    r = privkey[2]
    
    g = modinv(r,q)
    
    plainbag = (enc*g)%q
    plainbits = ""
 
    for i in range(len(w)-1-1-1):
        if plainbag-w[i] >= 0:
            plainbag-=w[i]
            plainbits += "1"
        else:
            plainbits += "0"
    return int(plainbits[::-1],2)
    
if __name__ == '__main__':
    msg=input("Your Message : ")
    msg=int(hexlify(msg.encode()),16)
    bits=len(bin(msg))-2
    bits+=(4-bits%4)
 
    p,q=keyGenerate(bits)
    c=encrypt(p,msg)
    
    msg = decrypt(q,c)
    print(unhexlify(hex(msg)[2:]))
cs


An ECB/CBC Mode and ECB Attack

2018. 8. 10. 18:43

Breaking_XOR_Cipher

2018. 8. 3. 18:43

Zip Crack Password! rockyou

2018. 7. 30. 14:28

rockyou.txt - zip크랙 비밀번호 리스트~


https://github.com/brannondorsey/naive-hashcat/releases/download/data/rockyou.txt

'Crypto' 카테고리의 다른 글

An ECB/CBC Mode and ECB Attack  (0) 2018.08.10
Breaking_XOR_Cipher  (0) 2018.08.03
RSA 공격법  (2) 2018.07.25
xortool 사용하기  (0) 2018.07.03
크립토 공부 사이트  (0) 2018.04.09

RSA 공격법

2018. 7. 25. 13:19

RSA 공격법에 대해 한번도 정리해놓은적이 없어서... CTF 문제로 RSA가 나올때마다 헤매게 되어서 한번 정리해두는게 좋을 것 같다는 생각이 들어서 이렇게 정리하게 되었다.


공격법에는 여러가지가 있는데, 일단 대충 정리하면 아래와 같다.


Sage : https://sagecell.sagemath.org/

http://sage.skku.edu/

http://matrix.skku.ac.kr/sage/



▶ 개인키 유출

p,q,ϕ(n),d 중 단 하나만이라도 노출된다면 나머지 키들을 취득할 수 있다.


이건 당연한 것이, 원래 n=pq에서 n을 구하고 나면 p,q는 파기되어야하나, 이 p,q를 구할 수 있다면 역으로 모두 다 구할 수 있게된다.

확장된 유클리드 호제법이나 유클리드 호제법을 사용하면 간단.




▶ 소인수분해

n을 소인수분해할 수 있다면 위의 개인키 유출과 같다.

아래는 소인수분해 DB로 DB에 이미 등록되있는 소인수분해값이라면 찾아서 알려준다.


FatorDB : http://factordb.com/    


ECM : https://www.alpertron.com.ar/ECM.HTM



▶ 선택 암호문 공격(Chosen Ciphertext Attack)

줄여서 CCA라고 부른다. RSA가 갖는 "곱셈에 대한 준동형사상 (Homomorphism) 성질"을 이용한 공격이라고 한다.

RSA 같은 키로 생성된 서로 다른 암호문 두 개를 곱하면, 평문 두개의 곱을 암호화한 것과 그 결과가 같다.



Textbook RSA에서 많이 쓰이는 공격법이다. (Textbook RSA와 일반 RSA 차이)

Textbook RSA에서는 메세지의 서명과 그 확인에 많이 쓰이기 때문에, 이를 이용해서 내가 서명하지않은 메세지의 평문또한 복구가능하다.

말로만으로는 어려우므로 밑에 수식으로 다시 표현하겠다. (https://asecuritysite.com/encryption/c_c)



여기서 우리는 C를 알고 있고, 임의로 r^e를 만들어 C'를 만들고 C'에 대한 서명(d)을 할 수 있다. 

그렇다면 우리가 얻게 되는 값은 평문 M*r 이 되고, r은 우리가 만든 값이므로 M을 구할 수 있다.





 순환 공격(Cyclic Attack)

평문이 나올 때까지, 암호문을 계속해서 암호화하는 공격이다. 이 공격은 결국 암호문을 해독하지만 소인수분해와 동일한 복잡도를 가진다.

RSA는 모듈러 연산을 사용하기 때문에 평문 집합과 암호문 집합이 동일하다.
예) mod 4를 사용하는 경우 평문 집합 {1, 2, 3} , 암호문 집합 {1, 2, 3}
암호문은 평문에 대해 1:1 대응이기 때문에 평문에 대한 암호문은 반드시 존재한다.
그러므로 계속 암호화하다보면 언젠가는 다시 평문이 나오게 되어있다.
하지만 n은 보통 굉장히 크므로... 현실적으로는 불가능하다.


 잘못된 암호화 공격(Faulty Encryption)
공격자 Malory가 Alice와 Bob이 사용하는 통신 채널에 접속할 수 있다면 Malory는 전송되는 모든 것을 들을 수 있고 전송되는 것을 변경할 수도 있다.
Alice는 개인적으로 Bob에게 말하기를 원하지만 공개키를 알지 못한다. Alice는 Bob에게 메일을 보내 공개키를 요청한다.
그러나 전송 중에 Malory는 Alice의 공개키를 볼 수 있고 Bob의 공개 지수에서 (e, n)을 (e', n)으로 변경한다.

Alice는 잘못된 키를 받고, 준비된 메시지를 암호화하여 Bob에게 보냅니다. (Malory도 가져옵니다). 물론 잘못된 키가 사용 되었기 때문에 Bob은 해독 할 수 없습니다. 그래서 그는 Alice에게 이를 알리고 Bob이 자신의 공개키를 다시 보내는 것으로 시작하여 다시 시도하기로 동의합니다. 
이번에 Malory는 방해하지 않는다. Alice가 메시지를 다시 보내고 이번에는 올바른 공개 키로 암호화합니다.

Malory에는 이제 두 개의 암호문이 있습니다. 하나는 오류 지수로 암호화되고 다른 하나는 올바른 암호문으로 암호화됩니다. 
이제 공격자 Malory는 모듈러 N과 공개지수 e를 모두 알고 있습니다. 따라서 Alice가 두 암호문 모두 정확히 암호화했다고 가정할 때
이제 우리는 Common Modulus Attack 을 사용할 수 있습니다.



 유사소수

간단히 말해 n이 유사소수이면 소인수분해하기 쉽다는 것이다. 링크로 대체


Fermat factorization 공격

페르마소수(Fermat number)를 사용하게 되면 소인수분해가 매우 쉬워져서, N을 소인수분해하는데 걸리는 시간이 매우 짧아

p,q를 알 수 있고, 개인키를 얻을 수 있다.

예) [MeepwnCTF 2018] Old School (Fermat's factorization attack)

예) https://ctftime.org/task/6295

https://xerxes-break.tistory.com/397?category=679888

위 롸업에서 PLUS팀이 part1에서 페르마소수 인수분해 사용

(p/q의 값이 1에 근사하면 주어진 RSA 암호화 방식은 Fermat factorization 공격에 취약)

=> https://xerxes-break.tistory.com/450






 p-1 공격, 이차 체

 링크로 대체




 개인키 부분 취득

간단히 설명하면, 개인키의 일부분만 찾을 수 있어도 RSA를 털 수 있다는 뜻

이런 문제가 나온다면 공부하게 되겠지




 부채널 공격
(1) 시간차 공격(Timing Attack)
(2) 전력차 공격(Power Analysis Attack) 

부채널 분석(Side-Channel Analysis) 또는 부채널 공격(Side-Channel Attack)이란 과거 암호 해독 기법과는 달리 암호 연산 수행 시 장비에서 발생되는 전력 또는 전자파와 같은 정보를 이용해 비밀키를 찾는 방법이다. 
일반적으로 시간공격 (TA, Timing Attack), 전자기누출공격 (EMA, ElectroMagnetic emission Attack) 그리고 전력분석 공격(PA, Power analysis Attack) 등으로 나눌 수 있으며, 수집한 전력 또는 전자파의 일반적인 특성 또는 통계적인 분석기법을 활용한다. 
특히, 전력분석 공격은 하드웨어에서 ‘0’, ‘1’을 처리하는 소비되는 전력이 서로 다르다는 점을 이용하여, 장비에서 암호 알고리즘이 실행되는 동안의 소비 전력을 분석해 비밀키에 대한 정보를 얻어 내는 방법이다. 
이는단순 전력 분석(SPA, Simple Power Analysis), 차분 전력 분석(DPA, Differential Power Analysis) 및 상관 전력분석(CPA, Correlation Power Analysis) 등으로 나뉜다.

예시) 2016 암호경진대회 6번
공격자 Malory가 Alice와 Bob이 사용하는 통신 채널에 접속할 수 있다면 Malory는 전송되는 모든 것을 들을 수 있고 전송되는 것을 변경할 수도 있다.




 낮은 지수 공격(Low Exponent Attack)

공개키 e의 값이 매우 작을 때 사용하는 공격법 - 출처(https://en.wikipedia.org/wiki/Coppersmith%27s_attack)


컴퓨터 모듈러 연산의 속도를 위하여 e값으로 페르마 소수 F0, F2, F4를 사용하는 경우가 많다. 

e값이 작고 평문의 길이가 매우 짧을 경우, 보통 n의 값이 매우 크기 때문에 암호문 C가 모듈러 연산에 걸리지않거나 몇 번걸리지않는 경우가 있다. 

그래서 이 경우 C값을 그냥 e 거듭제곱근을 구해서 평문을 복호화해 낼 수가 있다.

예) [UIUCTF 2018] Hastad

ex) 공격법 python의 gmpy모듈을 이용


import gmpy를 하면 result, bool = gmpy.root( num, k)를 쓸수가 있는데; 
이게 num를 k제곱근한 값을 result에 저장하고 bool에 성공여부를 0과 1로 저장한다.

import gmpy2, gmpy2.root도 있다.




위 예시의 문제의 출제의도는 Hastad Attack이였으나, N값이 C값에 비해 매우 크고, e=3이였기 때문에 세제곱근을 구하는 것을 통해 평문을 그냥 복구할 수 있었다.




 위너 공격(Wiener's attack)

앞서 이야기한 낮은 지수 공격과 비슷한 느낌의 공격으로 Wiener's attack이 있다. - https://en.wikipedia.org/wiki/Wiener%27s_attack


Let  with . Let .
Given  with , the attacker can efficiently recover .


간단하게 위와 같다. 즉, 개인키 d가 작다면 이를 쉽게 복구할 수 있다는 것이다.

예시로는 PlaidCTF 2015 RSA 문제나 검색해보면 꽤 많은 문제들이 나온다.




 Common Modulus Attack

공격자가 두 수신자의 동일한 메시지 m의 두 암호문 c1, c2 와 수신자의 공개키 e1, e2가 서로소임을 알고 있다.





 하스타드 공격 (Hastad's Broadcast Attack)

Coppersmith 정리를 이용한 첫 번째 응용, Hastad가 제안했던 알고리즘을 개선한 내용

Bob은 평문 M을 암호화하여 P1,P2,…,Pk 라고 하는 여러명에게 동시에 전송한다.

각 P은 각기 RSA 공개키를 갖고있다. 그리고 평문M이 모든 Ni보다 작다고 하자. 

이런 상황에서 Oscar는 Bob이 모르게 이들의 통신을 도청할 수 있고, k개의 암호문을 가로 챌 수 있다. 

편의상, 모든 공개키 ei가 3이라고 하자. 이 경우, k≥3이면, Oscar는 암호문으로부터 평문 M을 복호화 할 수 있다. 

즉, Oscar가 세 개의 암호문 C1,C2,C3를 얻었다고 하자. 여기서, C1=M^3 modN1, C2=M^3 modN2, C3=M^3 modN3 이다. 

그리고, Oscar가 Ni를 소인수 분해하지 못하게 하기 위해, 서로 다른 i, j 에 대해 gcd(Ni,Nj)=1라고 가정할 수 있다. 

그러면, CRT을 이용해, C'=M^3 modN1N2N3을 만족하는 원소 C'를 구할 수 있다. 가정에서 M이 모든 Ni보다 작다고 했기 때문에 M^3 < N1N2N3이고 따라서 C'=M3 이 성립한다. 따라서, Oscar는 C'의 삼제곱근을 실수에서 연산하면, M을 얻을 수 있다. 일반적으로, 모든 공개키(public exponent) ei가 같다고 하고 "k≥ei" 이기만 하면 Oscar는 평문 M을 복호화할 수 있다. 이 공격법은 공개키 e가 작은 경우에 한한다. 


정리:

  • e=3, {n1, n2, n3}에 대해 동일한 평문 M을 3개 전송하였을 때

  • c1, c2, c3가 존재한다. (각각 (n1,e), (n2,e), (n3,e)로 암호화) 

  • 중국인의 나머지정리(CRT)를 이용하여 c^t == x (mod n1n2n3)를 계산

  • m^3 < n1n2n3 이기 때문에 c^t=m^3

예시)

[UIUCTF 2018] Hastad

2016 국가암호공모전 II-A 분야 1번문제


공격 python 코드


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import gmpy2
gmpy2.get_context().precision = 4096
 
from binascii import unhexlify
from functools import reduce
from gmpy2 import root
import gmpy
 
# Hastad's Broadcast Attack
# https://id0-rsa.pub/problem/11/
 
# Resources
# https://en.wikipedia.org/wiki/Coppersmith%27s_Attack
# https://github.com/sigh/Python-Math/blob/master/ntheory.py
 
EXPONENT = 3
 
CIPHERTEXT_1 = "ciphertext.1"
CIPHERTEXT_2 = "ciphertext.2"
CIPHERTEXT_3 = "ciphertext.3"
 
MODULUS_1 = "modulus.1"
MODULUS_2 = "modulus.2"
MODULUS_3 = "modulus.3"
 
 
def chinese_remainder_theorem(items):
    # Determine N, the product of all n_i
    N = 1
    for a, n in items:
        N *= n
 
    # Find the solution (mod N)
    result = 0
    for a, n in items:
        m = N // n
        r, s, d = extended_gcd(n, m)
        if d != 1:
            raise "Input not pairwise co-prime"
        result += a * s * m
 
    # Make sure we return the canonical solution.
    return result % N
 
 
def extended_gcd(a, b):
    x, y = 01
    lastx, lasty = 10
 
    while b:
        a, (q, b) = b, divmod(a, b)
        x, lastx = lastx - q * x, x
        y, lasty = lasty - q * y, y
 
    return (lastx, lasty, a)
 
 
def mul_inv(a, b):
    b0 = b
    x0, x1 = 01
    if b == 1:
        return 1
    while a > 1:
        q = a // b
        a, b = b, a % b
        x0, x1 = x1 - q * x0, x0
    if x1 < 0:
        x1 += b0
    return x1
 
 
def get_value(filename):
    with open(filename) as f:
        value = f.readline()
    return int(value, 16)
 
if __name__ == '__main__':
    C1 = get_value(CIPHERTEXT_1)
    C2 = get_value(CIPHERTEXT_2)
    C3 = get_value(CIPHERTEXT_3)
    ciphertexts = [C1, C2, C3]
 
    N1 = get_value(MODULUS_1)
    N2 = get_value(MODULUS_2)
    N3 = get_value(MODULUS_3)
    modulus = [N1, N2, N3]
    
    C_M = [(c,m) for c,m in zip(cipher, modulus)]
 
    C = chinese_remainder_theorem(C_M)
    M = int(root(C, 3))
 
    M = hex(M)[2:]
    print(unhexlify(M).decode('utf-8'))
cs


원본 : https://github.com/aaossa/Computer-Security-Algorithms/blob/master/11%20-%20H%C3%A5stad's%20Broadcast%20Attack/hastads-broadcast-attack.py#L7



 LSB Oracle Attack

CTF에 의외로 자주나오는 유형. 평문을 encrypt한 lsb값을 출력하는 oracle이 있을때

이 방법을 이용해서 문제를 풀 수 있다.

ex) 

https://xerxes-break.tistory.com/448

https://xerxes-break.tistory.com/449




 Coppersmith’s attack

자세한 내용 - 원문 - https://en.wikipedia.org/wiki/Coppersmith%27s_attack

Franklin-Reiter related-message attack

Coppersmith’s short-pad attack


참고할만한 블로그 글 - https://www.cryptologie.net/article/222/implementation-of-coppersmith-attack-rsa-attack-using-lattice-reductions/

Stereotyped messages

For example if you know the most significant bits of the message. You can find the rest of the message with this method.

The usual RSA model is this one: you have a ciphertext c a modulus N and a public exponent e. Find m such that m^e = c mod N.

Now, this is the relaxed model we can solve: you have c = (m + x)^e, you know a part of the message, m, but you don't know x. For example the message is always something like "the password today is: [password]". Coppersmith says that if you are looking for N^1/e of the message it is then a small root and you should be able to find it pretty quickly.


간단히 말하면, e가 매우 작고 (e=3정도) 평문 메세지 M을 알고 있고, 그 M의 뒷부분이 아주 조금 변하는 정도라면 이 공격을 통해 M을 구할 수 있다는 공격이다. 


예시문제 : 

[MeePwnCTF 2018] bazik




 오류 주입 공격(몽고메리알고리즘[Montgomery Ladder])

Montgomery Ladder 지수승 알고리즘을 이용하여 서명(S=m^d mod N)가 수행되는 동안 오류 주입 공격을 통해 오류가 여러개의 서명 값을 이용하여 비밀키 d를 복구하는 것.



Copper

https://asecuritysite.com/encryption/copper

https://medium.com/asecuritysite-when-bob-met-alice/so-what-was-the-problem-with-the-estonian-id-system-and-tpms-1ef02a9bde7f


[Back] With the ROCA (Return of the Coppersmith Attack) vulnerability an RSA private key can be recovered from the knowledge of the public key [article]. It has the CVE identifier of CVE-2017-15361. The vulnerability related to the Infineon RSA library on the Infineon Trusted Platform Module (TPM) firmware. It affected BitLocker with TPM 1.2 and YubiKey 4. In this case we calculate the prime number with Prime=k×M+(65537amodM):




'Crypto' 카테고리의 다른 글

Breaking_XOR_Cipher  (0) 2018.08.03
Zip Crack Password! rockyou  (0) 2018.07.30
xortool 사용하기  (0) 2018.07.03
크립토 공부 사이트  (0) 2018.04.09
마타하리 암호  (0) 2017.10.16

+ Recent posts