2
\$\begingroup\$

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))
\$\endgroup\$
  • \$\begingroup\$ for the record: 256 is really only enough for transient communication. For less time-critical applications, 2048 bits or even 4096 are not unheard of. \$\endgroup\$ – Vogel612 Mar 18 at 10:49
  • 1
    \$\begingroup\$ Bad padding (especially for RSA), bad key encryption, magic values, CBC enabling (more) padding oracles, lots of unexplained code, DSA for encryption (?) and many more mistakes (did I see a raw signature in there?). This is good practice crypto code, but there are too many things for me to even look at, and the result is definitely not as secure as it should be. KEYRYPT is nice and all, but some kind of documentation of the functions would be nice. \$\endgroup\$ – Maarten Bodewes Apr 25 at 2:21
  • \$\begingroup\$ @MaartenBodewes To be fair, CBC padding oracles are unlikely to be relevant in this use case. \$\endgroup\$ – forest Apr 25 at 10:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.