Some Tricks of Symmetric Cryptography

Symmetric Cryptography means this encryption algorithm use the same key for encryption and decryption. Although I am a web dog, encryption algorithm in some web question recently let me broken heart.

1. Some Symmetric Encryption Algorithm

DES、3DES、TDEA、Blowfish、RC2、RC4、RC5、IDEA、SKIPJACK

Here is another blog for S-DES which i wrote before.

2. Symmetric Cryptography in CTF-WEB

here are some crypto in ctf.which i know most is Padding Oracle or Cbc Flipped Ciphertext Bits.
for example CBC in NJCTF or shiyanba easy login question and so on . some i want introduce some Padding Oracle Tricks in ctf_web.

3. Some Encryption mode

Symmetric encryption has two types of encryption modes, namely block encryption and stream encryption.but in AES algorithm has five modes.if you want to find a aes decrypto/encrypto online mabey you need choice which mode you want use.
for example

Electronic Codebook Book (ECB)
Cipher Block Chaining (CBC)
Counter (CTR)
Cipher FeedBack (CFB)
Output FeedBack (OFB)

3.1. Electronic Codebook Book (ECB)

ECB is the simplest mode is encryption modes.

As we can see,we just need block the plaintext.Encryto every piece,than split joint every ciphertext.this is ECB encryption mode.

3.2. Cipher Block Chaining (CBC)

In this mode, the plaintext is first divided into several segments, and then each segment is XOR with the initial block(first segments) or the ciphertext segment of the previous segment, and then encrypted with the key.

Explain this encryption flow chart:
• Ciphertext-0 = Encrypt(Plaintext XOR IV)—for first plaintext segment
• Ciphertext-N= Encrypt(Plaintext XOR Ciphertext-N-1)—othere plaintext segments
Decyption as same.

•Plaintext-0 = Decrypt(Ciphertext XOR IV) —for first ciphertext segment
•Plaintext-N= Decrypt(Ciphertext XOR Ciphertext-N-1)—othere ciphertext segments

4.CBC Byte Flipping Attack

Purpose of CBC Byte Flipping Attack:To change a byte in the plaintext by corrupting a byte in the ciphertext.

4.1.Working Principle

Note: The Ciphertext-N-1 is used to generate the plaintext of the next block; this is where the byte flipping attack comes into play. If we change one byte of the Ciphertext-N-1 then, by XORing with the net decrypted block, we will get a different plaintext! You got it? Do not worry, we will see a detailed example below. Meanwhile, below is a nice diagram explaining this attack

And this is we know in math

1
2
3
4
5
Now_Plantext(A) = Now_Ciphertext(B) ^ Last_Ciphertext(C)
A = B ^ C
A ^ A = 0; 0 ^ A = A
C = A ^ A ^ C = B ^ C ^ A ^ C = A ^ B
ascii('a') ^ C ^ A ^ B = ascii('a') ^ A ^ B ^ A ^ B = ascii('a') ^ 0 = ascii('a')

so if we want let answer become “a”,we just need Last_Ciphertext ^ Now_Plantext ^ Now_Ciphertext ^ "A"

4.2.For example if we can change iv

1
2
3
KEY = 'mHAxsLYz'
vi = "qwerasdf"
admin->bdmin

This env len(vi==Key),so we just need new_cipher=bytes([ord(chr(cipher[0]))^ord('a')^ord('b')])+cipher[1:]

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
from pyDes import des, CBC, PAD_PKCS5
import binascii

KEY = 'mHAxsLYz'
vi = "qwerasdf"
def des_encrypt(s):
secret_key = KEY
iv = vi
k = des(secret_key, CBC, iv, pad=None, padmode=PAD_PKCS5)
en = k.encrypt(s, padmode=PAD_PKCS5)
return binascii.b2a_hex(en)

def des_descrypt(s,iv):
secret_key = KEY
iv = iv
k = des(secret_key, CBC, iv, pad=None, padmode=PAD_PKCS5)
de = k.decrypt(binascii.a2b_hex(s), padmode=PAD_PKCS5)
return de

str = des_encrypt("admin")
cipher = bytes(vi,encoding='utf-8')
print(cipher)
x = bytes([ord(chr(cipher[0]))^ord('a')^ord('b')])+cipher[1:]
print(x)
str3 = des_descrypt(str,x)
print(str3)


In php we use those func for cbc

1
2
openssl_encrypt($plain, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv);
openssl_decrypt($cipher, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv)

4.2.1.Question of only change iv

source_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
<?php 
error_reporting(0);
define("METHOD", "aes-128-cbc");
define("SECRET_KEY", "XXXXX");
$flag = "flag{this_is_flag}";
session_start();

function get_random_token(){
$random_token='';
for($i=0;$i<16;$i++){
$random_token.=chr(rand(1,255));
}
return $random_token;
}

function get_identity()
{
$defaultID = "heheda";
$token = get_random_token();
$_SESSION['id'] = base64_encode(openssl_encrypt($defaultID, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $token));
setcookie("token", base64_encode($token));
$_SESSION['isadmin'] = false;
}

function is_admin()
{
if(isset($_SESSION['id'])){
$token = base64_decode($_COOKIE['token']);
if($id = openssl_decrypt(base64_decode($_SESSION['id']), METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $token)){
if($id == "admin")
$_SESSION['isadmin'] = true;
} else {
die("Error!");
}
}
}

if(!isset($_SESSION['id']))
get_identity();
else {
is_admin();
if ($_SESSION["isadmin"]){
echo "You are admin!\n";
die($flag);
}else
echo "You are not admin!\n";
}
?>

exp1 for py2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# -*- coding: UTF-8 -*-
import base64 as b64
import binascii
import requests

source_str = 'heheda' + 10 * '\x0a'
target_srt = 'admin' + 11 * '\x0b'
token = 'eANhyUAiu6jfkh5CvDnkpQ==' #you got token
token = list(b64.b64decode(token))
#print(token)
for x in xrange(0,len(target_srt)):
token[x] = chr(ord(token[x]) ^ ord(target_srt[x]) ^ ord(source_str[x]))
print token
print b64.b64encode(''.join(token))

exp2 for py3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# -*- coding: UTF-8 -*-
import base64 as b64
import binascii
import requests

source_str = 'heheda' + 10 * '\x0a'
target_srt = 'admin' + 11 * '\x0b'
token = 'yxC20Pw32gbpCPjLc5qNRw==' #you got token
token = bytearray(b64.b64decode(token))
for x in range(0,len(target_srt)):
token[x] = token[x] ^ ord(target_srt[x]) ^ ord(source_str[x])
print(b64.b64encode(token))

#cQJkxUpIuqnekx9DvTjlpA==

4.3.change cipher&&iv

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
#coding:utf-8
from Crypto.Cipher import AES
from binascii import b2a_hex,a2b_hex
key="1234567890123456"

def encrypt(iv,plaintext):
if len(plaintext)%16 != 0:
print "plaintext length is invalid"
return
if len(iv) != 16:
print "IV length is invalid"
return
aes_encrypt = AES.new(key,AES.MODE_CBC,IV=iv)
return b2a_hex(aes_encrypt.encrypt(plaintext))

def decrypt(iv,cipher):
if len(iv) != 16:
print "IV length is invalid"
return
aes_decrypt = AES.new(key,AES.MODE_CBC,IV=iv)
return b2a_hex(aes_decrypt.decrypt(a2b_hex(cipher)))

def test():
iv="ABCDEFGH12345678"
plaintext="0123456789ABCDEFhelloyoucgetflag"
print "plaintext:"+plaintext
cipher=encrypt(iv, plaintext)
print "cipher:"+cipher
de_cipher = decrypt(iv, cipher)
print "de_cipher:"+de_cipher
print "de_cipher:"+a2b_hex(de_cipher)

def test_cbc_15():
iv="ABCDEFGH12345678"
plaintext="0123456789ABCDEFhelloyoucgetflag"
print "plaintext:"+plaintext
cipher=encrypt(iv, plaintext)
print "cipher:"+cipher
bin_cipher = bytearray(a2b_hex(cipher))
bin_cipher[15] = bin_cipher[15] ^ ord('g') ^ ord('G')
de_cipher = decrypt(iv,b2a_hex(bin_cipher))
print "de_cipher2:"+de_cipher
print a2b_hex(de_cipher)

def test_cbc_all():
iv="ABCDEFGH12345678"
plaintext="0123456789ABCDEFhelloyoucgetflag"
print "plaintext:"+plaintext
cipher=encrypt(iv, plaintext)
print "cipher:"+cipher
bin_cipher = bytearray(a2b_hex(cipher))
bin_cipher[15] = bin_cipher[15] ^ ord('g') ^ ord('1')
de_cipher = decrypt(iv,b2a_hex(bin_cipher))
bin_decipher = bytearray(a2b_hex(de_cipher))
bin_iv = bytearray(iv)
for i in range(0,len(iv)):
bin_iv[i] = bin_iv[i] ^ bin_decipher[i] ^ ord('X')
de_cipher = decrypt(str(bin_iv),b2a_hex(bin_cipher))
print "de_cipher3:"+de_cipher
print a2b_hex(de_cipher)
test_cbc_all();

5.CBC Padding Oracle

5.1.Q

padding_serv.py

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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
'''
This server is a modified version of the previous one.
This server will send you some data encrypted in CBC mode.
You can request decryptions of ciphertexts. The server will tell you if your ciphertext
decrypts to something with valid padding, but you won't get the plaintext back.
This is one of those obscure crypto implementation details that you can use to completely
defeat the cryptosystem.
The Secret is in the decrypted ciphertext.
Good luck!
Ciphertexts are sent back and forth as ASCII Encoded Hex Strings. 0xFF will be sent as
"FF" (2 Bytes), not as "\xff" (1 Byte).
You can use python's string.encode('hex') and string.decode('hex') to quickly convert between
raw data and string representation if you need/want to.
Email biernp@rpi.edu with questions/comments :)
-Patrick Biernat
'''

from twisted.internet import reactor, protocol
from Crypto.Cipher import AES
import os
import random

PORT = 9004

KEYSIZE = 16
KEY = "AAA" + "BBB" + "CCC" + '\x01' + "\x80" * 6
IV = os.urandom(16)
SECRET = "A" * 16 + "d4t_!nf0_l34k_y0"


def pad(instr, length):
'''
Generate valid PKCS#7 Padding
'''
if(length == None):
print "Supply a length to pad to"
elif(len(instr) < length):
return instr + chr((length - len(instr))) * (length - len(instr))
elif(len(instr) % length == 0):
#Add a block-length worth of padding.
return instr + (chr(length) * length)
else:
return instr + chr((length - (len(instr) % length ))) * (length - (len(instr) % length ))


def valid_padding(instr):
'''
Determine if valid PKCS#7 Padding is present in instr.
'''
#Grab the last byte of instr.
length = instr[-1]
# print length.encode('hex')
test = instr[-1 * int(length.encode('hex'),16):]
test = test.replace(length,"")
if (test != ""):
return False
return True

def encrypt_block(key, plaintext):
encobj = AES.new(key, AES.MODE_ECB)
return encobj.encrypt(plaintext).encode('hex')

def decrypt_block(key, ctxt):
decobj = AES.new(key, AES.MODE_ECB)
return decobj.decrypt(ctxt).encode('hex')

def xor_block(first,second):
'''
Return a string containing a XOR of bytes in first with second
'''
if(len(first) != len(second)):
print "Blocks need to be the same length!"
return -1

first = list(first)
second = list(second)
for i in range(0,len(first)):
first[i] = chr(ord(first[i]) ^ ord(second[i]))
return ''.join(first)

def encrypt_cbc(key,IV, plaintext):
'''
High Level Function to encrypt things in AES CBC Mode.
1: Pad plaintext if necessary.
2: Split plaintext into blocks of length <keysize>
3: XOR Block 1 w/ IV
4: Encrypt Blocks, XOR-ing them w/ the previous block.
'''
if(len(plaintext) % len(key) != 0):
plaintext = pad(plaintext,len(key))
blocks = [plaintext[x:x+len(key)] for x in range(0,len(plaintext),len(key))]
for i in range(0,len(blocks)):
if (i == 0):
ctxt = xor_block(blocks[i],"\x00"* 16)
ctxt = encrypt_block(key,ctxt)
else:
tmp = xor_block(blocks[i],ctxt[-1 * (len(key) * 2):].decode('hex')) #len(key) * 2 because ctxt is an ASCII string that we convert to "raw" binary.

ctxt = ctxt + encrypt_block(key,tmp)
return ctxt

def decrypt_cbc(key,IV,ctxt):
'''High Level function to decrypt thins in AES CBC mode.
1: Split Ciphertext into blocks of len(Key)
2: Decrypt block.
3: For the first block, xor w/ IV. For the others, xor with last ciphertext block.
'''
ctxt = ctxt.decode('hex')
if(len(ctxt) % len(key) != 0):
print "Invalid Key."
return -1
blocks = [ctxt[x:x+len(key)] for x in range(0,len(ctxt),len(key))]
for i in range(0,len(blocks)):
if (i == 0):
ptxt = decrypt_block(key,blocks[i])
ptxt = xor_block(ptxt.decode('hex'),IV)
else:
tmp = decrypt_block(key,blocks[i])
tmp = xor_block(tmp.decode('hex'),blocks[i-1])
ptxt = ptxt + tmp
return ptxt


def padding_check(data):
print "DATA:"
print data
print decrypt_cbc(KEY,IV,data)
return valid_padding(decrypt_cbc(KEY,IV,data))

def get_your_ctxt():
'''
This will return the Secret String that you need to decrypt.
'''
return encrypt_cbc(KEY,IV,pad(SECRET,len(KEY)))

class MyServer(protocol.Protocol):
def dataReceived(self,data):
if(len(data) > 512):
self.transport.write("Data too long.\n")
self.transport.loseConnection()
return
#Make Profile From "Email"
if(data.startswith("get:")):
resp = get_your_ctxt()
if (resp == -1):
self.transport.write("No Cheating!\n")
else:
self.transport.write(resp)

#Decrypt Ciphertext and "parse" into Profile
elif(data.startswith("parse:")):
print "PARSING"
data = data[6:]
if (len(data) % KEYSIZE != 0):
print "BAD CIPHERTEXT"
self.transport.write("Invalid Ciphertext <length>\n")
self.transport.loseConnection()
return

if(padding_check(data) == True):
self.transport.write("VALID PADDING")

else:
self.transport.write("INVALID PADDING")

else:
self.transport.write("Syntax Error")
self.transport.loseConnection()


class MyServerFactory(protocol.Factory):
protocol = MyServer



factory = MyServerFactory()
reactor.listenTCP(PORT, factory)
reactor.run()

exp

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
import socket

def str2num(s):
return int(s.encode('hex'),16)
def num2str(n):
d = ('%x' % n)
if len(d) % 2 == 1:
d = '0' + d
return d

def func(j,z):
x = "00"
for i in range(255):
y = "parse:"+"00"*j + x + z+"ef2c6db9d0bc134f752a119899160b72"
client.send(y)
data = client.recv(1024)
if data == "VALID PADDING":
# print i, hex(i)
return i
x = int(x, 16) + 1
x = num2str(x)

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 9004))

client.send("get:AAAAAAA")
data = client.recv(1024)
print data

client.send("parse:f9b6e7556dd0fa9038ac346e849cc5c1ef2c6db9d0bc134f752a119899160b7217a36290f3ed054ca48f1b052578b644")
data = client.recv(1024)
print data

sum = 16
j = 15
k = 1
tmp = "01" # from 1 to \x10
string_s = ""
string_mid = ""
z = ""
while(sum):
confir = num2str(func(j,z))
# print confir
string_s = str(confir)
#print "string_s:"+string_s
#print "string_mid:"+string_mid
#print tmp*k
string_mid = str(hex(int(string_s,16)^int(tmp,16)))+string_mid

print "string_mid:" + string_mid
tmp = int(tmp, 16) + 1
tmp = num2str(tmp)
string_mid = string_mid[2:].replace('L','')
if len(string_mid)%2 !=0:
string_mid = "0"+string_mid
#print string_mid
#print tmp
temp = len(z)+3

z = str(hex(int(string_mid,16)^int(tmp*k,16)))
z = z[2:].replace('L','')
if temp == len(str(hex(int(string_mid,16)^int(tmp*k,16)))):
z = "00" + z
if len(z)%2 !=0:
z = "0"+z
#print "z:"+z
j = j-1
k = k+1
sum = sum-1
#func(j,z)

client.close()