Content Disclaimer Copyright @2020. All Rights Reserved. 
Links : Home Index (Subjects) Contact StatsToDo
Introduction RC4 AES RSA E F G DisclaimerI created this page during the Covid pandemic lockdown of 2021, partly to have something to do, partly to teach myself encryption, and partly to teach myself Python. Although I have done all I can to make sure the results are correct and reproducible, the algorithms presented on this page has not been extensively reviewed or tested, so that errors and omissions cannot be ruled out.Readers are reminded that there are numerous professionally developed encryption products. They are inexpensive and readily available, and should be used for any serious encryption needs. The contents of this page should therefore be treated as merely an exercise, and readers wishing to use them must accept their own responsibility for doing so. IntroductionThe history and usage of encryption is well describe and summarised in wikipedia, and not repeated here.For personal security needs, Microsoft Office, pdf, and Winzip all include the AES algorithm, which is considered secure as of 2021. For professional and institutional security, particularly for transmission and protection against intrusion, the pgp algorithm, in its various commercial variants is widely available and inexpensive. Most email clients also offer similar products as plug in. The 2020 review of available products can be consulted. ProgramsThis page presents 3 of the most commonly used encryption algorithms, each in its own set of subpanels
RC4 Introduction
RC4 Javascript Program
RC4 Python Crypto
RC4 Python Plain
RC4 is well described in Wikipedia, and only a very brief summary is presented here as the basis for subsequent descussions.
Description of RC4RC4 consists of the following components
Vulnerability of RC4When RC4 became available in the late 1980s, it was rapidly incorporated into many applications, and for encrypting messages to send over the Internet. After a few years of extensive use however, a vulnerability in RC4 was discovered.Although RC4 remains secure if the password is used only once, repeated use of the same password was found to be vulnerable, for the following sequence of reasons
Although there are many more secure encryption algorithms available, RC4 remains an important encryption method because it is a stream cipher that is low cost, simple and fast, and it can be embedded easily into other applications without licence or complex APIs. Repeated modifications of the basic RC4 algorithms have therefore been made as attempts to address this vulnerability (see Wikipedia).
I had difficulties finding a Python library for the basic RC4 algorithm, probably because the algorithm is so simple nobody bothered to create one. The only one I found was in the package Cryptography, and it is ARC4 for Alleged RC4. It adds a nonce, a string of random numbers to both the key and to the plain text, to disrupt the pattern of the cipher text, making it more difficult to hack. However, I could not make it work, as I have an incomplete understanding of the hash functions involved. The original code, copied from https://pycryptodome.readthedocs.io/en/latest/src/cipher/arc4.html is as follows
# * coding: utf8 * >>> from Crypto.Cipher import ARC4 >>> from Crypto.Hash import SHA >>> from Crypto.Random import get_random_bytes >>> from Crypto.Cipher import ARC4 >>> from Crypto.Hash import SHA >>> from Crypto.Random import get_random_bytes >>> >>> key = b'Very long and confidential key' >>> nonce = get_random_bytes(16) >>> tempkey = SHA.new(key+nonce).digest() >>> cipher = ARC4.new(tempkey) >>> msg = nonce + cipher.encrypt(b'Open the pod bay doors, HAL')However, I used the ARC4 library, excluded the nonce, and built some subroutines to handle input and output as text strings, and produced the same results as the Javascript and the Python plain code programs. This algorithm is therefore a short cut, but mostly a validation of the other two programs which I constructed Th code is as follows from Crypto.Cipher import ARC4 #Crypto is in package cryptography class MyRC4: """ class to do standard RC4 """ key = None def Init(self, pw): """ initialize password into byte key """ self.key = pw.encode() def Encrypt(self, pw, plainText): """ encrypt plain text string using password pw """ self.Init(pw) # creates key from password myrc4 = ARC4.new(self.key) # calls ARC4 from crypto ct = myrc4.encrypt(plainText) # cipher ct as byte object ctHex = ct.hex() # cipher as a hex string return ctHex def Decipher(self, pw, cipherText): """decipher hex string cipherText using password pw """ self.Init(pw) # creates key from password myrc4 = ARC4.new(self.key) # calls ARC4 from crypto ct = bytes.fromhex(cipherText) # convert cipher hex string byte objects pt = myrc4.decrypt(ct) # plain as byte object pt = pt.decode() # decode to plain text return pt if __name__ == "__main__": """ Testing """ rc4 = MyRC4() pw = "MyPassword" plainText = "The quick brown fox jumps over the lazy dog" cipherText = rc4.Encrypt(pw, plainText) print(cipherText) plainText = rc4.Decipher(pw,cipherText) print(plainText)Results from testing d8ff380d3055f42c4d6681bce14b63a595752d3a541aa6974c34138e67233d014cc0fcadb461354af1222f The quick brown fox jumps over the lazy dog
The code presented in this panel is a direct translation of the Javascript ptogram in the earlier panel.
# * coding: utf8 * class RC4: """ from source code from Github: farhadi / rc4.js in Instantly share code, notes, and snippets. https://gist.github.com/farhadi/2185197 """ def __init__(self):# class variable self.sAr = [] # the S array for rc4 self.ii = 0 # array indeces self.jj = 0 """ shuffles array with a list of bytes of any length """ def ShuffleSArray(self, keyAr): leng = len(keyAr) # length of keyAr lengthOfCycle = 256 # set length to sAr at 256 if(leng>256): lengthOfCycle = leng # change to len if len greater j = 0 for i in range(lengthOfCycle): si = i%256 # index of S array ki = i%leng # index of key array j = (j + self.sAr[si] + keyAr[ki]) % 256 x = self.sAr[si] self.sAr[si] = self.sAr[j] self.sAr[j] = x """ Changes the s array with password """ def SetPassword(self, password): """ creates the S array with string passwords """ """ fill initial S array """ self.sAr = [n for n in range(256)] """ convert password to key array of bytes """ keyAr = [] for i in range(len(password)): keyAr.append(ord(password[i])) """ shuffles S array and reset keys """ self.ShuffleSArray(keyAr) self.ii = 0 self.jj = 0 """ Get the next byte from S array uses the Pseudorandom generation algorithm (PRGA) """ def GetNextCipher(self): self.ii = (self.ii + 1) % 256 self.jj = (self.jj + self.sAr[self.ii]) % 256 tmp = self.sAr[self.ii] self.sAr[self.ii] = self.sAr[self.jj] self.sAr[self.jj] = tmp tmp = (self.sAr[self.ii] + self.sAr[self.jj]) % 256 return self.sAr[tmp] def DecToHex(self, i, c): """ decimal to hex : i=integer, c=number of character """ fStr = '{:0' + str(c) + 'x}' return fStr.format(i) def DecipherByte(self,txt,i): h = int(txt[i:i+2],16) # hex > byte return h ^ self.GetNextCipher() # decipher byte def RC4_Encrypt(self, plainText, password): """ encrypt plainText with password """ # creates the S array and set ii self.SetPassword(password) cipherText = "" for i in range(len(plainText)): a = ord(plainText[i]) # character > byte e = a ^ self.GetNextCipher() # encrypted byte cipherText += self.DecToHex(e,2) # append to cipherText return cipherText def RC4_Decipher(self, cipherText, password): """ decipher cipherText with password """ self.SetPassword(password) # creates the S array and set ii plainText = "" i = 0 while(iResults of testing. Note: RC4Plus produces different cipher text each time even with the same password and plain text The quick brown fox jumps over the lazy dog MyPasswordc 67bcbe17291c68e9ae3fd2acebad9747b8f8ab1f3505ee3d84eb943be2aac8e2449d065f92111dc1d5c34a The quick brown fox jumps over the lazy dog
Introduction
AES in Javascript Program
AES with Python Crypto
AES Python Plain
AES is widely and clearly described on the www, examples of which are that by Wikipedia and this tutorial. This panel will therefore provide only a brief summary, as a basis for understanding the program and how to use it.
AES is currently (2021) accepted as the standard symmetrical key encryption, and the 128 bit key is considered unbreakable using current computer power. The program takes blocks of 16 bytes from the plain data, and shuffle it in a way that is controlled by the key. The result cipher is a combination of the key and the original information. The original information can be decrypted from the cipher using the same key in a reverse process. Currently AES is widely ditributed commercially. It is accepted by the US military, and included in the password protection of Microsoft Office documents, pdf, and Winzip. All 3 programs are modifications of the originals that I have found on the www. They all work, but produces different cypher text.
Password can be any length. If too short, program will expand it by repeating the input. If too long the excess will be discarded. Default Examples can be replaced by user's own choice The original Javascript source code was downloaded from http://pointatinfinity.org/jsaes/jsaes.js, and this web based user interface added.
There are many AES libraries in Python, with varying degrees of sophistication and complexity. I have chosen this particular algorithm because it is simple and terse, so easy to understand and use. It requires only the addition of the Cryptography package to the environment.
The program is copied from http://www.codekoala.com/posts/aesencryptionpythonusingpycrypto/, and modified. Readers are encouraged to access the original and compare it with the following modified version. The original version was for demonstrating the algorithm, and contained many short cuts. It was also in a previous version of Python, and some of the commands would not work with Python 3. The key and cipher text was also in the Python Bytes object syntex, and would be difficult for inexperienced programmer to access and use. The main changes are
from Crypto.Cipher import AES import base64 """ the block size for the cipher object; must be 16 per FIPS197 block size can be set by the programmer, by commenting out must be 16 (128 bits), 24 (192 bits) or 32 (256 bits) """ BLOCK_SIZE = 16 # default in original program #BLOCK_SIZE = 24 #BLOCK_SIZE = 32 """ The character used for paddingwith a block cipher such as AES, the value you encrypt must be a multiple of BLOCK_SIZE in length. This character is used to ensure that your value is always a multiple of BLOCK_SIZE """ #PADDING = '{' # default from original code PADDING = chr(0) # to avoid error when a real character { is being coded """ oneliner to sufficiently pad the text to be encrypted """ pad = lambda s: s + (BLOCK_SIZE  len(s) % BLOCK_SIZE) * PADDING """ Origonal oneliners to encrypt/encode and decrypt/decode a string EncodeAES = lambda c, s: base64.b64encode(c.encrypt(pad(s))) DecodeAES = lambda c, e: c.decrypt(base64.b64decode(e)).rstrip(PADDING) """ """ function to encode. c an instance of the class AES s the plain text produces cipher text hex string """ EncodeAES = lambda c, s: base64.b64encode(c.encrypt(pad(s))).hex() """ function to decode. c an instance of the class AES e the cipher text, a hex string produces decrypted plain text string """ DecodeAES = lambda c, e: c.decrypt(base64.b64decode(bytes.fromhex(e))).decode().rstrip(PADDING) """ Alternative codes handling cipher string as base64 numbers as in original codes and is shorter #EncodeAES = lambda c, s: base64.b64encode(c.encrypt(pad(s))).decode() #DecodeAES = lambda c, e: c.decrypt(base64.b64decode(e.encode())).decode().rstrip(PADDING) """ """ converts password (a text string) to a key (bytes object) key is then used to instantiate class AES """ def PasswordToKey(pw): """ make key (bytes) from password and set block size """ if len(pw) > BLOCK_SIZE : pw = pw[0 : BLOCK_SIZE] # Too long i = 0 while len(pw) < BLOCK_SIZE: # too short pw += str(pw[i]) i += 1 key = pw.encode() # make key return key def Encrypt(pw, pt): """ pw = password pt = plain text both are chr strs ct is encrypted numbers in a hex string """ key = PasswordToKey(pw) # make key from password aes = AES.new(key) # instantiate class AES ct = EncodeAES(aes, pt) # encrypt to cipher text return ct def Decrypt(pw, ct): """ pw = password chr string ct = cipher text hex string pt is decrypted plain text in chrs """ key = PasswordToKey(pw) # make key from password aes = AES.new(key) # instantiate class AES pt = DecodeAES(aes, ct) # decrypt to plain text return pt if __name__ == "__main__": """ Testing Program """ """ Before running program: 1. Check BLOCK_SIZE. Must be 16, 24, or 32. Bigger more secure. 2. Check padding character PADDING. Recommend default settings unless there is good reason to do otherwise """ pw = "MyPassword" #password pt = "The quick brown fox jumps over the lazy gog" #plain text print(pt) ct = Encrypt(pw, pt) # cipher text in hex print(ct) dt = Decrypt(pw, ct) # decrypt text (plain text) print(dt) print("\nExample of using actual text strings to run") pt = "Hello" print(pt) ct = Encrypt('MyPassword', "Hello") print(ct) dt = Decrypt('MyPassword', '754f667376704e4f484e635259616f357149537563673d3d') print(dt)On running the test program, the original plain text, the cipher text is a hex string, and the decrypted palain text are printed out as follows. Please note that the cipher text is a continupus string of hex characters. I have chopped it up to fit it in the panel The quick brown fox jumps over the lazy gog 6276486b67316d366d696657574d5774455877416d414a42303054697a62503571503545674d644f5 84b5772426563546a43712b76612f47723630774154484b The quick brown fox jumps over the lazy gog Example of using actual text strings to run Hello 754f667376704e4f484e635259616f357149537563673d3d Hello
This program is downloaded from https://github.com/DimuthuKasunWP/AES_Algorithm_Cryptography/blob/master/aes.py and modified as follows
""" This is an exercise in secure symmetrickey encryption, implemented in pure Python (no external libraries needed). Original AES128 implementation by Bo Zhu (http://about.bozhu.me) at https://github.com/bozhu/AESPython . PKCS#7 padding, CBC mode, PKBDF2, HMAC, byte array and string support added by me at https://github.com/boppreh/aes. Other block modes contributed by @righthandabacus. Although this is an exercise, the `encrypt` and `decrypt` functions should provide reasonable security to encrypted messages. """ s_box = ( 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16, ) inv_s_box = ( 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB, 0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB, 0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E, 0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25, 0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92, 0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84, 0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06, 0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B, 0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73, 0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E, 0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B, 0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4, 0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F, 0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF, 0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D, ) def sub_bytes(s): for i in range(4): for j in range(4): s[i][j] = s_box[s[i][j]] def inv_sub_bytes(s): for i in range(4): for j in range(4): s[i][j] = inv_s_box[s[i][j]] def shift_rows(s): s[0][1], s[1][1], s[2][1], s[3][1] = s[1][1], s[2][1], s[3][1], s[0][1] s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2] s[0][3], s[1][3], s[2][3], s[3][3] = s[3][3], s[0][3], s[1][3], s[2][3] def inv_shift_rows(s): s[0][1], s[1][1], s[2][1], s[3][1] = s[3][1], s[0][1], s[1][1], s[2][1] s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2] s[0][3], s[1][3], s[2][3], s[3][3] = s[1][3], s[2][3], s[3][3], s[0][3] def add_round_key(s, k): for i in range(4): for j in range(4): s[i][j] ^= k[i][j] # learned from http://cs.ucsb.edu/~koc/cs178/projects/JT/aes.c xtime = lambda a: (((a << 1) ^ 0x1B) & 0xFF) if (a & 0x80) else (a << 1) def mix_single_column(a): # see Sec 4.1.2 in The Design of Rijndael t = a[0] ^ a[1] ^ a[2] ^ a[3] u = a[0] a[0] ^= t ^ xtime(a[0] ^ a[1]) a[1] ^= t ^ xtime(a[1] ^ a[2]) a[2] ^= t ^ xtime(a[2] ^ a[3]) a[3] ^= t ^ xtime(a[3] ^ u) def mix_columns(s): for i in range(4): mix_single_column(s[i]) def inv_mix_columns(s): # see Sec 4.1.3 in The Design of Rijndael for i in range(4): u = xtime(xtime(s[i][0] ^ s[i][2])) v = xtime(xtime(s[i][1] ^ s[i][3])) s[i][0] ^= u s[i][1] ^= v s[i][2] ^= u s[i][3] ^= v mix_columns(s) r_con = ( 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36, 0x6C, 0xD8, 0xAB, 0x4D, 0x9A, 0x2F, 0x5E, 0xBC, 0x63, 0xC6, 0x97, 0x35, 0x6A, 0xD4, 0xB3, 0x7D, 0xFA, 0xEF, 0xC5, 0x91, 0x39, ) def bytes2matrix(text): """ Converts a 16byte array into a 4x4 matrix. """ return [list(text[i:i+4]) for i in range(0, len(text), 4)] def matrix2bytes(matrix): """ Converts a 4x4 matrix into a 16byte array. """ return bytes(sum(matrix, [])) def xor_bytes(a, b): """ Returns a new byte array with the elements xor'ed. """ return bytes(i^j for i, j in zip(a, b)) def inc_bytes(a): """ Returns a new byte array with the value increment by 1 """ out = list(a) for i in reversed(range(len(out))): if out[i] == 0xFF: out[i] = 0 else: out[i] += 1 break return bytes(out) def pad(plaintext): """ Pads the given plaintext with PKCS#7 padding to a multiple of 16 bytes. Note that if the plaintext size is a multiple of 16, a whole block will be added. """ padding_len = 16  (len(plaintext) % 16) padding = bytes([padding_len] * padding_len) return plaintext + padding def unpad(plaintext): """ Removes a PKCS#7 padding, returning the unpadded text and ensuring the padding was correct. """ padding_len = plaintext[1] assert padding_len > 0 message, padding = plaintext[:padding_len], plaintext[padding_len:] assert all(p == padding_len for p in padding) return message def split_blocks(message, block_size=16): assert len(message) % block_size == 0 return [message[i:i+16] for i in range(0, len(message), block_size)] class AES: """ Class for AES128 encryption with CBC mode and PKCS#7. This is a raw implementation of AES, without key stretching or IV management. Unless you need that, please use `encrypt` and `decrypt`. """ rounds_by_key_size = {16: 10, 24: 12, 32: 14} def __init__(self, master_key): """ Initializes the object with a given key. """ assert len(master_key) in AES.rounds_by_key_size self.n_rounds = AES.rounds_by_key_size[len(master_key)] self._key_matrices = self._expand_key(master_key) def _expand_key(self, master_key): """ Expands and returns a list of key matrices for the given master_key. """ # Initialize round keys with raw key material. key_columns = bytes2matrix(master_key) iteration_size = len(master_key) // 4 # Each iteration has exactly as many columns as the key material. columns_per_iteration = len(key_columns) i = 1 while len(key_columns) < (self.n_rounds + 1) * 4: # Copy previous word. word = list(key_columns[1]) # Perform schedule_core once every "row". if len(key_columns) % iteration_size == 0: # Circular shift. word.append(word.pop(0)) # Map to SBOX. word = [s_box[b] for b in word] # XOR with first byte of RCON, since the others bytes of RCON are 0. word[0] ^= r_con[i] i += 1 elif len(master_key) == 32 and len(key_columns) % iteration_size == 4: # Run word through Sbox in the fourth iteration when using a # 256bit key. word = [s_box[b] for b in word] # XOR with equivalent word from previous iteration. word = xor_bytes(word, key_columns[iteration_size]) key_columns.append(word) # Group key words in 4x4 byte matrices. return [key_columns[4*i : 4*(i+1)] for i in range(len(key_columns) // 4)] def encrypt_block(self, plaintext): """ Encrypts a single block of 16 byte long plaintext. """ assert len(plaintext) == 16 plain_state = bytes2matrix(plaintext) add_round_key(plain_state, self._key_matrices[0]) for i in range(1, self.n_rounds): sub_bytes(plain_state) shift_rows(plain_state) mix_columns(plain_state) add_round_key(plain_state, self._key_matrices[i]) sub_bytes(plain_state) shift_rows(plain_state) add_round_key(plain_state, self._key_matrices[1]) return matrix2bytes(plain_state) def decrypt_block(self, ciphertext): """ Decrypts a single block of 16 byte long ciphertext. """ assert len(ciphertext) == 16 cipher_state = bytes2matrix(ciphertext) add_round_key(cipher_state, self._key_matrices[1]) inv_shift_rows(cipher_state) inv_sub_bytes(cipher_state) for i in range(self.n_rounds  1, 0, 1): add_round_key(cipher_state, self._key_matrices[i]) inv_mix_columns(cipher_state) inv_shift_rows(cipher_state) inv_sub_bytes(cipher_state) add_round_key(cipher_state, self._key_matrices[0]) return matrix2bytes(cipher_state) def encrypt_cbc(self, plaintext, iv): """ Encrypts `plaintext` using CBC mode and PKCS#7 padding, with the given initialization vector (iv). """ assert len(iv) == 16 plaintext = pad(plaintext) blocks = [] previous = iv for plaintext_block in split_blocks(plaintext): # CBC mode encrypt: encrypt(plaintext_block XOR previous) block = self.encrypt_block(xor_bytes(plaintext_block, previous)) blocks.append(block) previous = block return b''.join(blocks) def decrypt_cbc(self, ciphertext, iv): """ Decrypts `plaintext` using CBC mode and PKCS#7 padding, with the given initialization vector (iv). """ assert len(iv) == 16 blocks = [] previous = iv for ciphertext_block in split_blocks(ciphertext): # CBC mode decrypt: previous XOR decrypt(ciphertext) blocks.append(xor_bytes(previous, self.decrypt_block(ciphertext_block))) previous = ciphertext_block return unpad(b''.join(blocks)) def encrypt_pcbc(self, plaintext, iv): """ Encrypts `plaintext` using PCBC mode and PKCS#7 padding, with the given initialization vector (iv). """ assert len(iv) == 16 plaintext = pad(plaintext) blocks = [] prev_ciphertext = iv prev_plaintext = bytes(16) for plaintext_block in split_blocks(plaintext): # PCBC mode encrypt: encrypt(plaintext_block XOR (prev_ciphertext XOR prev_plaintext)) ciphertext_block = self.encrypt_block(xor_bytes(plaintext_block, xor_bytes(prev_ciphertext, prev_plaintext))) blocks.append(ciphertext_block) prev_ciphertext = ciphertext_block prev_plaintext = plaintext_block return b''.join(blocks) def decrypt_pcbc(self, ciphertext, iv): """ Decrypts `plaintext` using PCBC mode and PKCS#7 padding, with the given initialization vector (iv). """ assert len(iv) == 16 blocks = [] prev_ciphertext = iv prev_plaintext = bytes(16) for ciphertext_block in split_blocks(ciphertext): # PCBC mode decrypt: (prev_plaintext XOR prev_ciphertext) XOR decrypt(ciphertext_block) plaintext_block = xor_bytes(xor_bytes(prev_ciphertext, prev_plaintext), self.decrypt_block(ciphertext_block)) blocks.append(plaintext_block) prev_ciphertext = ciphertext_block prev_plaintext = plaintext_block return unpad(b''.join(blocks)) def encrypt_cfb(self, plaintext, iv): """ Encrypts `plaintext` using CFB mode and PKCS#7 padding, with the given initialization vector (iv). """ assert len(iv) == 16 plaintext = pad(plaintext) blocks = [] prev_ciphertext = iv for plaintext_block in split_blocks(plaintext): # CFB mode encrypt: plaintext_block XOR encrypt(prev_ciphertext) ciphertext_block = xor_bytes(plaintext_block, self.encrypt_block(prev_ciphertext)) blocks.append(ciphertext_block) prev_ciphertext = ciphertext_block return b''.join(blocks) def decrypt_cfb(self, ciphertext, iv): """ Decrypts `plaintext` using CFB mode and PKCS#7 padding, with the given initialization vector (iv). """ assert len(iv) == 16 blocks = [] prev_ciphertext = iv for ciphertext_block in split_blocks(ciphertext): # CFB mode decrypt: ciphertext XOR decrypt(prev_ciphertext) plaintext_block = xor_bytes(ciphertext_block, self.decrypt_block(prev_ciphertext)) blocks.append(plaintext_block) prev_ciphertext = ciphertext return unpad(b''.join(blocks)) def encrypt_ofb(self, plaintext, iv): """ Encrypts `plaintext` using OFB mode and PKCS#7 padding, with the given initialization vector (iv). """ assert len(iv) == 16 plaintext = pad(plaintext) blocks = [] previous = iv for plaintext_block in split_blocks(plaintext): # OFB mode encrypt: plaintext_block XOR encrypt(previous) block = self.encrypt_block(previous) ciphertext_block = xor_bytes(plaintext_block, block) blocks.append(ciphertext_block) previous = block return b''.join(blocks) def decrypt_ofb(self, ciphertext, iv): """ Decrypts `plaintext` using OFB mode and PKCS#7 padding, with the given initialization vector (iv). """ assert len(iv) == 16 blocks = [] previous = iv for ciphertext_block in split_blocks(ciphertext): # OFB mode decrypt: ciphertext XOR decrypt(previous) block = self.decrypt_block(previous) plaintext_block = xor_bytes(ciphertext_block, block) blocks.append(plaintext_block) previous = block return unpad(b''.join(blocks)) def encrypt_ctr(self, plaintext, iv): """ Encrypts `plaintext` using CTR mode and PKCS#7 padding, with the given initialization vector (iv). """ assert len(iv) == 16 plaintext = pad(plaintext) blocks = [] nonce = iv for plaintext_block in split_blocks(plaintext): # CTR mode encrypt: plaintext_block XOR encrypt(nonce) block = xor_bytes(plaintext_block, self.encrypt_block(nonce)) blocks.append(block) nonce = inc_bytes(nonce) return b''.join(blocks) def decrypt_ctr(self, ciphertext, iv): """ Decrypts `plaintext` using CTR mode and PKCS#7 padding, with the given initialization vector (iv). """ assert len(iv) == 16 blocks = [] nonce = iv for ciphertext_block in split_blocks(ciphertext): # CTR mode decrypt: ciphertext XOR decrypt(nonce) block = xor_bytes(ciphertext_block, self.decrypt_block(nonce)) blocks.append(block) nonce = inc_bytes(nonce) return unpad(b''.join(blocks)) import os from hashlib import pbkdf2_hmac from hmac import new as new_hmac, compare_digest AES_KEY_SIZE = 16 HMAC_KEY_SIZE = 16 IV_SIZE = 16 SALT_SIZE = 16 HMAC_SIZE = 32 def get_key_iv(password, salt, workload=100000): """ Stretches the password and extracts an AES key, an HMAC key and an AES initialization vector. """ stretched = pbkdf2_hmac('sha256', password, salt, workload, AES_KEY_SIZE + IV_SIZE + HMAC_KEY_SIZE) aes_key, rest = stretched[:AES_KEY_SIZE], stretched[AES_KEY_SIZE:] hmac_key, rest = stretched[:HMAC_KEY_SIZE], stretched[HMAC_KEY_SIZE:] iv = stretched[:IV_SIZE] return aes_key, hmac_key, iv def encrypt(key, plaintext, workload=100000): """ Encrypts `plaintext` with `key` using AES128, an HMAC to verify integrity, and PBKDF2 to stretch the given key. The exact algorithm is specified in the module docstring. """ if isinstance(key, str): key = key.encode('utf8') if isinstance(plaintext, str): plaintext = plaintext.encode('utf8') salt = os.urandom(SALT_SIZE) key, hmac_key, iv = get_key_iv(key, salt, workload) ciphertext = AES(key).encrypt_cbc(plaintext, iv) hmac = new_hmac(hmac_key, salt + ciphertext, 'sha256').digest() assert len(hmac) == HMAC_SIZE return hmac + salt + ciphertext def decrypt(key, ciphertext, workload=100000): """ Decrypts `plaintext` with `key` using AES128, an HMAC to verify integrity, and PBKDF2 to stretch the given key. The exact algorithm is specified in the module docstring. """ assert len(ciphertext) % 16 == 0, "Ciphertext must be made of full 16byte blocks." assert len(ciphertext) >= 32, """ Ciphertext must be at least 32 bytes long (16 byte salt + 16 byte block). To encrypt or decrypt single blocks use `AES(key).decrypt_block(ciphertext)`. """ if isinstance(key, str): key = key.encode('utf8') hmac, ciphertext = ciphertext[:HMAC_SIZE], ciphertext[HMAC_SIZE:] salt, ciphertext = ciphertext[:SALT_SIZE], ciphertext[SALT_SIZE:] key, hmac_key, iv = get_key_iv(key, salt, workload) expected_hmac = new_hmac(hmac_key, salt + ciphertext, 'sha256').digest() assert compare_digest(hmac, expected_hmac), 'Ciphertext corrupted or tampered.' return AES(key).decrypt_cbc(ciphertext, iv) """ END IMPORTED ORIGINAL PROGRAM Encrypt and Decrypt are Added functions, mostly to manipulate the input and output into text strings """ def Encrypt(password, plainText): """ password and plainText are both character texts. They can be any length, returns cipherText, a hex string """ cipherBytes = encrypt(password, plainText) cipherList = bytearray(cipherBytes) # bytes object to byte list cipherText = "" # byte list to hex string for v in cipherList: hx = hex(v)[2:] if len(hx)<2: hx = "0" + hx cipherText += hx return cipherText #hex string def Decrypt(password, cipherText): """ password is character texts. It can be any lengthut must be the same as the encrypting password cipherText is a hex string as created by Encrypt result bytes object is then converted to plain text """ cipherByteList = [] # hex string to byte list i = 0 while i<len(cipherText): cipherByteList.append(int(cipherText[i:i+2],16)) i+=2 cipherBytes = bytearray(cipherByteList) # byte list to byte object decryptedBytes = decrypt(password,cipherBytes) return decryptedBytes.decode() # text string if __name__ == '__main__': """ Testing """ password = "MyPassword" plainText = "The quick brown fox jumps over the lazy dog" cipherText = Encrypt(password, plainText) print(cipherText) decryptedText = Decrypt(password, cipherText) print(decryptedText)The results are as follows. Note: the cipher text hex string is a continuous line, I have chopped it up to fit into the panel 67448b5c8dbb19e9c934bdf6455f19b36888d45575db80a1577c56bc3c157fbf70b913a631bd5 e3092d1a08f39fbbbf983e88256eaadf2ab7c2bb7c4637a38ae06dd7ed078f0ac916c94960b87 c3bcb6b481041121303ee62f6e4f7d930be0fb The quick brown fox jumps over the lazy dog
RSA Introduction
RSA Javascript Program
RSA Python Crypto
RSA Python Plain
There are numerous resources available on the www regarding RSA, examples of which are wikipedia and this tutorial. Only a very brief introduction will therefore be offered in this page, to be the basis of understanding and discussions of the programs.
RSA is the asymmetical key encryption that is in common use (2021). The mathematical principles are as follows
Note: Detailed discussions on doing RSA in Javascript in Section 5, bottom of this panel
Section 1. Create Keys
Section 2. Keys To Be Used for Encryption and Decryption in Sections 3 and 4
Section 3. Encrypt or Decrypt a Number Using Keys in Section 2
Section 4. Encrypt or Decrypt Text string Using Keys in Section 24.a. Encrypt character text string to space delimited numerical string
Section 5. DiscussionsKey size (Section 1)Although RSA is conceptually simple and elegant, requiring only a few lines of code to perform the task, it presents major difficulties in implementation using Javascript. The main difficulty is the size of the numbers used in calculation, and from that the time it takes to do the calculations. The encryption and decryption both use the formula (data^{key}) % modulus, the larger the key, the more secure but also takes longer to calculate. Ordinary Javascript has a maximum value of 2^{52} so the level of security obtained is hardly worth the effort. With the introduction of the data type BigInt larger numbers can be processed. However, the program crashes when calculating n^{n}, if n > 65534. At that level, even if the calculation is possible, it takes a very long time to complete. As the modulus (P*Q, n, MOD) is the largest number in the equation, and represents key size, a limit of P*Q<65534 is imposed on the Javascript program in this panel. This represents a key size of just under 16 bits. This means that the algorithm in this panel is not secure enough for commercial use, and is thus for demonstrating how RSA works only. Creating the keys (Section 1)
The keys (Section 2) The keys are in the text boxes of Section 2. The values are inserted when section 1 (creating keys) is run, or the user can enter previously created keys by hand. The public key (E) is required for encryption, and private key (D) for decryption, and the common key (P*Q) is required for both. Encrypting or decrypting a number (Section 3) The appropriate keys must be present in the text boxes of section 2, and the number must be > 0 and < the value of the common key. If there is an error the value of 1 is returned Encrypting a character string (Section 4.a.)
Decrypting a string of numbers separated by single spaces (Section 4.b.)
This program was originally copied from https://www.tutorialspoint.com/cryptography_with_python/cryptography_with_python_symmetric_and_asymmetric_cryptography.htm. This is the simplest RSA program that contains all the following features
from Crypto import Random from Crypto.PublicKey import RSA import base64 def generate_keys(): # key length must be a multiple of 256 and >= 1024 modulus_length = 256*4 privatekey = RSA.generate(modulus_length, Random.new().read) publickey = privatekey.publickey() return privatekey, publickey def encrypt_message(a_message , publickey): encrypted_msg = publickey.encrypt(a_message, 32)[0] encoded_encrypted_msg = base64.b64encode(encrypted_msg) return encoded_encrypted_msg def decrypt_message(encoded_encrypted_msg, privatekey): decoded_encrypted_msg = base64.b64decode(encoded_encrypted_msg) decoded_decrypted_msg = privatekey.decrypt(decoded_encrypted_msg) return decoded_decrypted_msg """ END OF ORIGINAL PROGRAM The following are additions to handle keys and messages as texts """ def GenerateKeys(): privatekey , publickey = generate_keys() #calls original function #modify public key publickeyList = publickey.exportKey().decode().split() # turn to list reducedList =[publickeyList[i] for i in range(3,len(publickeyList)3)] #chop out descriptions publicKey = '\n'.join(reducedList) # reconstitute the core part #modifies private key privatekeyList = privatekey.exportKey().decode().split() # turn to list reducedList =[privatekeyList[i] for i in range(4,len(privatekeyList)4)] #chop out descriptions privateKey = '\n'.join(reducedList) # reconstitute the core part return privateKey, publicKey # returns both keys as base 64 text strings def Encrypt(publicKey, plainText): #public key and plain text are b64 and chr strings pub = RSA.importKey(base64.b64decode(publicKey.encode())) # converts to b64 byte object return encrypt_message(plainText.encode() , pub).decode() # cipher text as b64 string def Decrypt(privateKey, cipherText): #private key and cipher text both b64 string pri = RSA.importKey(base64.b64decode(privateKey.encode())) # converts to b64 byte object return decrypt_message(cipherText.encode() , pri).decode() # decypted chr string if __name__ == "__main__": # create keys privateKey, publicKey = GenerateKeys() print('privateKey') print(privateKey) print('publicKey') print(publicKey) # plain text plainText = "The quick brown fox jumps over the lazy dog" print('Original Plain Text Message') print(plainText) # Encrypt cipherText = Encrypt(publicKey, plainText) print('cipher textext') print(cipherText) # Decipher decryptedText = Decrypt(privateKey, cipherText) print('decrypted text') print(decryptedText) # Direct I/O print('\nTesting Direct Input') cipherText = Encrypt('MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCZN6rVXbmiROt\ FYM/NsGluUdbkrXh+NtOVER4an6Tf+u1Ac6zD8/QN0vUDxNQ4YwINhxUkIFzUPu774cIi7muaUhZY\ IZP847xgFW78bhEKrYb0o4Noy0q1OyGftZoT1WFiHiTRiPFjqwg0HAyjavnqTeq7eObmvkz21LcX8/\ 38UQIDAQAB', 'Hello There') print(cipherText) decryptedText = Decrypt('MIICXQIBAAKBgQCZN6rVXbmiROtFYM/NsGluUdbkrXh+NtOV\ ER4an6Tf+u1Ac6zD8/QN0vUDxNQ4YwINhxUkIFzUPu774cIi7muaUhZYIZP847xgFW78bhEKrYb0o4\ Noy0q1OyGftZoT1WFiHiTRiPFjqwg0HAyjavnqTeq7eObmvkz21LcX8/38UQIDAQABAoGAChaDNfcs\ 0MVO5EuCgx15Y50Z1Aaj51N+zNLKs6ANP/4KfvLeziwSxpI8NZpRCsFiEjfxqWZEFmlqXMU5fglKpk\ 1oayv4dqxlaur+2wht76OAN3QWe0jAE97MZw9jqd8Vnf0pqduXank/oC27p10RGCf3yYrvXxYwL3+7\ 4bcm9/0CQQDAvzh72jSrACrOMHyjB6tluMwLxXgOyESuyLfTevOlHzz6hwaMz/21n4yReX9+i3+y5R\ pVNM2QbVHiH4sZIQvrAkEAy3+NBmR9W6yG45YvJkjoihVLozIzd24sCCYCxWxa8Ce9YzyNn+srLz9l\ bcBXrJE6i9ZfxssS+dOtu6G6W4E1swJBAIyU6+WpqXBvlsj8pGtkVKbEuk57oK1ndHDnBOzCaKKuvh\ McGLLroOivjh8stsjdhi4824/6C1Sj520+BH43lDECQQC7TIrgd11qI0GD95cuBa0CatdTPcFhC2Y7\ mcCzNSf+IpWN4Q35Qtpcgl04xu/rRUA9tPIyZnbwuoQNTq3XyvVBAkA8sWPkBOaAr50YbDLorcZiq4\ o2XOXM8bFm4HUV1Fidz22t/IQuUMFOFo+V/YRzPgULDrjQqWOd9Hiajq1UKe3y', 'Bb4WbQ865UIR\ Q0NN1lCUOwOQnkHsuHHeCG2SCItwYTKEFiNZiT5RNK0rifpAOnYKDI/KGutb39xcsusD/rzliDVTa9\ 2a1P1c26hdFCiiPXkXsSNxLkuJb7a0ycj+gGZRcoOfasKRrJoy9bZ7Uk69On2+A/R0l+c3xqlTAxFP\ n6w=') print(decryptedText)The output is as follows. Please note keys and cipher text should be single continuous lines. I have chopped them up to fit them to the panel privateKey MIICWgIBAAKBgQC60O46dOSmIGWIN4yGqB8oC+tj7jbVYHmWnUB7wi5DwUFFnPzO 3tCP7vl4TBgTHqchGzbngvcmvQOLx9u1hzEerBdog1F/NX1ZQtxxR9C981m4nwp1 1PrejUVY1/FB8Xi/CHLTfsyCcEhHaG5qyarANqFxYm2LARaWnjpNJnslVwIDAQAB An90B0/u/qsEgxqMf/PHrqOrO7v3VPy5ijH6WbCzLOePLXhcxCRAWCMxQjuve5N4 TJGA/mYbZDwc00hgiMDs/gqC790E9+1Us/SByqWwIcbOL90mtq25HrRXGnraFEsh FS8cPVNyVPw/FTfYsQPRfLNgm8jj9VDV4IvBdJnRKa9BAkEAvTAhPbUjlS6yat6O k2LgHbtJBVdKzkzRytpG9BzRu594CHwDhFQqJyafWDEsv8gN9yEgD5Xv2ml0Fygo yPLXIQJBAPzKXhVlIkJ2kLXun3AO+UOiuLvJAX0i1+/KIrNLP01+zZAq1vgqIW8G ZjJGFPqPMPH1DIEztjYps25Yi8o1hXcCQD3pbUdrauycLokoshHrjppMOvZDfdiL bTBsREII2F4FLtvf8aqxfGX5jrXmydUtG9ze+0X+dFLOpc4aN1ajmeECQQCflLDo GLwT8P1RnJ4SA6HfzK2J/k5uTLy8p4JYZTxFE7N5O1IRv6bzi7qgKhW8RwDC/csz FQ4GCzU2h7dXo4npAkAIkhgS0G9wRXwXn6uhpYzb4DnP0kW12usc6U3sf6qMYbh6 5+6Bl3+PwL+SPkAj+huUchMKUwbArvBQAtv048l8 publicKey MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC60O46dOSmIGWIN4yGqB8oC+tj 7jbVYHmWnUB7wi5DwUFFnPzO3tCP7vl4TBgTHqchGzbngvcmvQOLx9u1hzEerBdo g1F/NX1ZQtxxR9C981m4nwp11PrejUVY1/FB8Xi/CHLTfsyCcEhHaG5qyarANqFx Ym2LARaWnjpNJnslVwIDAQAB Original Plain Text Message The quick brown fox jumps over the lazy dog cipher textext ar5mHqmYWARjaYabK4dNbAAuwUVhWChLvkJhjQALLv8Vk853Q6/Summy1//CACLQR zX276wkVxFx9mCwtPTycyuKYSuyaS6OgIqimPEjYoRVg7ean/c7bPaCoHC+xC4p8C qAIoBQGHSpIDJoZ2xyP3KcWNQFsRG0xicuBQ2Ps+0= decrypted text The quick brown fox jumps over the lazy dog Testing Direct Input Bb4WbQ865UIRQ0NN1lCUOwOQnkHsuHHeCG2SCItwYTKEFiNZiT5RNK0rifpAOnYKDI /KGutb39xcsusD/rzliDVTa92a1P1c26hdFCiiPXkXsSNxLkuJb7a0ycj+gGZRcoOf asKRrJoy9bZ7Uk69On2+A/R0l+c3xqlTAxFPn6w= Hello There
This program is adapted from the source code in https://github.com/liudonghua123/RSAPython/blob/master/RSA_Python.py
The original program search through all possible prime numbers and testing them, so it takes a long time to run. I have made the following modifications, to truncate run time, and also to reduce the complexity so that less experienced programmer can cope with it.
E
F
G
