I hope to get some more frequent updates on the blog in the coming months. But today I wanted to show a basic crypto attack, the bitflip attack on AES_CBC.
The “normal” mode of operation for AES is AES ECB(Electronic Codebook). This has some problems. Most importantly, a plaintext is always represented as the same ciphertext block when encrypted under the same key. This can lead to some fun statistical attacks :)
To solve this we can introduce a random vector into the encryption routine. This leads us straight into AES_CBC!
AES_CBC works by introducing a random initialization vector(IV) and chaining the blocks so that each block is “dependant” on the previous block.
The basic idea is that since each plaintext block is XORed with the previous ciphertext block we can alter(flip) the ciphertext to represent the plaintext we want it to be (if we know/can guess the original plaintext). Let’s look at the decryption routine!
Imagine we have a ciphertext that looks like (block size=4)
ABCDEFGH
with the plaintext
BITFLIP!
Our goal is to flip the 5th character in the plaintext (L) to K.
Look at how CBC works. The previous ciphertext block is XORed with the decrypted ciphertext block to represent the plaintext block. Maybe a bit confusing, but it’s actually simple.
L = A^decrypt(EFGH)[0]
Now, lets do the attack. We want L to be represented as K.
CIPHERTEXT = ABCDEFGH
TARGET = BITFKIP!
CIPHERTEXT[0] = A^L^K
FLIPPED = decrypt(CIPHERTEXT)
FLIPPED == TARGET?
true
Think about how XOR works
L = A^decrypt(EFGH)[0]
What do we control here? The ciphertext. So what does the ciphertext have to be in order for L to become K in the plaintext?
Remember that
decrypt(EFGH)[0] = A^L
What do we need to put in Y for
K = Y^decrypt(EFGH)[0]
to be true?
Y = A^L^K (since decrypt(EFGH)[0] = A^L)
Now imagine we encrypt session tokens or cookies with AES_CBC…
import json
from base64 import b64encode, b64decode
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from Crypto.Util.Padding import pad, unpad
class AES_CBC():
def __init__(self, blksize=16):
if blksize not in [16, 24, 32]:
raise ValueError("Blocksize must be 16, 24 or 32")
self.blksize = blksize
self.key = get_random_bytes(self.blksize)
def encrypt(self, plaintext):
cipher = AES.new(self.key, AES.MODE_CBC)
pt_padded = pad(plaintext, AES.block_size)
ct_bytes = cipher.encrypt(pt_padded)
iv = b64encode(cipher.iv).decode('utf-8')
ct = b64encode(ct_bytes).decode('utf-8')
return json.dumps({
'IV':iv,
'ciphertext':ct
})
def decrypt(self, cipher_obj):
b64 = json.loads(cipher_obj)
iv = b64decode(b64['IV'])
ct = b64decode(b64['ciphertext'])
cipher = AES.new(self.key, AES.MODE_CBC, iv)
pt_padded = cipher.decrypt(ct)
return unpad(pt_padded, AES.block_size)
if __name__ == "__main__":
aes = AES_CBC()
res = aes.encrypt(b"{id=11;date=20200129;role=common}")
b64 = json.loads(res)
ct = b64decode(b64['ciphertext'])
iv = b64['IV']
to_flip = list(ct)
target = "admin;"
before = "common"
for i in range(6):
to_flip[10+i] = ct[10+i]^ord(before[i])^ord(target[i])
flipped = json.dumps({
'IV':iv,
'ciphertext': b64encode(bytes(to_flip)).decode('utf-8')
})
print(aes.decrypt(flipped))
Change to a more modern mode of operation. Or do MAC then Encrypt!
]]>From Wikipedia -
A blockchain is a growing list of records, called blocks, that are linked using cryptography.
Each block contains a cryptographic hash of the previous block, a timestamp, and transaction data.
This wasn’t complicated at all. Simply a linked list where the integrity and order of the blocks is controlled by an hashing algorithm(H).
____ ____ ____
| G | | A | | B | *n
| |--->|H(G)|--->|H(A)|--->
|data| |data| |data|
|____| |____| |____|
The implication of this is that if an attacker would change an arbitrary block in the chain, he would then need to change all blocks after that block in order for the chain to be correct. Most blockchains is managed in a peer-to-peer network, which means that the attacker, without network majority, cannot control the entire chain after the changed block. Simply, the chain is copied to each peer in the network and only trusts the majority. If the attacker would change a block and his copy of the chain, the network would vote on which copy is correct, and choose the majority’s copy.
(This is the basic idea of a decentralized blockchain, but there are other types of blockchain which have different voting algorithms or other models in which the former statement doesn’t hold true. The voting/majority part that is. Thanks to Hampus Bengtsson for pointing that out).
To prevent the chain from overflowing with blocks we must ensure that it’s a hard problem to add new blocks to the chain. This is very important in cryptocurrencies i.e. Bitcoin. This is what’s called mining. Mining comes from the parable with mining gold(or other metals). We add more money into the economy. If anyone could add coins into the chain very easily we would have a Weimar hyperinflation all over again. That’s why any new block needs a proof-of-work which is very hard to calculate. A commonly used proof-of-work is that the hash of every block must start with x-numbers of zero.
class Block():
def __init__(self, id, data, prev_hash=None):
self.id = id
self.data = data
self.prev_hash = prev_hash
self.nonce = 0
def mine(self, x=4):
while hash(self)[:x] != "0"*x:
self.nonce += 1
self.hash = hash(self)
return
def dump(self):
if self.hash is None:
self.mine()
return {
"id":id,
"data": self.data,
"timestamp": time.now(),
"nonce": self.nonce,
"prev_hash"=self.prev_hash
}
If you for some reason would want the code to actually work - change the hash-call to a real hashing algorithm, and make the time.now() call to a string
A simple block class which holds transaction data and the hash of the parent. If we would like to add a new block to our chain we would simply do (note that block.dump() calls on block.mine() if the block is not mined yet)
# Initialize with genesis block
chain = []
genesis = Block(id=0, data={XXXX}) # prev_hash=None
chain.append(genesis.dump())
# Add a new block to the chain
new_block = Block(id=1, data={xxxxx}, prev_hash=genesis.hash)
chain.append(new_block.dump())
As you can see blockchains are actually very simple in it’s basic form, but there are so many variants on every algorithm and voting policies and so forth that there could be thousands of books to cover the complete topic. This was only a fairly quick reading to learn the basics about blockchains.
In this basic form of the blockchain the only attack vector would be to take majority of the network to change the transaction data in any block successfully. As mentioned above, every blockchain has it’s own algorithms and forms, and therefore new attack vectors.
There a million of blog posts like this that are much better and more thourough, I only wrote this since I wanted to know how basic blockchains work and to learn something new. I guess next post will be on how to build a CAN-network with arduinos :) Or maybe a crypto-attack
]]>Homomorphism is defined in a mathematical set such as the result of an operation is mirrored in another set by performing the same operation in that set.
$f:G\rightarrow H$ where $f(xy)=f(x)f(y)$
Oh yes, I finally took the time to integrate LaTeX into Hugo. No more ugly x^2 equations.
In the context of cryptography this means that the result of an operation performed on the ciphertext(G) is mirrored when the same operation is performed on the plaintext(H).
For example, given a ciphertext $\mathit{C}$ and a plaintext $\mathit{P}$, with an operation $\star$ the following holds true in a fully* homomorphic scheme.
$$C'=x\star C$$ $$P'=decrypt(C’)$$ $$P=x\star^{-1} P'$$
where $\mathit{x}$ can be any argument to the operation.
There are basically three types of homomorphic encryption. (1) Partially homomorphic encryption(PHE), this allows one type of operation, (2) Somewhat homomorphic encryption (SWHE) which allows some types of operations and (3) Fully homomorphic encryption (FHE) which allows all operations.
This does sound like black magic. Actually RSA is a homomorphic encryption-scheme (PHE). RSA allows unlimited amount of modular multiplications.
$$C = P^{e} \mod n$$ $$C'= C\times x^{e} \mod n$$ $$P'=C'^{d} \mod n$$ $$P=\frac{P’}{x} \mod n$$
Because
$$P'=C'^{d}=(C\times x^{e})^{d}=(P^{e}\times x^{e})^{d}=Px^{ed}=Px \mod n$$
Remember that
$$ed = 1 \mod n$$
This allows one type of operation (modular multiplication) on the ciphertexts. But there actually exists a scheme which allows any operation on the ciphertext, a fully homomorphic encryption-scheme. It’s called Gentry’s FHE.
A obvious application is cloud services. In a traditional cloud service where we hold sesitive encrypted data, the cloud provider owns both the encrypted data and the method to encrypt (and decrypt) the data. Now say an attacker takes control of the cloud service, he owns the encrypted data, and also the means to decrypt it. If we could employ a homomorphic encryption-scheme, the cloud provider can outsource the encryption and decryption methods to the original owner of the data. The cloud provider now only has the encrypted data but can still perform any operation (statisics, sorting, querys etc.) and so on. If an attacker now takes control of the cloud service, he only has control of the encrypted data, which in that form is worthless.
Often when there is talk about homomorphic encryption, some people often mention encrypted databases. Encrypted databases is almost always a bad idea. Matthew Green has an awesome article about this. Looks like with a sufficent amount of querys an attacker can reconstruct parts of the database. Oops!
This is of course super interesting, and is seen like the holy grail of cryptography. Today there exists a fully homomorphic scheme, but it suffers from really serious performance issues. We are probably a long time away from a applicable FHE-scheme.
If you found this interesting. Me and a classmate Johan Näslund, wrote a short paper about homomorphic encryption. You can find it here (Swedish).
]]>I recently took a course called Kryptering 3 (advanced cryptography), which mostly is about post-quantom cryptography and the corresponding algorithms. Yesterday I fell into some writing mode again, so today I’m gonna talk a bit about quantom computers and show a “quantom safe” algorithm - GGH.
A regular computer has it’s lowest information represented as a bit, one or zero. A quantom computer has its lowest information represented as a qubit. Reading a qubit is different from reading a bit. A bit is always one or zero, but a qubit is represented as a normalized superposition of either one or zero (this is black magic). This gives quantom computers special properties, which in short makes quantom computers incredibly fast at solving specific problems. Or rather, given this quantom property we can construct specific algorithms which utilizes these to solve specific problems. You may have heard about one of these algorithms - Shor’s algorithm.
A common myth is that quantom computers breaks all encryption. This is not true. Rather shor’s algorithm (in theory) solves the integer factorization and discrete logarithm problem in polynomial time.
What does this mean then? Well, the encryption schemes which builds on these problems are broken with suffiently good quantom computers. Any schemes that comes to mind? How about RSA, ECDSA and Diffie-Hellman? Which all are asymmetric algorithms. Symmetric algorithms (like AES) seems to be fine for now, i.e. there is no quantom algorithm which breaks these like Shor’s algorithm breaks some asymmetric schemes.
Google just declared quatom supremacy and earlier this year Martin Ekerå and Craig Gidney wrote a paper which described how to factor 2048-bit RSA integers in 8 hours.
Such quantom computers are not here yet though. But according to Martin Ekerå - they can be in the next 10-25 years.
Well, this sucks :D But don’t worry too much just yet, there are solutions to this.
So, Shor’s algorithm solves the integer factorization and discrete logarithm problem in polynomial time. Can we build asymmetric schemes which depends on other problems? Looks like we can!
A fairly simple algorithm (compared to other post-quantom schemes) is GGH. It is broken though, so don’t use it. But it’s easier than others to explain.
First we need to have something called vector space. A vectorspace V over R is defined if V is closed under addition and multiplication for all vectors in V and elements i R. This means that the result of adding two arbitrary vectors from V is in V, and the scalar multiplication of an arbitrary vector in V and element in R is in V. It’s easier to show -
u,v in V
a in R
u+v in V
au in V
There are more rules to this (read my other posts).
A vectorspace V has a base B=(v1,v2,…,vm) if B is linearly independant and every vector, v can be written like a linear combination of B.
A Lattice is a vector space with a base (linearly independant vectors) which generates the lattice.
Example:
L = lattice
(v_1,v_2,...,v_n) = base
Given the base v_1=(1,0) and v_2=(2,1) where v_1,v_2 in R^2 the lattice L is described as
L = {xv1, yv2} = {x+2y, y} for all elements x,y in Z
The lattice can be represented as all points {x,y} in the plane.
One problem which is hard to solve (in higher dimensions) is the Closest vector problem. Given a vector w in R, find the closest vector v to w in the vector space V, i.e. find the vector v in V with the shortest distance to w
Pick n random linearly independant vectors from Z^n and set the private key
V = (v_1,v_2,...,v_n)^T
Pick a random matrix U with order n such as determinant U = +-1
Set the public key W to
W = UV
The plaintext p should be coded in such a way that p=(p_1,p_2,…,p_n) is in Z^n.
Pick a random efemary key r in Z^n. I will describe why below.
The ciphertext is then given by
c = pW + r
We have a vector c which is not in V. Why not? Because we introduced a noise, r.
To find the original message p we first need to find pW (which is a vector in V). To do this we find the closest vector to c. This means that me remove the noise introduced by r.
pW = solve_CVP(c, V)
p = pWW^-1 = pI = p
Solving approximate CVP can be done by Babai’s algorithm. But not knowing V (the private key) makes it a really hard problem to solve.
As you can see above, GGH is built on the closest vector problem (and not integer factorization or discrete logarithms). GGH is very broken though! But there are other post-quantom algorithms which work much better (such as McEliece and NTRU).
This was a basic introduction to post-quantom cryptography and why we all should care. Next post will be a introduction to homomorphic encryption (yay, more buzzwords!).
]]>The keys are based on Diffie-Hellman. It’s actually really simple:
def generate_keypair(self): # Diffie-Hellman
self.private = randint(1, self.baseorder)
self.public = self.multiply(self.private, self.base)
A.session_key = A.private*(B.public*base)
B.session_key = B.private*(A.public*base)
This works, because of the abelian property associativeness (maybe an english word?).
a = A.private
b = B.private
A.session_key = a*(b*base) = (a*b)*base = b*(a*base) = B.session_key
Wikipedia actually explains the algorithm very well, and why it works. Take a look.
Python code for the algorithm look like this:
def sign(self, H):
z = int("0x"+H, 16)
k = randint(1,self.baseorder-1)
P = self.multiply(k,self.base)
if P.x == 0:
return self.sign(H)
r = P.x
s = (z+r*self.private)*I_invert(k, self.baseorder)%self.baseorder
return int(r), int(s)
def verify_signature(self, signature, pub, H):
r,s = signature
if not (1 <= r <= self.baseorder-1 and 1 <= s <= self.baseorder-1):
raise Exception("Invalid signature")
z = int("0x"+H, 16)
u1 = z*I_invert(s,self.baseorder)%self.baseorder
u2 = r*I_invert(s,self.baseorder)%self.baseorder
P = self.add(self.multiply(u1,self.base),self.multiply(u2, pub))
if r == P.x:
return True
else:
raise Exception("Invalid signature")
Since I programmed it, I can decide the hashing algorithm, and therefore z. I chose sha1 with regards to the length of the hash.
The complete code can be found at github.
So I’ve spent the last day to learn basic Rust and implementing ECDSA. Therefore my implementation may be relativaly bad from a speed perspective. But I think it turned out okay for my first time writing Rust.
Rust by default do not support the sizes of our parameters, therefore I needed to implement a big-num library (or crate as Rust calls it).
Rust does not support classes either, but like Go, it supports structs. I think this is fine, but it did change my implementation a bit. It now deviates a bit from my Python implementation.
Here’s the sign and verify_sign functions.
fn sign(h: &str, private: Integer) -> (Integer, Integer) {
let base = Point{
x: hex2int("188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012"),
y: hex2int("07192b95ffc8da78631011ed6b24cdd573f977a11e794811"),
};
let baseorder = hex2int("ffffffffffffffffffffffff99def836146bc9b1b4d22831");
let z = hex2int(h);
let k = gen_random();
let P = point_multiply(&k, &base);
if P.x == Integer::from(0) {
sign(h, private.clone());
}
let r = P.x;
let s = (z+r.clone()*private)*Integer::from(k.invert_ref(&baseorder).unwrap()).rem_floor(&baseorder);
(r,s)
}
fn verify_signature(signature: (Integer, Integer), public: Point, h: &str) -> bool {
let base = Point{
x: hex2int("188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012"),
y: hex2int("07192b95ffc8da78631011ed6b24cdd573f977a11e794811"),
};
let baseorder = hex2int("ffffffffffffffffffffffff99def836146bc9b1b4d22831");
let r = signature.0;
let s = signature.1;
if 1 > r && r > baseorder.clone()-Integer::from(1) && 1 > s && s > baseorder.clone()-Integer::from(1) {
panic!("Invalid signature");
}
let z = hex2int(h);
let u1 = z*Integer::from(s.invert_ref(&baseorder).unwrap()).rem_floor(&baseorder);
let u2 = r.clone()*Integer::from(s.invert_ref(&baseorder).unwrap()).rem_floor(&baseorder);
let P = point_add(&point_multiply(&u1, &base), &point_multiply(&u2, &public));
if r == P.x {
return true
}
return false
}
The complete code can be found at github.
For each iteration I generate the keys, sign a message and verify the message. I do this 1000 times on each program.
# ec.py
for i in range(1000):
ec = EC(a,b,G,n,p)
ec.generate_keypair()
H = "719609852b46b8ea9a5fcd39eb7bc9088fa36399"
r,s = ec.sign(H)
ec.verify_signature((r,s), ec.public, H)
Here are the results.
$ time python ec.py
real 0m8,314s
user 0m7,358s
sys 0m0,033s
$ time ecRust/target/release/ec_rust
real 0m5,099s
user 0m5,097s
sys 0m0,000s
So Rust was a bit faster, but not by much. This is probably due to my bad Rust skills and only knowing it for about 24 hours :) I suspect that my implementation can be sped up by a factor or two.
My CAN-buses haven’t arrived yet, but when they do I will take a dive into CAN and some cool stuff!
]]>I’ve recently ordered some Arduino boards and CAN-buses, so soon I will be taking a deep dive into CAN and car hacking! Making myself ready for our big move to Gothenburg :)
From Wikipedia -
In mathematics, an elliptic curve is a plane algebraic curve defined by an equation of the form
y^2=x^3+ax+b
which is non-singular; that is, the curve has no cusps or self-intersections.
Which means that it is non-singular, i.e it has no singular points on the curve. As Wikipedia says - it has no intersection nor cusps.
A cusp is a point where both the tangent of x and y is zero. You can also say that you can decide two tangents in a singular point, where in a non-singular you can only decide one.
Take P(x1,y1) and Q(x2,y2) where the intersection is non-vertical, the line which intersects P and Q is
(kx+m)^2 = x^3+ax+b
Which also is a point on the curve.
BUt WhAt iF tHE iNteRsECtiOn iS VerTIcAl
That can happen. Therefore we introduce a point to represent infinity (Since the third point on the line doesn’t exist). This will be proven to come in handy later :)
So, we actually do implement Elliptical curves as fields. This requires some rules -
(This is a really basic explaination)
We already defined an identify element (the one to represent infinity, 0). Nice!
So for our nice elliptical curve, the rules follows as such -
So the proof for the addition algorithm is a bit tricky (Read: I’m to lazy :D) But it looks like this -
(P+Q) % p
x = delta^2-x1-x2
y = delta*(x1-x)-y1
delta =
if p1=p2: # This means that the point of intersection is the derivative, f'(x) of f(x)
delta = (3*x1^2+A) / 2*y1
else:
delta = (y2-y1) / (x2-x1)
*omitted*
def add(self,p1, p2):
if p1 == self.id:
return p2
if p2 == self.id:
return p1
if p1 == p2.invert(self.p):
return self.id
if p1 == p2:
delta = (3*pow(p1.x,2)+self.A)*gmpy2.invert(2*p1.y, self.p)
else:
delta = (p2.y - p1.y)*gmpy2.invert(p2.x - p1.x, self.p)
x = pow(delta, 2)- p1.x - p2.x
y = delta*(p1.x-x)-p1.y
return Point(x % self.p, y % self.p)
*omitted*
Point is a simple class
class Point():
def __init__(self,x,y):
self.x = x
self.y = y
def __str__(self):
return "{"+str(self.x)+", "+str(self.y)+"}"
def __eq__(self, p2):
return True if self.x == p2.x and self.y == p2.y else False
def __mod__(self,p):
return Point(self.x%p, self.y%p)
def invert(self, p):
return Point(self.x, p-self.y)
I have actually written the multiplication algorithm for fields already. Check out galois fields
So we end up with
*omitted*
def multiply(self,n,p):
q = self.id
while n > 0:
if n&1:
q = self.add(q,p)
p = self.add(p,p)
n = n >> 1
return q % self.p
*omitted*
Where n is an integer for the multiplication nP = Q
Now we have everything to set up Elliptical curve cryptography, which I will cover in the next post. Until next time!
]]>After a well deserved break with 6(!!) days of vacation, I’m back! Not really feeling the urge to pick up some new crypto stuff to write about just yet, but “den som väntar på något gott” :)
This saturday me and my good friend Elin had our joint birthday party.
It all started with a case of beers and a jacuzzi, where we decided to host a party where we cooked Neapolitan pizza for everyone, one pizza each, just like a one-night, pop-up pizza place. Since my parents neighbour just happened to have a wood oven in his basement it was maybe not the stupidest idea in the world. Maybe
You start off with picking the sort of pizza you want to make.
Waiting…
Correct! Neapolitan style is what you want to do.
Here you can pick whatever you want but we choose:
Mutti tomatoes which we crushed
Sea salt
Black pepper
Fresh Mozzarella
Basil
Olive oil
Parma ham
This is the classic Neapolitan margherita (minus the Parma ham).
The most important part. For the best result you need a wood oven. We had our wood oven running at about 450-500 degrees celsius. A pizza took about 45-50 seconds to cook.
PAKE Protocols is a authenticated key exchange protocol. An example of a unauthenticated key exchange protocol is Diffie-Hellman. I’ve written code on a very basic MITM-attack on Diffie-Hellman here.
In a PAKE protocol you authenticate through a password - and what makes it really great is that the password is also protected. After a failed attempt the client and server should only know if the attempt failed or not if the password matched the server’s expected value.
If you’ve heard of Secure Remote Password, or SRP, you may know that it’s like OPAQUE, a PAKE protocol. SRP has some issues, for instance it allows an attacker to run an offline-dictionary attack, since it exposes the salt to the user. I’ve written some code on the attack here
So, OPAQUE… OPAQUE does not reveal the salt to the user, nor does it reveal the password to the server. It does this by having the server and the client run an oblivious PRF.
It looks like this:
H = hashfunction
H' = maps a hash into the group psuedorandomly.
x = password, or password hash.
k = server salt
g = generator in group
r = random element in group
# Client runs this
A = H'(x)*g^r
# Server calculates
v = g^k
b = A^k
# Client calculates the random-looking password
rwdU = H(x,v, b*v^{-r})
*Note*
b*v^{-r} = A^k * v^{-r} = (H'(x)*g^r)^k * (g^k)^{-r} = (H'(x)*g^rk) * (g^{-r}k} = H'(x)
# Which means that the correct password generates the same rwdU for every randomly selected r and correct server salt, k.
So the client is never exposed to the salt, and the server never learns the password.
First a user needs to do a “password registration” at the server.
pwdU = Client's password
privU, pubU = Client keys
kU = Server OPRF salt, random and independant of each user.
vU = g^kU
privS,pubS = Server keys. Can be the same for all users
# Client
rwdU = oprf(pwdU)
# Server also runs oprf but ignores the result.
oprf()
# Client
envelope = AuthEnc(rwdU, privU, pubU, pubS, vU)
# Client sends envelope and deletes rwdU, pwdU and keys.
# Server saves envelope
So the server has the encrypted envelope and kU. And the client has nothing saved.
Authentication can look like this:
xU, yU = Client DH keys
xS, yS = Server DH keys
# Client
rwdU = oprf()
# Server
oprf()
signature = sign(privS, H(yU, yS))
# Client
decrypt envelope with rwdU
verified = verify(envelope(pubS), H(yU,yS))
signature = sign(envelope(privU), H(HKDF(dh_shared), envelope(pubU)))
# Server
verified = verify(pubU, H(HKDF(dh_shared), pub))
If the last verified check passes the client is authenticated, since it proves that the user knows the password.
I’ve implemented a simple server/client PoC here
This was a quick hack over two days, do not use this for anything important :-)
Next project is looking at security in some IoT-devices were given by a sponsor from BTH_CTF…
Until next time!
]]>Galois field, or finite field is a set in which the basic math operations are defined - addition, subtraction, multiplication and division. There is obviously a lot of rules for a field, but a major one is that the field must be isomorphic, i.e all elements in the field must have an inverse. Therefore Galois Fields are only defined with the order of (number of elements) p. Where p is a prime or a prime power, p^k, where k is a positive integer.
GCM works in GF(2^128) with the irreducible polynom.
x^128+x^7+x^2+x+1
In a Galois Field the irreducible polynom is like a prime number, i.e it has no factors (in that field).
Here is a python implementation of addition, multiplication and division in GF(2^k).
# Returns the degree of the polynomial
def deg(a):
deg = len(bin(a)[2:])-1
return deg
# Add and subtract is the same. Since it's GF(2^k)
def add(a,b):
return a^b
def divmod(a, b):
q, r = 0, a
while deg(r) >= deg(b):
d = deg(r) - deg(b)
q = q ^ (1 << d)
r = r ^ (b << d)
return q, r
# Multiplicative over GF(2^k)
def gf_mul(a,b,m):
# Peasant multiplication from wikipedia
p = 0
while a > 0:
if a & 1:
p = p^b
a = a >> 1
b = b << 1
# Modulus the polynomial m
if deg(b) == deg(m):
b = b^m
return p
Now when we can do math in GF(2^k), let’s implement the GHASH
Pick the MAC secret. Which is a block of zero bytes encrypted with your AES key.
Zero pad the ciphertext and additional data block by block, so it’s divisible by the block length (AES-128=16). Then concatenate the blocks.
Add a block which concatenates the bit length of the ciphertext and AD.
For each of the concatenated blocks, do:
tag += block
tag *= MAC secret
mask = AES(nonce || 1)
tag += mask
Now you have derived a hash from the ciphertext, additional data, nonce and AES-key. Cool!
Here’s my GHASH implemenation in Python3.
import struct
from Crypto.Cipher import AES
from Crypto.Util.number import long_to_bytes, bytes_to_long
# The code shown above...
from galois import *
# GF(2^128)
# p = x^128+x^7+x^2+x+1
def convert_to_field_element(a):
p = 340282366920938463463374607431768211591
if deg(a) >= deg(p):
return a^p
else:
return a
class GHASH():
def __init__(self, K, nonce, CT, AD=None):
self.K = K
self.nonce = nonce
self.CT = CT
self.AD = AD
# MAC secret
self.H = convert_to_field_element(bytes_to_long(self.E_K(b'\x00'*AES.block_size)))
# Calculate Length block
AD_len = 0 if AD==None else len(AD)
len_block = struct.pack('<Q', AD_len*8)+struct.pack('<Q', len(CT)*8)
# Pad CT and AD :-)
self.pad()
# Concatenate stuff
self.data = self.CT+len_block if self.AD == None else self.AD+self.CT+len_block
def E_K(self, data):
cipher = AES.new(self.K)
return cipher.encrypt(data)
def pad(self):
if len(self.CT) % AES.block_size != 0:
self.CT = self.CT+b'\x00'*(AES.block_size-(len(self.CT) % AES.block_size))
if self.AD != None:
if len(self.AD) % AES.block_size != 0:
self.AD = self.AD+b'\x00'*(AES.block_size-(len(self.AD) % AES.block_size))
def round(self):
g = 0
for b in self.data:
b = convert_to_field_element(bytes_to_long(b))
g ^= b
g = gf_mul(g,self.H,340282366920938463463374607431768211591)
return g
def derive(self):
# Calculate the rounds
g = self.round()
# Mask tag
s = bytes_to_long(self.E_K(self.nonce+struct.pack('<i',4)))
self.tag = g^s
return self.tag
Basically what the tag looks like is this (MS is MAC secret):
tag = (((((block1*MS)+block2)*MS)+len)*MS)+mask
Which becomes
tag = block1*MS^3+block2*MS^2+len*MS+mask
Now we know how the tag is calculated. Say that we accidentally repeat a nonce using the same AES key. What could possibly go wrong? Well, let’s look at it.
tag1 = b1*MS^3+b2*MS^2+len1*MS+mask
tag2 = d1*MS^3+d2*MS^2+len2*MS+mask
What do they have in common? MS, of course, since it’s AES(key, ‘\x00’*block_length). What else?
The mask!
Since we work in GF(2^k) where addition and subtraction is the same, we can add the two tags together to remove the mask.
tag1+2 = (b1^d1)*MS^3+(b2^d2)*MS^2+(len1^len2)*MS
The validation of the tag is that we plug in the mac secret into the polynomial and check if the result equals zero. So the mac secret is a root to tag.
tag = f(MS) = 0
The attack is then to factor the polynomial tag1+2 to find the mac secret. If we find the MAC secret, we can fake the authentication tags for all ciphertexts under the same AES key.
So it seems like AES is a bit complicated. Most people see AES and think - “Great! This can’t be broken”.
And sure, it isn’t feasible in any amount of time to get the key from a ciphertext, even when knowing the plaintext. But there are other problems with the implemenation of AES. Let’s look at the most basic mode of operation, AES ECB.
ECB, or Electronic Codebook is the simplest mode of operation. You divide the plaintext into blocks and encrypt the blocks separately.
What’s the problem with this? Two encrypted plaintexts will always be represented as the same ciphertext (if encrypted with the same key, of course).
Ok, ECB is bad. But what if we add another random element into the encryption of each block, so two encrypted plaintexts will not be represented as the same ciphertext. This seems like a good idea! Let’s do it.
CBC stands for Cipher Block Chaining, as the encryption of each block is dependant on the ciphertext from the previous block. This looks much better! But with this chaining comes other problems, like bitflipping attacks.
I’ve written some code on a basic bitflipping attack here. The basic idea of a bitflipping attack is that if the attacker know the plaintext, he/she can do some math to change the ciphertext into being decrypted into anything. Oh, that’s bad.
CTR stands for Counter. Instead of splitting the plaintext into blocks and padding it, we can have a counter with a random nonce (like IV) and XOR the “keystream” with the plaintext. This turns the AES block cipher into a stream cipher, which is a more logic way of looking at data. This is also suspectible to bitflipping attacks. attack code.
How do we fix this? By adding integrity checks of course! Which leads us perfectly into AES GCM.
GCM stands for Galois Counter Mode. This is bit more complex solution, since adding a simple hash after the ciphertext would fix nothing (since the attacker can modify the hash to the flipped code). GCM implements a type of MAC. This makes it near impossible for an attacker to fiddle with the ciphertext, since any change would be detected when comparing the hashes. And the attacker can’t spoof the hash, since he/she doesn’t know the secret.
Some explanation:
Galois field as a MAC is chosen because it actually allows the Auth Tag calculation to be computed in parallel, which makes it faster than for example CBC. And even more so, than a traditional SHA-1 MAC. And speed in cryptography is important.
So, first ever post. Hope that I can keep this blog project going for quite some time atleast.
This weekend I (along with some classmates) organized our first ever CTF! It was amazing. We had so much fun, and so much work put into it. And it was so worth it. We had a total of 24 teams playing in our school overnight. Contestants from Dalarna, Linköping and of course our school.
I was responsible for some of our crypto challenges, and some other (one reverse and two OSINT). I will be posting writeups for my challenges. Atleast those I released.
You can find pictures of the event here.
Huge thank you to our sponsors. SecureLink and IKEA!
]]>