-
Notifications
You must be signed in to change notification settings - Fork 11
/
rsa.py
238 lines (192 loc) · 7.18 KB
/
rsa.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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
#!/usr/bin/python3
# RSA skeleton code from the https://aaronbloomfield.github.io/ics/hws/hw-rsa.html assignment
import sys, random, math, hashlib
# An RSA key, either public (if d is None), private (if e is None), or both
# (if neither are None)
class rsakey:
l = None # the bit length
e = None # the public key (or None if it's just a private key)
d = None # the private key (or None if it's just a public key)
n = None # the modulus (n=p*q)
def __init__(self,_l,_e,_d,_n):
(self.l,self.e,self.d,self.n) = (_l,_e,_d,_n)
# Ciphertext class
class ciphertext:
c = [] # the list of the encrypted blocks
l = None # the length of the original plaintext file or string in bytes
b = None # the length of each block in bytes
def __init__(self,_c,_l,_b):
(self.c,self.l,self.b) = (_c,_l,_b)
# Whether to print verbose messages. This is useful for debugging, as it will
# never be set to true (via the -verbose flag) during grading.
verbose = False
# Whether the values of p and q are displayed during key generation. This is
# useful for debugging, and it *is* something that we will test during grading.
showPandQ = False
# This will write an RSA key to a file (or files) in the standard format used
# for this assignment. If the key contains both d and e, then two files
# (one private, one public) are written. Given the file basename of
# <filebasename>, the key files are <filebasename>-public.key and
# <filebasename>-private.key.
def writeKeyToFile(key, filebasename):
if key.e is not None: # it's a public key
with open(filebasename + "-public.key","w") as f:
print("public\n" + str(key.l) + "\n" + str(key.e) + "\n" + str(key.n) + "\n",file=f)
if key.d is not None: # it's a private key
with open(filebasename + "-private.key","w") as f:
print("private\n" + str(key.l) + "\n" + str(key.d) + "\n" + str(key.n) + "\n",file=f)
# This will read an RSA key from a file in the standard format used for this
# assignment
def readKeyFromFile(filename):
with open(filename) as f:
if f.readline().strip() == "public":
return rsakey(int(f.readline().strip()),int(f.readline().strip()),None,int(f.readline().strip()))
else:
return rsakey(int(f.readline().strip()),None,int(f.readline().strip()),int(f.readline().strip()))
# This will read cipher text from a file in the standard format used for this
# assignment
def readCipherTextFromFile(inputFileName):
c = []
with open(inputFileName) as f:
l = f.readline().strip().split(" ")
while True:
x = f.readline().strip()
if x == '':
break
c.append(int(x))
return ciphertext(c,l[0],l[1])
# This will write cipher text to a file in the standard format used for this
# assignment
def writeCipherTextToFile(outputFileName, cipherText):
with open(outputFileName,"w") as f:
print(cipherText.l,cipherText.b,file=f)
for c in cipherText.c:
print(c,file=f)
# this function is included here because there is a Java version, but it
# is not needed in Python: to convert a SHA-256 hash to hex form, just use:
# hashlib.sha256(bytes(plaintext,'ascii')).hexdigest()
def convertHash():
assert False
# Given an ASCII string, this will convert it to an integer representation
def convertFromASCII(text):
return int.from_bytes(bytes(text,'ascii'),"big")
# Given an integer representation of an ASCII string, this will convert it to
# ASCII
def convertToASCII(block):
h = hex(block)[2:]
if len(h) % 2 == 1:
h = '0' + h
return bytes.fromhex(h).decode('ascii')
# Given a bit size and a certainty, this will generate a (probably) prime
# number of the desired size
def generate_prime(bits, k):
count = 0
while True:
# generate a number, make sure it's odd
n = random.randint(2,2**(bits+1)-1)
if n % 2 == 0:
n += 1
count += 1
# run the Fermat primality test
iterations = 0
for _ in range(k):
a = random.randint(1,n-1)
if pow(a,n-1,n) != 1: # it's composite
break
else: # prime so far
iterations += 1
if iterations == k:
return n
#----------------------------------------
# You have to implement these functions
# Given the passed bitlength (int), it will generate a key. This returns a
# rsakey object.
def generateKeys(bitlength):
pass
# Given the passed rsakey object and string, this will perform the RSA
# encryption. It should return a ciphertext object.
def encrypt(key, plaintext):
pass
# Given the provided rsakey object and ciphertext object plaintext, this will
# perform the RSA decryption. It should return a string.
def decrypt(key, cipherText):
pass
# Given the passed rsakey, which will not have a private (d) key, it will
# determine the private key by attempting to factor n. It returns a rsakey
# object.
def crack(key):
pass
# Given the passed rsakey object and string, it will return a ciphertext object that
# is the digital signature of the text, signed with the private key.
def sign(key, plaintext):
pass
# Given the passed rsakey object, string, and ciphertext object, this will
# check the signature; it only returns True (if the signature is valid) or
# False (if not).
def checkSign(key,plaintext,signature):
pass
#----------------------------------------
# Don't modify this! Or, if you do modify this, make sure you submit the
# original version when you submit the assignment. This is necessary for our
# testing code.
def main():
global verbose, showPandQ, outputFileName, inputFileName
outputFileName = "output.txt"
inputFileName = "input.txt"
keyName = "default"
i = 1
while i < len(sys.argv):
if sys.argv[i] == "-verbose":
verbose = not verbose
elif sys.argv[i] == "-output":
i = i + 1
outputFileName = sys.argv[i]
elif sys.argv[i] == "-input":
i = i + 1
inputFileName = sys.argv[i]
elif sys.argv[i] == "-key":
i = i + 1
keyName = sys.argv[i]
elif sys.argv[i] == "-showpandq":
showPandQ = True
elif sys.argv[i] == "-keygen":
i = i + 1
bitLength = int(sys.argv[i])
key = generateKeys(bitLength)
writeKeyToFile (key,keyName)
elif sys.argv[i] == "-encrypt":
key = readKeyFromFile (keyName + "-public.key")
plaintext = open(inputFileName).read()
cipherText = encrypt(key, plaintext)
writeCipherTextToFile(outputFileName, cipherText)
elif sys.argv[i] == "-decrypt":
key = readKeyFromFile (keyName + "-private.key")
cipherText = readCipherTextFromFile(inputFileName)
plaintext = decrypt(key, cipherText)
with open(outputFileName,"w") as f:
print(plaintext,file=f,end='')
elif sys.argv[i] == "-sign":
key = readKeyFromFile (keyName + "-private.key")
plaintext = open(inputFileName).read()
signature = sign(key,plaintext)
writeCipherTextToFile(inputFileName+".sign", signature)
elif sys.argv[i] == "-checksign":
key = readKeyFromFile (keyName + "-public.key")
plaintext = open(inputFileName).read()
signature = readCipherTextFromFile(inputFileName+".sign")
result = checkSign(key,plaintext,signature)
if not result:
print("Signatures do not match!")
elif sys.argv[i] == "-crack":
key = readKeyFromFile (keyName + "-public.key")
cracked = crack(key)
writeKeyToFile (cracked,keyName+"-cracked")
elif sys.argv[i] == "-seed":
seed = int(sys.argv[++i])
random.seed(seed)
else:
print ("Unknown parameter: '" + str(sys.argv[i]) + "', exiting.")
exit()
i += 1
if __name__ == '__main__':
main()