I have forked a previous version of my script, critiqued here: Generate public/private keys, encrypt, decrypt, sign, verify
This program allows you to encrypt and decrypt raw files using RSA keys generated by the program. When generating the key pairs, the private key gets protected with aes 256.
I'm fond of the prime number theorem so I added my python code back for that instead of soley relying upon gmpy2.
The file becomes larger and it takes a long time to decrypt. For 8192 you're looking at 7 minutes a MB to decrypt. 3ish min/MB with 4096, and much faster smaller than that. File size is not ideal with asymmetric.
I'm aware that keys aren't purposefully used for data; a key is only typically 256 bits. That's why I wrote the first one. For symmetric speed like in real world application.
I wrote this so I can say or feel in my head that my file is TRULY being encrypted with N-bit encryption. For example using a 16000 bit key would provide 1000 bit security. You could cryptolock the borg with that. I know there's absolutely no reason guys. I know 256 is enough. And I know its not ideal for data encryption and is truly ideal for signing and key exchange. I love the math, I wanted to pump up the numbers and send files to the Crypto-Abyss and bring them back again.
This was an experiment; my other tool remains my instructional aid as that represents more of a real world implementation.
#!/usr/bin/env python3
import os
import sys
import math
import re
import hashlib
import random
import base64
import string
import getpass
import multiprocessing as mp
from Crypto.Cipher import AES
from Crypto import Random
from Crypto.Protocol.KDF import PBKDF2
#Non builtins
from gmpy2 import mpz as mpz
from gmpy2 import is_extra_strong_lucas_prp as is_eslprp
#Primality testing, extended greatest common divisor and least common multiple
def get1prime(keysize):
while True:
p = random.randrange(1<<(keysize-(keysize//256)), 1<<(keysize+(keysize//256)))
if isprime(p):
return p
def isprime(n):
n = mpz(n)
if not n & 1: #check if first bit is 1
return False
for i in (3,5,7,11):
if divmod(n, i)[1] == 0:
return False
#Fermat
if (pow(2, n-1, n)) != 1:
return False
#MilRab, x**2 = 1 mod P - ERH
s = 0
d = n-1
while not d & 1:
d>>=1 #shifts binary rep of number right one place, same as dividing by 2^d
s+=1
assert(2**s * d == n-1) #Process to find s and d
def trial_composite(a):
if pow(a, d, n) == 1:
return False
for i in range(s):
if pow(a, 2**i * d, n) == n-1:
return False
return True
for i in range(23):
a = random.randrange(2, n-1)
if trial_composite(a):
return False
if is_eslprp(n,1):
return True
else:
return False
def modInverse(a, m) : #Euclid's Extended Algorithm
m0 = m
y = 0
x = 1
while (a > 1) :
q = a // m
t = m
m = divmod(a,m)[1]
a = t
t = y
y = x - q * y
x = t
if (x < 0) :
x = x + m0
return x
def lcm(x, y):
return (x*y)//math.gcd(x,y)
##AES256CHUNK
def get_private_key(password):
salt = b"We will know, we must know"
kdf = PBKDF2(password, salt, 64, 1000)
key = kdf[:32]
return key
def encryptaes(raw, password):
private_key = password
raw = pad(raw)
iv = Random.new().read(AES.block_size)
cipher = AES.new(private_key, AES.MODE_CBC, iv)
return base64.b64encode(iv + cipher.encrypt(raw))
def decryptaes(enc, password):
private_key = password
enc = base64.b64decode(enc)
iv = enc[:16]
cipher = AES.new(private_key, AES.MODE_CBC, iv)
return unpad(cipher.decrypt(enc[16:]))
BLOCK_SIZE = 64 #Block is 128 no matter what,this is multiple of 16
pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * chr(BLOCK_SIZE - len(s) % BLOCK_SIZE)
unpad = lambda s: s[:-ord(s[len(s) - 1:])]
#RSA
#Unique and Arbitrary Pub E, a prime.
e = 66047 # because I can
#e = 65537
def encryptit(e, n, thestring):#for sigining pass d as e
thestring = pad(str(thestring)).encode()
rbinlist = ['{0:08b}'.format(x) for x in thestring]
catstring = ''
catstring += rbinlist[0].lstrip('0')
del rbinlist[0]
for i in rbinlist:
catstring += str(i)
puttynumber = int(catstring,2)
cypherstring = str(pow(mpz(puttynumber), mpz(e), mpz(n)))
return cypherstring
def decryptit(d, n, cynum):#for signing pass e as d
decryptmsg = ''
n = int(n)
d = int(d)
puttynum = pow(mpz(int(cynum)), mpz(d), mpz(n))
puttynum = '{0:08b}'.format(puttynum)
while True:
if len(puttynum)%8 == 0:
break
puttynum = '0{0}'.format(puttynum)
locs = re.findall('[01]{8}', puttynum)
for x in locs:
letter = chr(int(x,2))
decryptmsg += letter
return unpad(decryptmsg)
def chunkitE(exp, N, phatstr):
line = phatstr
n = len(bin(N))//16 # speed tune
newlist = [line[i:i+n] for i in range(0, len(line), n)]
#print(newlist)
cypherlist = []
for i in newlist:
cypherlist.append(encryptit(exp, N, i))
return cypherlist
def chunkitD(d, N, phatlistnum):
declist = []
for i in phatlistnum:
declist.append(decryptit(d, N, i))
return declist
def primegenerator(keysize):
while True:
primes = []
plist = []
for i in range(mp.cpu_count()):
plist.append(keysize)
workpool = mp.Pool(processes=mp.cpu_count())
reslist = workpool.imap_unordered(get1prime, plist)
workpool.close()
for res in reslist:
if res:
primes.append(res)
workpool.terminate()
break
workpool.join()
#
workpool1 = mp.Pool(processes=mp.cpu_count())
reslist = workpool1.imap_unordered(get1prime, plist)
workpool1.close()
for res in reslist:
if res:
primes.append(res)
workpool1.terminate()
break
workpool1.join()
return primes
#Begin User Flow
choice = input("""
██ ▄█▀▓█████ ▓██ ██▓ ██▀███ ▓██ ██▓ ██▓███ ▄▄▄█████▓
██▄█▒ ▓█ ▀ ▒██ ██▒▓██ ▒ ██▒ ▒██ ██▒▓██░ ██▒▓ ██▒ ▓▒
▓███▄░ ▒███ ▒██ ██░▓██ ░▄█ ▒ ▒██ ██░▓██░ ██▓▒▒ ▓██░ ▒░
▓██ █▄ ▒▓█ ▄ ░ ▐██▓░▒██▀▀█▄ ░ ▐██▓░▒██▄█▓▒ ▒░ ▓██▓ ░
▒██▒ █▄░▒████▒ ░ ██▒▓░░██▓ ▒██▒ ░ ██▒▓░▒██▒ ░ ░ ▒██▒ ░
▒ ▒▒ ▓▒░░ ▒░ ░ ██▒▒▒ ░ ▒▓ ░▒▓░ ██▒▒▒ ▒▓▒░ ░ ░ ▒ ░░
░ ░▒ ▒░ ░ ░ ░ ▓██ ░▒░ ░▒ ░ ▒░ ▓██ ░▒░ ░▒ ░ ░
░ ░░ ░ ░ ▒ ▒ ░░ ░░ ░ ▒ ▒ ░░ ░░ ░
░ ░ ░ ░ ░ ░ ░ ░ ░
░ ░ ░ ░
Welcome to Dan's Cryptography Concept Program.
Generate/Encrypt/Decrypt/Sign
RSA++/DSA++/AES/OTP/Double DH key method w SHA256
Choose:
A: Generate New Public/Private Key Pair
B: Encrypt a File RSA/DSA
C: Decrypt a File RSA/DSA
=> """)
if choice == 'A' or choice == 'a':
try:
keysize = (int(input("Enter a keysize: "))>>1)
except ValueError as a:
print('Enter a number\n\n')
sys.exit()
pubkeyname = input('Input desired public key name: ')
pkey = input('Input desired private key name: ')
pwkey = get_private_key(getpass.getpass(prompt='Password to protect your private key: ', stream=None))
print('Generating Keys...')
primes = primegenerator(keysize)
if primes[0] != primes[1]:
p, q = primes[0], primes[1]
else:
print('God hates you')
exit()
n = p*q
cm = lcm(p-1, q-1)
print('Computing Private key ...')
d = modInverse(e, cm)
print('Private Key Size: {} bits'.format(keysize*2))
print('Functional Length of: {}'.format(len(bin((d)))))
keystring = encryptaes(str(d).encode('ascii', errors='ignore').decode('utf-8'),pwkey)
b64key = bytes.decode(base64.encodestring(bytes(str(hex(n)).encode())))
with open(pkey, 'w') as f1:
f1.write(str(n)+'\n')
f1.write(bytes.decode(keystring))
with open(pubkeyname, 'w') as f2:
f2.write(b64key)
print('Complete - {} and {} generated'.format(pubkeyname,pkey))
print('e exponent: {}'.format(str(e)))
print("""
-----BEGIN PUBLIC KEY-----
{}-----END PUBLIC KEY-----
""".format(b64key))
b64privkey = b64key = bytes.decode(base64.encodestring(bytes(str(hex(d)).encode())))
print("""
-----BEGIN PRIVATE KEY-----
{}-----END PRIVATE KEY-----
""".format(b64privkey))
if choice == 'B' or choice == 'b':
lineoutholder = []
pubkeyname = input('Enter the PUBLIC key of the RECIPIENT: ')
privkey = input('Enter YOUR Private KEY for signing: ')
pwkey = get_private_key(getpass.getpass(prompt='Password for your private key: ', stream=None))
try:
with open(pubkeyname, 'r') as f1:
pubkey = f1.read()
except:
print('bad keyname')
exit()
n = int(bytes.decode(base64.decodestring(bytes(pubkey.encode()))), 16)
workfile = input('Enter the file to ENCRYPT: ')
outfile = input('Enter filename to WRITE out: ')
sha256_hash = hashlib.sha256()
try:
os.system('pigz -9 {0};mv {0}.gz {0}'.format(workfile))
with open(workfile, 'rb') as f2:
wholefile = f2.read()
with open(workfile, 'rb') as f2:#open again to clear memory
for byte_block in iter(lambda: f2.read(4096),b""):
sha256_hash.update(byte_block)
HASH = sha256_hash.hexdigest()
with open(privkey) as f3:
priv = f3.readlines()
except Exception as x:
print(x)
exit()
try:
d = int(bytes.decode(decryptaes(priv[1], pwkey)))
except:
print('Bad PW')
exit()
HASH = [str(ord(i)) for i in HASH]
numhash = ''.join(HASH)
signature = pow(int(numhash), d, int(priv[0]))
plaintext = base64.encodestring(wholefile)
cypherlist = chunkitE(e, n, plaintext.decode('ascii'))
cyphertext = "X".join(cypherlist)
concat = str(str(signature)+'CUTcutCUTcutCUT'+str(cyphertext))
with open(outfile, 'w') as f3:
f3.write(concat)
os.system('pigz -9 {0};mv {0}.gz {0};rm {1}'.format(outfile, workfile))
print('Wrote to {} ...'.format(outfile))
if choice == 'C' or choice == 'c':
dspubkeyname = input('Enter the PUBLIC key of the SENDER: ')
try:
with open(dspubkeyname, 'r') as f1:
pubkey = f1.read()
except:
print('bad keyname')
exit()
nsig = int(bytes.decode(base64.decodestring(bytes(pubkey.encode()))), 16)
privkey = input('YOUR Private KEY filename to access the data: ')
pwkey = get_private_key(getpass.getpass(prompt='Password for your private keyfile: ', stream=None))
workfile = input('Enter the file to DECRYPT: ')
outfile = input('Enter the filename to WRITE out: ')
print('DECRYPTING')
os.system('mv {0} {0}.gz;pigz -d {0}.gz'.format(workfile))
sha256_hash = hashlib.sha256()
try:
with open(workfile) as f1:
lineholder = f1.read().split('CUTcutCUTcutCUT')
signature, cyphertext = lineholder[0], lineholder[1]
except:
print('Bad file name or path')
exit()
try:
with open(privkey) as f2:
priv = f2.readlines()
except:
print('Bad private key location')
n = priv[0]
try:
d = int(bytes.decode(decryptaes(priv[1], pwkey)))
except:
print('Bad PW')
exit()
sigdec = pow(int(signature), e, nsig)
cypherlist = cyphertext.split("X")
plainlist = chunkitD(d, n, cypherlist)
decstr = ''.join(plainlist)
cleartext = base64.decodestring(bytes(decstr, 'ascii'))
with open(outfile, 'wb') as f1:
f1.write(cleartext)
with open(outfile, 'rb') as f2:
for byte_block in iter(lambda: f2.read(4096),b""):
sha256_hash.update(byte_block)
HASH = sha256_hash.hexdigest()
HASH = [str(ord(i)) for i in HASH]
numhash = ''.join(HASH)
if int(numhash) == int(sigdec):
print('Signature Verified')
else:
print('FAILURE, bad hash. TRANSPORTER ACCIDENT')
os.system('mv {0} {0}.gz;pigz -d {0}.gz;rm {1}'.format(outfile, workfile))
print('Wrote out to {} '.format(outfile))