# -- Cryptographie --
# 1- Algorithme de cryptographie
#	a- Que fait la fonction suivante ?
#	b- Son résultat peut-il être considéré comme une version chiffrée du texte initial ? Expliquez.

def getBytes(str):
	return [ord(c) for c in str]

print("Question 1")	
phrase 	= "My name is Bond, James Bond..."
bytes	= getBytes(phrase)
print(phrase)
print(bytes)

'''
1a- La fonction getBytes(...) retourne la liste des valeurs décimales de chaque octet qui compose le texte reçu:
ex: "My" --> [77,121] car 77 est le code de "M" et 121 celui de "y"

1b- Son résultat est effectivement une manière d'encoder (écrire différemment) le texte initial via un encodage
réversible. Techniquement, on peut donc dire qu'il s'agit d'un chiffrement dont la sécurité repose sur la
connaissance de l'argorithme utilisé (remplacement de chaque lettre par la valeur décimale de l'octet correspondant).
L'algorithme utilisé étant extrèmement simple, le chiffrement serait immédiatement "cassé" par un hacker.
'''


# 2- Ecrire la fonction réciproque getText(...) qui, à partir du résultat de getBytes(...), retourne le texte initial

def getText(bytes):
	return "".join([chr(b) for b in bytes])

print("Question 2")	
phrase 	= "My name is Bond, James Bond..."
bytes	= getBytes(phrase)
txt		= getText(bytes)
print(txt)

# 3- On veut maintenat utiliser une clé secrète pour encoder les valeurs de la liste résultat de getBytes() afin
# qu'il ne soit pas possible à un intervenant de retrouver le texte initial par getText(..) sans connaitre la clé.
# Pour ce faire, on décide d'utiliser l'opération "ou exclusif" (XOR) entre chaque octet du texte et celui de la clé
#	XOR est une opération binaire (notée ^ en Python) qui s'effectue sur chacun des bits des 2 opérandes:
#	Bit Opérande 1 	| Bit opérande 2 	| Résultat
#		0			|		0			|	0
#		0			|		1			|	1
#		1			|		0			|	1
#		1			|		1			|	0
# ex: 56 ^ 83 --> 0011 1000 ^ 0101 0011 = 0110 1011 --> 107
# Supposons, pour le moment, que la clé est plus longue que le texte:
# ex: Clé = "Ceci est une clé de 41 caractères de long"
#
# a- Ecrire une fonction qui reçoit un texte et une clé et retourne les octets du texte combinés aux octets respectifs
# de la clé via une l'opération XOR. Les octets résutat seront fournis dans une liste.
def computeXor(text,key):
	textBytes 	= getBytes(text)
	keyBytes 	= getBytes(key)
	return [textBytes[i] ^ keyBytes[i] for i in range(len(textBytes))]

# b- Appliquer cette fonction au texte "message urgent" avec la clé "cryptographie005". Afficher la liste des octets
# ainsi cryptés obtenus et comparer avec l'utilisation de la fonction getBytes(...) de la question 1
print("Question 3b")	
phrase 		= "message urgent"
key			= "cryptographie005"
xorBytes	= computeXor(phrase,key)
print(xorBytes)

bytes		= getBytes(phrase)
print(bytes)

# c- Constatez qu'une fois l'opération XOR appliquée, il n'est pas possible de récupérer la phrase d'origine avec
# getText(...)
print("Question 3c")	
crypText 	= getText(xorBytes)
print(crypText)

# d- Pour décrypter le texte, il faut appliquer l'opération inverse du XOR à la liste des octets chiffrés. Donc, il
# faut disposer de la clé secrète.
# L'opération XOR est involutive c'est à dire que si a XOR b = c alors c XOR a = b et c XOR b = a. Pour l'inverse,
# il suffit donc de l'appliquer une nouvelle fois, sur les octets chiffrés, avec la mème clé.
# Constatez qu'une fois l'opération xor réappliquée, le texte peut être déchiffré
print("Question 3d")	
phrase 		= "message urgent"
key			= "cryptographie005"
xorBytes	= computeXor(phrase,key)
print(xorBytes)
crypText 	= getText(xorBytes)
print(crypText)

xorBytes2	= computeXor(crypText,key)
print(xorBytes2)
decrypt 	= getText(xorBytes2)
print(decrypt)

# 4-On veut maintenant pouvoir utiliser une clé de longueur quelquonque (typiquement plus courte que le message
# à transmettre). Il n'y aura alors pas assez d'octets dans la clé pour calculer le XOR en une seule fois sur tout
# le message. On va donc répéter la clé, autant de fois que nécessaire, pour chiffrer le message en entier
# ex:
#	Clé: 	 		"Key"
#	Message: 		"Ceci est le message"
#					"KeyKeyKeyKeyKeyKeyK"
#	Message chiffré = [ord("C") ^ ord("K"), ord("e") ^ ord("e"),  ord("c") ^ ord("y"),  ord("i") ^ ord("K"), etc... ]
#
# a- Ecrire une fonction computeXorRepeatingKey(text,key) qui effectue ce calcul et l'appliquer sur une phrase

print("Question 4a")
def computeXorRepeatingKey(textBytes,key) :
	keyBytes 	= getBytes(key)
	return [textBytes[i] ^ keyBytes[i % len(keyBytes)] for i in range(len(textBytes))]

text 			= "My name is Bond, James Bond... I'm trying to find what is written into this message"
key				= "privateKey007"
textBytes 		= getBytes(text)
encodedBytes	= computeXorRepeatingKey(textBytes,key)

print("Phrase:\r\n{}".format(text))
print("Octets non encodés:\r\n{}".format(textBytes))
print("Octets encodés:\r\n{}".format(encodedBytes))

# b- Utiliser la même fonction, avec la même clé pour déchiffrer le résultat précdent et vérifier que la
# reconstruction de la phase conduit bien à celle de départ

print("Question 4b")
decodedBytes	= computeXorRepeatingKey(encodedBytes,key)
decodedText		= getText(decodedBytes)

print("Phrase:\r\n{}".format(text))
print("Octets encodés:\r\n{}".format(encodedBytes))
print("Octets décodés:\r\n{}".format(decodedBytes))
print("Restitution de la phrase:\r\n{}".format(decodedText))