0% found this document useful (0 votes)
42 views9 pages

Signature

The document discusses digital signatures using the MD5 and RSA cryptographic algorithms. MD5 is used to hash a message, and RSA is used to generate a public/private key pair (n, e, d) that can encrypt the hash for signing and allow verification. Together these algorithms allow securely signing and verifying that a message has not been altered during transmission.

Uploaded by

Raphaël Bihay
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
42 views9 pages

Signature

The document discusses digital signatures using the MD5 and RSA cryptographic algorithms. MD5 is used to hash a message, and RSA is used to generate a public/private key pair (n, e, d) that can encrypt the hash for signing and allow verification. Together these algorithms allow securely signing and verifying that a message has not been altered during transmission.

Uploaded by

Raphaël Bihay
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 9

11/3/23, 11:38 AM Signature

Signatures Numériques avec MD5 & RSA

Ce notebook Jupyter explore deux puissants algorithmes de cryptographie :

MD5 (Message Digest Algorithm 5)

et
RSA (Rivest–Shamir–Adleman).

Ensemble, ils permettent de sécuriser les communications, de générer des clés,

de chiffrer et de signer numériquement des données.

Ouverture et Hachage du message

In [ ]: import math

# MD5 Implementation

def leftRotate(value: int, shift: int) -> int:


"""Performs a left rotation (circular shift) on a 32-bit integer."""
return ((value << shift) | (value >> (32 - shift))) & 0xFFFFFFFF

def MD5(message: str) -> str:


"""
Calculates the MD5 hash of the input message.

Args:
message (str): The input message to hash.

Returns:
str: The hexadecimal representation of the MD5 hash.

file:///mnt/KPIHX-Datas/Travaux/Programmation/Applications/Python/Cryptographie_3GI/TP Signatures Numériques/Presentation/Signature.html 1/9


11/3/23, 11:38 AM Signature

"""
# Initialize constants
K = [int(abs(math.sin(i + 1)) * 2 ** 32) for i in range(64)]
# shifts values
s = [7, 12, 17, 22] * 4 + [5, 9, 14, 20] * 4 + [4, 11, 16, 23] * 4 + [6, 10, 15, 21] * 4

# Convert the message to bytes


messageBytes = message.encode('utf-8')

# Initialize state variables


a, b, c, d = 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476

# Padding the message


messageLength = len(messageBytes) * 8
messageBytes += b'\x80'
# In fact len(messageBytes) * 8 must be equal to 56 * 8 (448) mod 64* 8 = 512
while len(messageBytes) % 64 != 56:
messageBytes += b'\x00'
messageBytes += messageLength.to_bytes(8, byteorder='little')

# Process the message in Blocks of 64 bytes


for i in range(0, len(messageBytes), 64):
Block = messageBytes[i:i+64]
SubBlocks = [int.from_bytes(Block[j:j+4], byteorder='little') for j in range(0, 64, 4)]

A, B, C, D = a, b, c, d

for j in range(64):
if j <= 15:
F_func = (B & C) | (~B & D)
g = j
elif j <= 31:
F_func = (D & B) | (~D & C)
g = (5 * j + 1) % 16
elif j <= 47:
F_func = B ^ C ^ D
g = (3 * j + 5) % 16
else:
F_func = C ^ (B | ~D)
g = (7 * j) % 16

file:///mnt/KPIHX-Datas/Travaux/Programmation/Applications/Python/Cryptographie_3GI/TP Signatures Numériques/Presentation/Signature.html 2/9


11/3/23, 11:38 AM Signature

temp = D
D = C
C = B
B = (B + leftRotate(A + F_func + K[j] + SubBlocks[g], s[j])) & 0xFFFFFFFF
A = temp

a = (a + A) & 0xFFFFFFFF
b = (b + B) & 0xFFFFFFFF
c = (c + C) & 0xFFFFFFFF
d = (d + D) & 0xFFFFFFFF

# Concatenate the final state variables to obtain the MD5 hash


hashMD5 = (a.to_bytes(4, byteorder='little') +
b.to_bytes(4, byteorder='little') +
c.to_bytes(4, byteorder='little') +
d.to_bytes(4, byteorder='little'))

# Convert the MD5 hash to hexadecimal representation


hashMD5Hex = ''.join(format(byte, '02x') for byte in hashMD5)

return hashMD5Hex

# Opening of the message to be signed

with open("Signature/Message.txt", "r") as f:


message = f.read()

print("Message = \n", message)

# Message hashing

hashMessage = MD5(message)

print("\nMessage haché = \n", hashMessage)

file:///mnt/KPIHX-Datas/Travaux/Programmation/Applications/Python/Cryptographie_3GI/TP Signatures Numériques/Presentation/Signature.html 3/9


11/3/23, 11:38 AM Signature

Message =
Salut! On fait un test de notre implémentation des signatures numériques avec MD5 et RSA!
On va signer ce message et par la suite vérifier s'il n'a pas été altéré!
A plus pour la vérification...

Message haché =
00598162889abc65837467a804fbef25

Génération des clés publique et privée (n, e, d)

In [ ]: import random

# RSA Generation Keys Implementation

def isPrimeMillerRabin(n:int, k:int = 100)-> bool:


"""
Tests if n is prime using the Miller-Rabin primality test with k iterations.
This test is based on the fact that if p is an odd prime number,
then there exists an integer s and an odd number d such that p-1 = 2^s * d.
The test consists of choosing a random number a between 2 and p-2, and computing a^d mod p.
If this result is equal to 1 or to p-1, then p is probably prime.
Otherwise, the calculation is repeated by multiplying the result by itself s times,
and checking if p-1 is obtained at some point. If not, then p is not prime.
If yes, then p is probably prime, but there is a small probability that it is a false positive.
To reduce this probability, the test can be repeated several times with different values of a.

Parameters:
n (int): The number to test for primality. Must be greater than 3 and odd.
k (int): The number of iterations of the test. Must be positive. Default value is 10.

Returns:
bool: True if n is probably prime, False if n is definitely composite.

Complexity:
O(k log^3(n)), where k is the number of iterations and n is the number to test.
This is a polynomial complexity in the size of the number, which means that the
computation time increases reasonably with the size of the number.
This is much faster than deterministic primality testing methods, which have

file:///mnt/KPIHX-Datas/Travaux/Programmation/Applications/Python/Cryptographie_3GI/TP Signatures Numériques/Presentation/Signature.html 4/9


11/3/23, 11:38 AM Signature

exponential or factorial complexity.

Probability:
The probability that a false positive occurs is less than (1/4)^k. For example,
with k=10, this probability is less than 10^-6.
"""
# Check if n is valid
if n <= 3:
return n == 2 or n == 3
if n % 2 == 0:
return False
# Find s and d such that n-1 = 2^s * d
s = 0
d = n - 1
while d % 2 == 0:
s += 1
d //= 2
# Repeat the test k times
for i in range(k):
# Choose a random number a between 2 and n-2
a = random.randint(2, n-2)
# Compute a^d mod n using fast exponentiation
x = pow(a, d, n)
# If x is equal to 1 or to n-1, go to the next iteration
if x == 1 or x == n - 1:
continue
# Repeat the calculation by multiplying x by itself s times
for j in range(s - 1):
x = pow(x, 2, n)
# If x is equal to 1, then n is not prime
if x == 1:
return False
# If x is equal to n-1, go to the next iteration
if x == n - 1:
break
else:
# If no value of x is equal to n-1, then n is not prime
return False
# Otherwise, n is probably prime with high probability
return True

file:///mnt/KPIHX-Datas/Travaux/Programmation/Applications/Python/Cryptographie_3GI/TP Signatures Numériques/Presentation/Signature.html 5/9


11/3/23, 11:38 AM Signature

def genPrimeMillerRabin(nbits:int) -> int:


"""
Generates a random prime number of a given bit size using the Miller-Rabin primality test.

Parameters:
bits (int): The bit size of the prime number to generate. Must be positive.

Returns:
int: A random prime number of the given bit size.
"""
while True:
# Choose a random odd number between 2^(bits-1) and 2^bits - 1
# The Bretrand thoremen guarantees us to have at least a primer number in this interval
p = random.randrange(2**(nbits-1) + 1, 2**nbits+3, 2)
# Test if n is prime using the miller_rabin function with k=10 iterations
if isPrimeMillerRabin(p):
return p

def modInv(a, n):


"""
Finds the p-inverse of a modulo n where d = a^n, using the extended Euclidean algorithm.
It returns (u, p), where au = p mod n.
"""
# Initialize the variables for the algorithm
u0, u1 = 1, 0 # The coefficients of a
r0, r1 = a, n # The remainders of the Euclidean algorithm

# Loop until r1 becomes zero


while r1 != 0:
# Compute the quotient and the remainder of r0 and r1
r0, q, r1 = (r1,) + divmod(r0, r1)

# Update the values of u0, u1


u0, u1 = u1, u0 - q * u1

return (u0, r0)

def genKeysRSA(nbits: int) -> tuple[int, int, int]:


"""
Generates RSA key pair (public key, private key).

file:///mnt/KPIHX-Datas/Travaux/Programmation/Applications/Python/Cryptographie_3GI/TP Signatures Numériques/Presentation/Signature.html 6/9


11/3/23, 11:38 AM Signature

Args:
nbits (int): Number of bits for the key size.

Returns:
tuple[int, int, int]: (n, e, d) where:
- n: modulus
- e: public exponent
- d: private exponent
"""
# Generate two distinct prime numbers p and q
while True:
p = genPrimeMillerRabin(nbits)
q = genPrimeMillerRabin(nbits)
if p != q:
break

n = p * q
phi = (p - 1) * (q - 1)

# Choose a public exponent e such that gcd(phi, e) = 1


while True:
e = random.randrange(2, phi)
d, _ = modInv(e, phi)
if math.gcd(phi, e) == 1:
break

# Ensure d is positive
if d < 0:
d += phi

return n, e, d

# Generation and save of the public and private encryption keys

nbits = 1024 # minimum number of bits used to generate the random prime numbers
n, e, d = genKeysRSA(nbits)

with open("Signature/PublicKey.txt", "w") as f:


f.write(str(n)+'\n')
f.write(str(e))
with open("PrivateKey.txt", "w") as f:

file:///mnt/KPIHX-Datas/Travaux/Programmation/Applications/Python/Cryptographie_3GI/TP Signatures Numériques/Presentation/Signature.html 7/9


11/3/23, 11:38 AM Signature

f.write(str(d))

print("Modulo : n = ", n, "\nClé publique : e = ", e, "\nClé privée : d = ", d, sep = "\n")

Modulo : n =
16824991855836491824853359085697758966400671053237560846573348145978697882836991335454579293792955711788040981078050
45893948205284959672479207523969239873263158112065818751773678353986166531489599843634656819545796404934436028299152
70756078191606682064573609901588719274162931492601716217750490950837385180306862008409416452778617409288216609245656
99355844518803554275853625241628231633314537069165822548237740838060805531294972507397377660876874057255835088299234
01451524491109902885143645106762140181970503037418304625623903235205664086431353047152778539398273752956060137482018
3994008950985968697280062148213442681

Clé publique : e =
55798284315982155081451059060746905107514864426670909808242865814350871877955255508708337388275040064670825724670273
06667789608069194665498957361372941316390198505973188403285817950552603231997906998158862780961225743403691395454672
88873653383810815859815192251667919181858185374033848481932364323487793978237905301536786302578610049449018970483851
33556442248136087456689634253045313237363047085783893879344939139362641582563892918572891012763074365201768799105284
73247056165951858703180577051220158774507628687237482988296439134927692225475392205962374641391054083289596217395540
468872153628257518499108223535267805

Clé privée : d =
27318421682432543972036943668359397772947169470564104033241677658513834371708309508873486824189541376878650346023303
38307969128434744233786515115102560029868197558391844241051344447523663506136756066249003337188041496583525457940737
18693295537771133306105114927292918687418634128509556945556965424357509022633327521066802029868077623682682153329075
81850550682197577558552152104343289272844913436200405519613855377339934043278015323586462022897965934412974479183973
31837978388485819796634284306628916630167668009523285744345780334400839586167101790649939576994916367668846061045958
561755904163800116933931318327247221

Chiffrement du message haché (Signature proprement dite)

In [ ]: def cipherRSA(msg: str, n: int, key: int) -> int:


"""
Encrypts a message using RSA encryption.

Args:
msg (str): The plaintext message.
n (int): The modulus.
key (int): The encryption/decryption key (either public or private).

file:///mnt/KPIHX-Datas/Travaux/Programmation/Applications/Python/Cryptographie_3GI/TP Signatures Numériques/Presentation/Signature.html 8/9


11/3/23, 11:38 AM Signature

Returns:
int: The ciphertext.
"""
# Convert characters to their ASCII values and pad with zeros
msg_padded = [str(ord(c)).zfill(3) for c in msg]

# Determine the block size based on the length of n


len_block = math.floor(len(str(n)) / 3)

# Convert padded blocks to integers


msg_blocks = [int("".join(msg_padded[i : i + len_block])) for i in range(0, len(msg_padded), len_block)]

# Encrypt each block using modular exponentiation


msg_cipher = [pow(i, key, n) for i in msg_blocks]

# Combine ciphertext blocks into a single integer


return sum([msg_cipher[i] * (n ** i) for i in range(len(msg_cipher))])

SignedMessage = cipherRSA(hashMessage, n, d)

with open("Signature/SignedMessage.txt", "w") as f:


f.write(str(SignedMessage))

print("Message signé = \n", SignedMessage)

Message signé =
1705005349270368498277509172464249830183610499908347876055100540079446178871775775625534318189666959363658281035106
20014076522006406820281559597222311502372285971682366339058893809026759964243011709466555681902343140492411557791117
30963788834098657920256898308736063022455560107595034120552352838746548580633064994529437278733134697823570111232982
59383299241882065533418358360173543344926543697779599980587570845155583920095419046047780300177531971118376805183556
42265606141020055286412950735220141735359301511497586383028927852728329005787244893192449307109608390400590686799705
2478078662508872995853236404431392527

file:///mnt/KPIHX-Datas/Travaux/Programmation/Applications/Python/Cryptographie_3GI/TP Signatures Numériques/Presentation/Signature.html 9/9

You might also like