AES/Python

Return to main page




# AES/Python, an implementation of AES in Python with examples of encryption
# with PCBC (block-chaining, with authentication) and counter mode (without
# authentication).


# Prologue.


# A sketch of the main components of the software follows:

# The AES algorithm consists of keyexpansion() for keyscheduling and cipher()
# and invcipher() for encryption and decryption respectively. These invoke in
# their turn functions listed before them below with names the same as in
# FIPS-197 and with minimal deviations of syntax dictated by certain special
# properties of Python. After initialization with aesinit(), which invokes the
# keyscheduling, the function aes() is the function to be employed by the user
# to transform in ECB mode a 16-byte block sequence representing plaintext to
# a 16-byte block sequence representing ciphertext, or vice versa.

# The function checkencryptionwithfips197() enables the user to do a check in
# order to know that the present software works correctly for the examples that
# are given in Appendix C of FIPS-197.

# The function countermode() generates with a given initial counter value an
# arbitrarily long pseudo-random byte sequence needed by the user with AES in
# counter mode. Based on it the function countermodeencrypt() and
# countermodedecrypt() perform stream encryption processing of a user-given
# plaintext string (without authentication (integrity check)).

# The functions pcbcencprocessing() and pcbcdecprocessing() encrypts/decrypts
# an arbitrarily long 16-byte block sequence according to PCBC (block-chaining).
# PCBC is to my knowledge yet rather unknown but was more than a decade ago an
# idea of my own which I posted to an Internet group. It works analogously to
# CBC with the difference that, instead of employing the ciphertext of the
# immediately previous block to xor with the plaintext of the current block,
# one employs a block named sigma for doing chaining. sigma is initialized via
# encrypting a user-given initialization vector. On processing a plaintext
# block, sigma is xor-ed with it to form the input block of aes(). sigma is
# then updated by xoring it with the result of computation of a suitably chosen
# non-linear function f() of the plaintext and ciphertext of the current block.
# Thus at any time point sigma has in a sense summed up all the previously
# processed plaintext blocks and ciphertext blocks and consequently has the
# property of very strong error propagation such that its last value obtained
# (after processing all blocks of the plaintext of the user) can be used as a
# superior authentication (integrity check) on decryption processing. In other
# words, PCBC is a one-pass encryption processing with integrity check. The
# functions aespcbcencrypt() and aespcbcdecrypt() operate on user-given
# character strings instead of byte sequences and are based upon
# pcbcencprocessing() and pcbcdecprocessing().

# Purposes of functions having names identical to those in FIPS-197 are not
# given as comments at the head of the functions.

# On a common PC, measurements showed that less then 2 sec. is required to
# encrypt/decrypt 10000 bytes.


# Version 1.2, released on 16.05.2015.

# Update notes:

# Version 1.0: released on 01.05.2015.

# Version 1.1: 05.05.2015: Add countermodeencrypt() and countermodedecrypt().

# Version 1.2: 16.05.2015: Modification of pcbcencprocessing() and
# pcbcdecprocessing().


# Code lines of documents with the same version number are always identical.
# There may be interim modifications of comment lines. 


# This software may be freely used:

# 1. for all personal purposes unconditionally and

# 2. for all other purposes under the condition that its name, version number 
#    and authorship are explicitly mentioned and that the author is informed of
#    all eventual code modifications done.


# A list of present author's software that are currently directly maintained by
# himself is available at http://mok-kong-shen.de. Users are advised to
# download such software from that home page only.


# The author is indebted to TPS for review and suggestions throughout
# AES/Python's development phase. Any remaining deficiencies of the software
# are however the sole responsibilty of the author.

# Constructive critiques and comments are sincerely solicited. 

# Email address of the author: mok-kong.shen@t-online.de



################################################################################



import copy


sbox=bytearray([
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 
])


invsbox=bytearray([0 for i in range(256)])

for i in range(256):
  invsbox[sbox[i]]=i


def subbytes(state):
  for i in range(4):
    for j in range(4):
      state[i][j]=sbox[state[i][j]]
  return


def invsubbytes(state):
  for i in range(4):
    for j in range(4):
      state[i][j]=sbox.index(state[i][j])


def shiftrows(state):
  for i in range(1,4):
    state[i]=state[i][i:]+state[i][:i]
  return


def invshiftrows(state):
  for i in range(1,4):
    state[i]=state[i][-i:]+state[i][:-i]


# Multication in GF(2**8). (Google with "GF AES" for logic of this function.)
                                           
def gfm(a,b):
  if (a < b):
    t=b
    b=a
    a=t
  r=0
  suc=1
  while suc:
    if b&0x01: 
      r^=a
    t=a&0x80
    a=(a<<1)&0xff
    if t:
      a^=0x1b
    b>>=1
    suc=b
  return(r)


def mixcolumns(state):
  s=copy.deepcopy(state)
  for c in range(4):
    state[0][c]=gfm(0x02,s[0][c])^gfm(0x03,s[1][c])^s[2][c]^s[3][c]
    state[1][c]=s[0][c]^gfm(0x02,s[1][c])^gfm(0x03,s[2][c])^s[3][c]
    state[2][c]=s[0][c]^s[1][c]^gfm(0x02,s[2][c])^gfm(0x03,s[3][c])
    state[3][c]=gfm(0x03,s[0][c])^s[1][c]^s[2][c]^gfm(0x02,s[3][c])
  return


def invmixcolumns(state):
  s=copy.deepcopy(state)
  for c in range(4):
    state[0][c]=\
      gfm(0x0e,s[0][c])^gfm(0x0b,s[1][c])^gfm(0x0d,s[2][c])^gfm(0x09,s[3][c])
    state[1][c]=\
      gfm(0x09,s[0][c])^gfm(0x0e,s[1][c])^gfm(0x0b,s[2][c])^gfm(0x0d,s[3][c])
    state[2][c]=\
      gfm(0x0d,s[0][c])^gfm(0x09,s[1][c])^gfm(0x0e,s[2][c])^gfm(0x0b,s[3][c])
    state[3][c]=\
      gfm(0x0b,s[0][c])^gfm(0x0d,s[1][c])^gfm(0x09,s[2][c])^gfm(0x0e,s[3][c])
  return


def addroundkey(state,ww):
  for c in range(4):
    wc=ww[c]
    for i in range(3,-1,-1):
      state[i][c]^=wc&0xff
      wc>>=8
  return


def subword(u):
  shfs=[24,16,8,0]
  v=0
  for i in range(4):
    t=(u>>shfs[i])&0xff
    v<<=8
    v|=sbox[t]
  return(v)


def rotword(u):
  v=(u>>24)|((u<<8)&0xffffffff)
  return(v)


# We prefer to compute rcon during processing (i.e. not stored as an array of
# constants).

def rcon(i):
  assert i > 0
  u=0x01
  for j in range(i-1):
    u=gfm(u,0x02)
  v=u<<24  
  return(v)


# Convert a word to 4 bytes

def wordtobytes(word):
  by=bytearray(4)
  for i in range(3,-1,-1):
    by[i]=word&0xff
    word>>=8
  return(by)


# The reverse of wordtobytes().

def bytestoword(by):
  u=0
  for i in range(4):
    u<<=8
    u|=by[i]
  return(u)


def keyexpansion():
  global nb,nk,nr,keysize,keyhexstring,key,w
  i=0
  w=[0 for i in range(nb*(nr+1))]
  while i < nk:
    w[i]=bytestoword([key[4*i],key[4*i+1],key[4*i+2],key[4*i+3]])
    i+=1
  i=nk
  while i < nb*(nr+1):
    temp=w[i-1]
    if i%nk == 0:
      s=rotword(temp)
      ss=subword(s)
      rr=rcon(i//nk)
      tt=ss^rr
      temp=subword(rotword(temp))^rcon(i//nk)
    elif nk > 6 and i%nk ==4:
      temp=subword(temp)
    w[i]=w[i-nk]^temp
    i+=1
  return


def cipher(state):
  global nb,nk,nr,keysize,keyhexstring,key,w  
  addroundkey(state,w[0:nb])
  for round in range(1,nr):
    subbytes(state)
    shiftrows(state)
    mixcolumns(state)
    addroundkey(state,w[round*nb:(round+1)*nb])
  subbytes(state)
  shiftrows(state)
  addroundkey(state,w[nr*nb:(nr+1)*nb])
  return(state)


def invcipher(state):
  global nb,nk,nr,keysize,keyhexstring,key,w
  addroundkey(state,w[nr*nb:(nr+1)*nb])           
  for round in range(nr-1,0,-1):
    invshiftrows(state)
    invsubbytes(state)
    addroundkey(state,w[round*nb:(round+1)*nb])
    invmixcolumns(state)
  invshiftrows(state)
  invsubbytes(state)
  addroundkey(state,w[0:nb])
  return(state)


# Performs initialization for this software, in particular keyscheduling.

def aesinit():
  global nb,nk,nr,keysize,keyhexstring,key,w
  assert keysize in [128,192,256] and keysize==len(keyhexstring)*4
  hexs="0123456789abcdef"
  for s in keyhexstring:
    assert s in hexs
  nb=4
  if keysize==128:
    nk=4
    nr=10
  elif keysize==192:
    nk=6
    nr=12
  else:
    nk=8
    nr=14
# Convert user-given keyhexstring to key.
  key=hexstrtobyarray(keyhexstring)
# Invoke the keyscheduling function.
  keyexpansion()
  return


# The main function of AES (in ECB mode), working on a block of 4 bytes.
# kn=1: for encryption.
# kn=2: for decryption.

def aes(byarray,kn):
  assert 1 <= kn <= 2
  linby=len(byarray)
  assert linby == 16
  state=[[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]]
  k=0
  for j in range(4):
    for i in range(4):
      state[i][j]=byarray[k]
      k+=1  
  if kn==1:
    outstate=cipher(state)
  else:
    outstate=invcipher(state)
  outbyarray=bytearray(16)
  k=0
  for j in range(4):
    for i in range(4):
      outbyarray[k]=outstate[i][j]
      k+=1
  return(outbyarray)


# Transform a byte sequence to a hex string (without the prefix '0x').

def byarraytohexstr(byarray):
  hexstr=""
  for i in range(len(byarray)):
    u=hex(byarray[i])[2:]
    if len(u) < 2:
      u='0'+u
    hexstr+=u
  return(hexstr)


# The reverse of byarraytohexstr().

def hexstrtobyarray(hexstr):
  byarray=bytearray(0)
  lhexs=len(hexstr)
  assert (lhexs%2) == 0
  k=0
  while k < lhexs:
    v=int(hexstr[k:k+2],16)
    byarray+=bytearray([v])
    k+=2
  return(byarray)


# This function serves to check this software with examples in Appendix C of
# FIPS-197.

def checkencryptionwithfips197(keylength):
  global nb,nk,nr,keysize,keyhexstring,key,w
  sizes=[128,192,256]
  assert keylength in sizes
  kk=sizes.index(keylength)
  plaintext="00112233445566778899aabbccddeeff"
  keystr=[\
    "000102030405060708090a0b0c0d0e0f",\
    "000102030405060708090a0b0c0d0e0f1011121314151617",\
    "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"]
  ciphertext=[\
    "69c4e0d86a7b0430d8cdb78070b4c55a",\
    "dda97ca4864cdfe06eaf70a0ec0d7191",\
    "8ea2b7ca516745bfeafc49904b496089"]
  keysize=sizes[kk]
  keyhexstring=keystr[kk]
  aesinit()
  byarray1=hexstrtobyarray(plaintext)
  byarray2=aes(byarray1,1)
  hexstring1=byarraytohexstr(byarray2)
  byarray3=aes(byarray2,2)
  hexstring2=byarraytohexstr(byarray3)
  if hexstring1==ciphertext[kk] and hexstring2==plaintext:
    print("Keysize",keysize,
          " Check encryption/decryption with FIPS-197  ok")
  else:
    print("Keysize",keysize,
          " Check encryption/decryption with FIPS-197  failed *********")
  return


# Convert an 128-bit integer to 16 bytes.

def int128tobytes16(k):
  assert k < 2**128
  byarray=bytearray(16)
  for i in range(15,-1,-1):
    byarray[i]=k&0xff
    k>>=8
  return(byarray)


# The reverse of int238tobytes().

def bytes16toint128(byarray):
  lby=len(byarray)
  assert lby == 16
  k=0
  for i in range(16):
    k<<=8
    k|=byarray[i]
  return(k)


# Generation of psuedo-random byte sequence with AES in counter mode.

def countermode(initialcountervalue,numberofbytes):
  assert 0 <= initialcountervalue <= 2**128 and numberofbytes%16 == 0
  counter=initialcountervalue
  byarray=bytearray([])
  for i in range(numberofbytes//16):
    bydata=int128tobytes16(counter)
    byarray+=aes(bydata,1)
    counter+=1
  return(byarray)


# Encryption of plaintextstring via xor-ing it with byte sequences from AES in
# counter mode (stream encryption processing, without authentication (integrity
# check)).

def countermodeencrypt(initialcountervalue,plaintextstring):
  byarray=bytearray(plaintextstring,'latin-1')
  lby=len(byarray)
  q,r=divmod(lby,16)
  if r>0:
    q+=1   
  counterbyarray=countermode(initialcountervalue,q*16)
  cipherbyarray=bytearray(0)
  for i in range(lby):
    cipherbyarray+=bytes([byarray[i]^counterbyarray[i]])
  return(cipherbyarray)


# The reverse of countermodeencrypt().

def countermodedecrypt(initialcountervalue,cipherbyarray):
  lby=len(cipherbyarray)
  q,r=divmod(lby,16)
  if r>0:
    q+=1  
  counterbyarray=countermode(initialcountervalue,q*16)
  plaintextstring=""
  for i in range(lby):
    pb=cipherbyarray[i]^counterbyarray[i]
    plaintextstring+=chr(pb)
  return(plaintextstring)


# To update sigmaint in pcbcencprocessing(), xoring with pint^cint could be
# done. We prefer however to use the following non-linear function, since it has
# the nice property that for a constant y it is bijective in x and for a
# constant x it is bijective in y, indicating that it is optimal for mixing x
# and y in respect of entropy. User may replace it with his own favoured
# function. The value returned must be in interval [0, 2**128-1].

def nonlinfunc(x,y):
  r=(2*x*y+x+y)%(2**128)
  return(r)


# Encryption of byte sequences in 16-bytes blocks with PCBC (block-chaining).
# Block chaining is effected via sigmaby which is initialized with an encrypted
# block of ivstring. After processing the entire inbyarray, the last sigmaby
# is encrypted for the purpose of integrity check in pcbcdecprocessing(), as
# sigmaby is now in a sense the summation of all processed plaintext and
# ciphertext blocks. ivstring is a session-dependent (variable) character string
# of maximal length 16. If given too long, it will be curtailed, if too short,
# it will be filled with "#". One should attempt to choose it such that, with
# the employment of the same key of AES, all ivstring used are different. This
# could be done via systematically deriving ivstring from e.g. message number,
# date, time, subject, etc. ivstring need not be secret. It can be sent in the
# clear, in case there is no systematic scheme of derivation to be used by the
# receiver.

def pcbcencprocessing(inbyarray,ivstring):
  byarray=inbyarray[:]
  lby=len(byarray)
  assert lby%16 == 0
  numblock=lby//16 
  outbyarray=bytearray(0)
  liv=len(ivstring)
  if liv > 16:
    ivstring=ivstring[:16]
  else:
    ivstring+=(16-liv)*"#"
# Encrypt ivstring with AES to serve as initial value of sigmaby.
  sigmaby=bytearray(ivstring,'latin-1')
  sigmaby=aes(sigmaby,1)
  sigmaint=bytes16toint128(sigmaby)
  k=0
  for ii in range(numblock):
    k1=k+16
    p=byarray[k:k1]
    pint=bytes16toint128(p)
# xoring of plaintext block with sigmaby.
    for jj in range(16):
      p[jj]^=sigmaby[jj]
# Encrypt with AES to obtain ciphertext block.
    c=aes(p,1)
    outbyarray+=c
    cint=bytes16toint128(c)
# Update sigmaby.
    sigmaint^=nonlinfunc(pint,cint)
    sigmaby=int128tobytes16(sigmaint)
    k=k1
# Encryption of the last sigma to be checked in pcbcdecprocessing().
  p=sigmaby
  c=aes(p,1)
  outbyarray+=c
  return(outbyarray)


# The reverse of pcbcencprocessing(), authentication (integrity check is done).

def pcbcdecprocessing(inbyarray,ivstring):
  byarray=inbyarray[:]
  lby=len(byarray)
  assert lby%16 == 0
  numblock=lby//16 
  outbyarray=bytearray(0)
  liv=len(ivstring)
  if liv > 16:
    ivstring=ivstring[:16]
  else:
    ivstring+=(16-liv)*"#"
  sigmaby=bytearray(ivstring,'latin-1')  
  sigmaby=aes(sigmaby,1)
  sigmaint=bytes16toint128(sigmaby)
  k=0
  for ii in range(numblock-1):
    k1=k+16
    c=byarray[k:k1]
    p=aes(c,2)
    for jj in range(16):
      p[jj]^=sigmaby[jj]
    outbyarray+=p
    pint=bytes16toint128(p)
    cint=bytes16toint128(c)
    sigmaint^=nonlinfunc(pint,cint)
    sigmaby=int128tobytes16(sigmaint)
    k=k1
  k1=k+16
  c=byarray[k:k1]
  checksigmaby=aes(c,2)
# Check whether the last sigmaby obtained similarly to that of the sender is
# identical to that of the sender (which now has been decrypted checksigmaby).
  if sigmaby != checksigmaby:
    print("Authentication (integrity check) failed *********")
    exit(111)
  return(outbyarray)


# Encrypt with PCBC (block chaining) of plaintextstring, with ivstring as IV
# and fillerchar to fill the character string in case its length is not a
# multiple of 16. Fillerchar cannot be in the upper or lower case alphabet
# and cannot appear in plaintextstring.

def aespcbcencrypt(plaintextstring,ivstring,fillerchar):
  alpha="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
  assert fillerchar not in plaintextstring and fillerchar not in alpha
  lptstr=len(plaintextstring)
  r=lptstr%16
  if r!=0:
    d=16-r
    plaintextstring+=d*fillerchar
  byarray=bytearray(plaintextstring,'latin-1')
  cipherbyarray=pcbcencprocessing(byarray,ivstring)
  return(cipherbyarray)


# The reverse of aespcbcencrypt().

def aespcbcdecrypt(cipherbyarray,ivstring,fillerchar):
  byarray=pcbcdecprocessing(cipherbyarray,ivstring)
  lby=len(byarray)
  ordfc=ord(fillerchar)
  plaintextstring=""
  for i in range(lby):
    jj=byarray[i]
    if jj==ordfc:
      break
    plaintextstring+=chr(jj)
  return(plaintextstring)



################################################################################



# Installation of the software.

# Both communication partners have to download the same version 3x of Python
# from http://www.python.org. (Employing the same version of Python ensures
# against any potentially possible incompatibilities among different versions.)
# The present code can be stored in a file named e.g. aes.py and the
# example given further below run in Python's GUI IDLE. (File --> Open to find
# and open the file, then in the window showing the code Run --> Run Module to
# run it. One could also type aes.py in a DOS-window.) Modifications of
# the code in the code window, e.g. the plaintext string, can be done online and
# the code re-run.



################################################################################



# Useful utility functions for I/O.


# Read a byte sequence from a binary file.

def readbinaryfile(binaryfilename):
  fp=open(binaryfilename+".bin","rb")
  byarray=bytearray(fp.read())
  fp.close()
  return(byarray)


# Write a byte sequence to a binary file.

def writebinaryfile(byarray,binaryfilename):
  fp=open(binaryfilename+".bin","wb")
  fp.write(byarray)
  fp.close()
  

# Read a text file return a textstring.

def readtextfile(textfilename):
  fp=open(textfilename+".txt","r")
  textstring=fp.read()
  fp.close()
  return(textstring)


# Write a textstring to a text file.

def writetextfile(textstring,textfilename):
  fp=open(textfilename+".txt","w")
  fp.write(textstring)
  fp.close()
  return



################################################################################



# Check of correctness of our AES implementation with FIPS-197.


checkencryptionwithfips197(128)
print()

checkencryptionwithfips197(192)
print()

checkencryptionwithfips197(256)
print()
print()


# Illustrative Example 1. Encryption of a plaintext string with PCBC (block
# chaining with authentication (integrity check)).

print("Illustrative Example 1:")
print()

# The following parameters are assumed to be used by both the sender and the
# receiver. See aespcbcencrypt() and pcbcencprocessing().

# keysize must be one of [128, 196, 256].

keysize=128

# A string of hexadecimals of length keysize/4 specifying the secret key for
# AES encryption processing. The hexadecimals should be as random as possible.
# One practical way of their generation is throwing a bunch of dice. For a
# utility to convert dice throws to a hexadecimal sequence, see DICE.

keyhexstring="4a98717c70e997c2715e87c3fa1ef559"

# A session-dependent (variable) character string of length 16.

ivstring="HGS TR386 010515"

# A character employed for eventual filling of the user-given plaintext string
# to a length suitable for processing. It must be non-alphabetical and must
# not be in user-given plaintext string.

fillerchar="#"


# Sender side specific coding:

plaintext=\
  "The significant problems we face cannot be solved at the same level of "\
  "thinking we were at when we created them. -- Albert Einstein"

aesinit()

byarray=aespcbcencrypt(plaintext,ivstring,fillerchar)

writebinaryfile(byarray,"cipherbyarray")


# Receiver side specific coding:

byarray1=readbinaryfile("cipherbyarray")

aesinit()

plaintext1=aespcbcdecrypt(byarray1,ivstring,fillerchar)

print("Message received:")
print()
print(plaintext1)
print()


# Check of correctness of processing.

print("Message has been corrrectly transmitted:",plaintext1==plaintext)
print()
print()


# Illustrative Example 2. Encryption of a plaintext string via xor-ing it with
# bits from AES in counter mode (stream encryption processing, without
# authentication (integrity check)).

print("Illustrative Example 2:")
print()

# The following parameters are assumed to be used by both the sender and the
# receiver. See countermodeencrypt().


keysize=128

keyhexstring="4a98717c70e997c2715e87c3fa1ef559"

counterinitialvalue=3000


# Sender side specific coding.

aesinit()

plaintext=\
"Was sich ueberhaupt sagen laesst, laesst sich klar sagen; und wovon man "\
"nicht reden kann, darueber muss man schweigen. --  Ludwig Wittgenstein"

byarray=countermodeencrypt(counterinitialvalue,plaintext)

writebinaryfile(byarray,"cipherbyarray")


# Receiver side specific coding.

aesinit()

byarray1=readbinaryfile("cipherbyarray")

plaintext1=countermodedecrypt(counterinitialvalue,byarray1)

print("Message received:")
print()
print(plaintext1)
print()


# Check of correctness of processing.

print("Message has been corrrectly transmitted:",plaintext1==plaintext)



################################################################################



# Epilogue.


# We presume that the computer, on which this software is run, is free from
# malware infection via software and/or hardware means and that there are no
# emission security risks.

# The result of encryption of aespcbcencrypt() is binary and can be transported
# e.g. as an attached file in a normal email system. Thus in particular the
# end-to-end encryption of emails between two persons can be very simply
# realized without the use of any claimed(!) secure-email software that commonly
# involve S/MIME or PGP which, though being open-source packages, have in
# reality not been thoroughly scrutinized by independent experts due to their
# huge sizes, leading thus in the recent past to catastrophic consequences like
# those of the Heartbleed Bug. Note that, even if one's email system
# incorporates such claimed secure features, it evidently can do no harm for
# the user anyway to introduce an additional protection layer with the present
# software, which costs very little additional work, as our Illustrative
# Example clearly show. 

# Users who have also interest in public key cryptography may note that the
# present author has a Python code of RSA key generation and encryption in his
# software PROVABLEPRIME, which has also its epilogue some comments of general
# interest.

# Concerning key management, it could be advantageous to have a master secretkey
# that is kept secure for a long duration and is used only to generate secondary
# secretkeys for actual use in e.g. a month of a certain year (via encrypting a
# string of that time point or something equivalent to it) so as to take care of
# eventual needs of key revocation and not to have too much materials processed
# with the same secretkey. A hierarchy of secretkeys could also be employed,
# if needed.

# Use of AES in counter mode as a PRNG to generate arbitrary long bit sequences
# is commonly considered to be ok, provided that multiple uses with the same key
# employ counter values in intervals that don't overlap on another, which can be
# simply done e.g. via using initial counter values of the form g + n*h, where
# g is a chosen constant, n the message number and h an estimated upper bound of
# the number of bits (8 * message length) that a message has. One communication
# partner could, for example, choose g=0 and the other g=2**127. Evidently their
# counter intervals would never overlap in practice. If a master key and a
# common counter is kept by both partners, then AES in counter mode could also
# be used to supply session keys for use to encrpyt messages.

# The present author has earlier thought of a paradox concerning AES in counter
# mode. The keysize of AES is one of 128, 196 and 256 and the key has a maximum
# of keysize bits of entropy. If the output sequence of counter mode being used
# is m*keysize bits long, then the average entropy of a bit in that sequence is
# 1/m bit, which would be fairly smaller than 1 even for some moderate values
# of m. The seemingly paradoxical question is then how bits with such small
# entropies could nevertheless be used to do any secure stream encryption
# processing. I have in the past raised that issue to a few Internet groups,
# without however obtaining any response that resolves that paradox.

# Note that the pseudo-random byte sequences generated with AES in counter mode
# could, if desired, be post-processed or combined with other pseudo-random
# sequences.



Return to main page