Implement a socket-based key exchange that employs RSA-based digital signatures.
This socket based repository of public keys employs the RSA algorithm to provide access only to the registered users. They can request the public key of another person and then encrypt it, first applying the digital signature, and finally encrypt it with the receiver's public key.
Each time the client establishes a connection with the server, the server is challenged, that is the client sends a value to the server and the server modifies it, then, this modified value is encrypted with the server's private key (digital signature) and sent to the client. The client must also modify the value. If the decrypted value matches it, the connection is accepted, that is one-way authentication. When the client is already registered and the requested action (a query or modify a key) requires a connection with the server, the client is also challenged, that is two-way authentication.
Main functions and classes
The function istheserver(clientobject) verifies the authenticity of the server:
def istheserver(cl):
e = 670655
n = 4560161
x = randint(2, n)
efxs = int(cl.ssendrecv(str(x)))
fxs = modExp(efxs, e, n) # Decrypt fxs
fxc = (x**2 + 25) % n # f(x) Challenge
print "Fx Server:", fxs
print "Fx Client:", fxc
if fxs == fxc:
print "This is the server."
return True
else:
print "This is not the server."
return False
The function istheclient(clientobject, purpose) verifies the authenticity of the client:
def istheclient(cl, p):
email = raw_input("Enter your email:")
if not exists(email):
print "First of all try 'client.py -k' option."
return False
else:
f = open(email + '/id_rsa', 'r')
privatekey = f.readline().split(";")
d = int(privatekey[0])
n = int(privatekey[1])
x = int(cl.ssendrecv("c" + p + ";" + email))
if x != 0: # If client exists
fx = (x**3 + 2) % n # f(x) Challenge
efx = modExp(fx, d, n) # Signature
print efx
isit = cl.ssendrecv(str(efx))
if isit[0] == '1': # Si aprobo este cliente
return True
else: #Si no aprobo
return False
else:
print "Email not registered."
return False
The sockets in the client side are manipulated with the methods of this class:
class client():
def __init__(self):
self._s = socket.socket() # Socket object
self._host = socket.gethostname() #The server ip, localhost for testing
self._port = 9000 # Defines the port
def sconnect(self):
self._s.connect((self._host, self._port)) # Tries to connect
def ssendrecv(self, option): # Send a string and receive an answer
self._s.send(option)
self._received = self._s.recv(1024)
print self._received
return self._received
def sclose(self):
self._s.close() # Close the connection
The server script
The server accepts multiple socket based connections using the threading module.
class c_thread(threading.Thread):
def __init__(self, client, ip):
threading.Thread.__init__(self)
self._client = client
self._ip = ip
def run(self):
try:
self._rcvd = self._client.recv(1024)
print self._rcvd
self._client.send(serverchallenge(self._rcvd))
self._action = self._client.recv(1024)
if self._action[0] == 'r':
self._client.send(register(self._action))
elif self._action[0] == 'c' and c_thread.clientchallenge(self):
if self._action[1] == 'q':
emailr = self._client.recv(1024)
self._client.send(request(emailr))
elif self._action[1] == 'm':
l = self._action.split(";")
info = self._client.recv(1024)
self._client.send(modify(l[1], info))
The run() method determines the action to be performed.
The client script
Easy of use
The client script is supposed to be easy of use, when the user runs this script without arguments the help is displayed. The help is also displayed using -h.
victor@victor-HP-G42-Notebook-PC:~/Documents/Homework/Cryptography/rsasockets$ ./client.py -h
Options:
-k Runs the RSA algorithm to create a Public Key and a Private Key.
-r Register a user in the repository.
-q Request someone's public key.
-c Modify a public key in the repository.
-e Encrypt a message (digital signature, receiver's public key).
-d Decrypt a message.
-m Convert a decrypted string to a readable message. You must get the string using this script. This option is used in -d.
Subscription
The user is registered in the repository using ./client -r. The prompt requests the email (identifier) and the public key (e and n). The server is challenged in this option.
victor@victor-HP-G42-Notebook-PC:~/Documents/Homework/Cryptography/rsasockets$ ./client.py -r
2376580
Fx Server: 2685748
Fx Client: 2685748
This is the server.
Email:jaime
Public Key:
e:5345
n:23454
Registered!
The key can be created before the user is registered. Using -k the RSA algorithm creates the keys for a given user.
victor@victor-HP-G42-Notebook-PC:~/Documents/Homework/Cryptography/rsasockets$ ./client.py -k
Your email: victor
Public Key: (67967, 355613L)
Private Key: (144215L, 355613L)
In the public key the first value is e and the second is n.
Don't share your private key. The first value is d. The second is n.
Then, when the option -r is used, the prompt doesn't ask for a public key, automatically it is read from the id_rsa.pub file in the directory of the user, named as the given email.
victor@victor-HP-G42-Notebook-PC:~/Documents/Homework/Cryptography/rsasockets$ ./client.py -r
1522324
Fx Server: 164055
Fx Client: 164055
This is the server.
Email:victor
Registered!
Query
The query starts with the argument -q. The server and client are challenged. The prompt asks for the email of the user who requests and the script reads the private key of the user in the file “user/id_rsa”.
If the tests are passed, the server sends the public key of the requested user and this information is stored in a hidden directory “.user” in a file named “.id_rsa.pub”, as a client-side cache.
Basically, the option -q calls this function:
def query():
cl = client()
cl.sconnect()
if istheserver(cl) and istheclient(cl, 'q'):
emailr = raw_input("Email requested:")
user = cl.ssendrecv(emailr)
if user[0] == '0':
print "Email is not registered."
else:
saveuser(user)
else:
print "Failed."
cl.sclose()
Example:
victor@victor-HP-G42-Notebook-PC:~/Documents/Homework/Cryptography/rsasockets$ ./client.py -q
2183813
Fx Server: 495314
Fx Client: 495314
This is the server.
Enter your email:victor.riosm@live.com
155575
263713
1:The user is valid.
Email requested:leonardo
leonardo;295067;433793
If the requesting user doesn't exists:
victor@victor-HP-G42-Notebook-PC:~/Documents/Homework/Cryptography/rsasockets$ ./client.py -q
776381
Fx Server: 3868154
Fx Client: 3868154
This is the server.
Enter your email:leon
First of all try 'client.py -k' option.
Failed.
If the requested user does not exists:
victor@victor-HP-G42-Notebook-PC:~/Documents/Homework/Cryptography/rsasockets$ ./client.py -q
2714392
Fx Server: 4086458
Fx Client: 4086458
This is the server.
Enter your email:victor.riosm@live.com
82302
148638
1:The user is valid.
Email requested:leon
0
Email is not registered.
Encryption
The encryption uses digital signatures and public key encryption. The sender's private key is used for the digital signature and the receiver's public key is used to encrypt the message. If the receiver's public key had been requested previously then the prompt won't ask for it. All the ASCII characters can be encrypted.
This is the part of the code that encrypts the message, also applies the digital signature:
message = raw_input("Enter the message to send >> ")
nmessage = ''
for char in message:
nmessage += str(ord(char)).zfill(3)
encrypted = ''
block = 3
stringnumber = ''
i = 0
for char in nmessage:
stringnumber += char
i += 1
if i == block:
number = int(stringnumber)
stringnumber = ''
i = 0
signature = modExp(number, ds, ns)
encrypted += str(modExp(signature, er, nr)).zfill(8)
try:
number = int(stringnumber)
signature = modExp(number, ds, ns)
encrypted += str(modExp(signature, er, nr)).zfill(8)
except:
print 'Encrypted'
print "Encrypted message:", encrypted
Usage:
victor@victor-HP-G42-Notebook-PC:~/Documents/Homework/Cryptography/rsasockets$ ./client.py -e
Enter your email >> leonardo
Enter the receiver email >> juan
Enter the message to send >> Leonardo used digital signatures and then encrypted this message using Juan's public key :)
Encrypted
Encrypted message: 00246575003086040015291200644751003507420052428500201583001529120048878800422626002626190030860400201583004887880020158300128454006464010012845400531802003507420038172500488788002626190012845400646401006447510035074200531802004226260052428500308604002626190048878800350742006447510020158300488788005318020041644200308604006447510048878800308604006447510039480200524285006500280048529200531802003086040020158300488788005318020041644200128454002626190048878800232354003086040026261900262619003507420064640100308604004887880042262600262619001284540064475100646401004887880047461700422626003507420064475100349729002626190048878800485292004226260010520200381725001284540039480200488788004068350030860400650028004887880016435900009845
Decryption
The decryption first takes the encrypted message and decrypts it using the receiver's private key. Then the sender's public key is used to verify the digital signature. This key can be read directly from the file created in a query.
The code for decrypting the ciphertext:
decrypted = ''
block = 8 # the number of characters in the encrypted string
stringnumber = ''
signature = ''
i = 0
try:
for char in encrypted:
stringnumber += char
i += 1
if i == block:
number = int(stringnumber)
stringnumber = ''
i = 0
signature = modExp(number, dr, nr)
decrypted += str(modExp(signature, es, ns)).zfill(3)
try:
number = int(stringnumber)
signature = modExp(number, dr, nr)
decrypted += str(modExp(signature, es, ns)).zfill(3)
except:
print 'Decrypted'
print "Decrypted:", decrypted
message = getmessage(decrypted)
return message
except:
return "Could not be decrypted"
Usage:
victor@victor-HP-G42-Notebook-PC:~/Documents/Homework/Cryptography/rsasockets$ ./client.py -d
Ciphertext >> 00246575003086040015291200644751003507420052428500201583001529120048878800422626002626190030860400201583004887880020158300128454006464010012845400531802003507420038172500488788002626190012845400646401006447510035074200531802004226260052428500308604002626190048878800350742006447510020158300488788005318020041644200308604006447510048878800308604006447510039480200524285006500280048529200531802003086040020158300488788005318020041644200128454002626190048878800232354003086040026261900262619003507420064640100308604004887880042262600262619001284540064475100646401004887880047461700422626003507420064475100349729002626190048878800485292004226260010520200381725001284540039480200488788004068350030860400650028004887880016435900009845
Enter your email >> juan
Enter sender's email >> leonardo
Decrypted
Decrypted: 076101111110097114100111032117115101100032100105103105116097108032115105103110097116117114101115032097110100032116104101110032101110099114121112116101100032116104105115032109101115115097103101032117115105110103032074117097110039115032112117098108105099032107101121032058041
Leonardo used digital signatures and then encrypted this message using Juan's public key :)
If the ciphertext is decrypted using an incorrect private key:
victor@victor-HP-G42-Notebook-PC:~/Documents/Homework/Cryptography/rsasockets$ ./client.py -d
Ciphertext>> 00246575003086040015291200644751003507420052428500201583001529120048878800422626002626190030860400201583004887880020158300128454006464010012845400531802003507420038172500488788002626190012845400646401006447510035074200531802004226260052428500308604002626190048878800350742006447510020158300488788005318020041644200308604006447510048878800308604006447510039480200524285006500280048529200531802003086040020158300488788005318020041644200128454002626190048878800232354003086040026261900262619003507420064640100308604004887880042262600262619001284540064475100646401004887880047461700422626003507420064475100349729002626190048878800485292004226260010520200381725001284540039480200488788004068350030860400650028004887880016435900009845
Enter your email >> usuario
Enter sender's email >> leonardo
Decrypted
Decrypted: 250380167016265627762053536352041292251712656271772342085651664831670162251711772342251713316883622013316882815913536353865811772341664833316883622017620535363528159120856520412916701616648317723435363576205225171177234281591191055167016762051772341670167620531237204129225642795628159116701622517117723428159119105533168816648317723421523716701616648316648335363536220116701617723420856516648333168876205362201177234458220856535363576205417320166483177234279562085651109403865813316883123717723427579516701622564177234161711345913
Could not be decrypted
Other functions
The arguments -c and -m modifies a public key in the repository and converts a decrypted string in a readable message (ASCII), respectively.
Converting a message
A decrypted ciphertext is automatically converted to a readable message calling the next function:
def getmessage(decrypted):
# Converts a decrypted value to a readable message
message = ''
i = 0
block = 3
caracter = ''
for char in decrypted:
caracter += char
i += 1
if i == block:
i = 0
message += chr(int(caracter))
caracter = ''
try:
message += chr(int(caracter))
except:
'Decrypted'
return message
Code
The scripts are in this directory.
Github repository
References