PERMPOLYSP

Return to main page




# PERMPOLYSP, a block cipher (with authentication) with substitutions of bit
# groups with pseudo-randomly determined permutation polynomials mod 2**n and
# pseudo-random permutations of bytes.



# Prologue.

# The present software is a rewrite of my earlier PERMUTATIONPOLYNOMIALS. It is
# more complicated algorithmically but only to an extent that is fairly
# moderate, involving a number of additional operations to enhance the
# dynamics/variability of runtime encryption processing which are very easily to
# be understood and which IMHO are advantageous to have due to their relatively
# high benefit/cost ratios.

# The functions to be used to encrypt/decrypt byte arrays and text strings are
# encryptbytearray()/decryptctbytearray() and encryptptstringtoctbytearray()/
# decryptctbytearraytoptstring() respectively, with an initialization to be done
# with initpermpolysp(). A brief sketch of the scheme follows:

# The byte sequence resulting from user input is appended with pseudo-random
# bytes for authentication (integrity check). The whole is then processed as
# a sequence of blocks, with each block consisting of numsdiv subdivisions (each
# of size of n bits) to be processed sequentially in substitutions. On
# encryption the bytes of a block as a whole are first pseudo-randomly permuted.
# Then each subdivision, which can be considered as an n-bit integer p, is xored
# with a pseudo-random value randsum as well as with a chaining value sigma to
# result in a variable h. h is transformed by a permutation polynomial mod 2**n
# (dynamically chosen out of a pool of such polynomials) to result in a variable
# hh. hh is rotated by a pseudo-randomly determined number of bits to become
# an n-bit integer c, which is coverted to a byte sequence that is the result
# of the substitution operation. The chaining value sigma is then updated by
# xoring it with p and c (PCBC chaining). When all numsdiv subdivisions of
# a block have thus been processed, the resulting bytes of the block are as
# a whole subjected to a final pseudo-random permutation. There is a counter and
# a mask (to be applied to the value of sigma) to determine runtime events
# which dynamically influence the computations done in a block. For details,
# see encryptbytearray() and also comments in initpermpolysp().

# cpu time on a common PC for encryption/decryption procesing of 10000
# characters employing the same parameter values as in the illustrative example
# further below is of the order of 0.3 sec, which is evidently well acceptable
# for our targeted users, the common people.


# Version 1.0, released on 17.08.2016.


# 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
# PERMPOLYSP's development phase. Any remaining deficiencies of the software
# are however the sole responsibilty of the author.


# Constructive critiques, comments and suggestions of extensions and
# improvements are sincerely solicited. Address of the author:
#
# Email: mok-kong.shen@t-online.de
#
# Mail: Mok-Kong Shen, Postfach 340238, Munich 80099, Germany
#
# (Sender data optional, if no reply is required.)



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



import random



# The functions fullperiodcriteria(), evalpermpoly(), invpermpoly() are
# general functions required for computation with permutation polynomials
# mod 2**n.


# Modify user-supplied coefficients of the polynomial poly so as to satisfy the
# full-period criteria of a permutation polynomial mod 2**n. The degree of poly
# is required to be at least 2. m denotes the number of coefficients of the
# polynomial, i.e. degree of polynomial plus 1. For the criteria see ref.[1],
# p.283.
# [1] V. Anashin, A. Khrennikov, Applied Algebraic Dynamics, Berlin, 2009.
#     http://www.degruyter.com/view/supplement/9783110203011_Errata.pdf
# We use for simplification of coding a restricted form of the criteria, namely:
#    c_0 = c_1 = 1  mod 4
#    c_i = 0  mod 4    for all other i

def fullperiodcriteria(poly,m):
  global np1,tpwnm1
  if m<3:
    print("Error in fullperiodcriteria")
    exit(1)
  gg=(tpwnm1<<2)&tpwnm1
  permpoly=poly[:]
  for i in range(2):
    permpoly[i]=(permpoly[i]&gg)|1
  for i in range(2,m):
    permpoly[i]&=gg
  return(permpoly)


# Evaluate permpoly with argument x with Horner's method.

def evalpermpoly(permpoly,m,x):
  global np1,tpwnm1
  y=permpoly[m-1]
  for i in range(m-2,-1,-1):
    y=(y*x+permpoly[i])&tpwnm1
  return(y)


# Inverse of evalpermpoly(). First derivative of permpoly(x) = 1 mod 2. Hensel's
# lifting lemma leads to the iteration: x_(i+1)=x_i-g(x_i) mod 2**(i+1), with
# g(x)=permpoly(x)-y. See ref.[1], p.306. 

def invpermpoly(permpoly,m,y):
  global np1,tpwnm1
  if (((permpoly[0]-y)&tpwnm1)&1)==0:
    x=0
  else:
    x=1
  for i in range(2,np1,1):
    x=(x-(evalpermpoly(permpoly,m,x)-y))&tpwnm1
  return(x)


# Compute from sessionkeyextension an integer v and concatenate it to secretkey
# to form sessionkey. See Epilogue.

def formsessionkey():
  global secretkey,sessionkeyextension
  global sessionkey
  assert type(secretkey)==type(0) and type(sessionkeyextension)==type("a")
  by=bytearray(sessionkeyextension,'latin-1')
  lby=len(by)
  assert lby > 0
  v=0
  for i in range(lby):
    v<<=8
    v|=by[i]
  sessionkey=(secretkey<<(lby*8))|v
  return


# Initialization.

def initpermpolysp():
  global secretkey,sessionkeyextension
  global sessionkey
  global blocksize,numsdiv,bytesn,bytesblock
  global n
  global np1,tpwnm1
  global polynormdegree,numbcoeff,permpoly
  global sigma,sigmamaskbitn,sigmamask,numnewpoolelem
  global psbitn,poolsize,pool,seed
  global use,rotn,idx1,idx2,ptr1,ptr2
  global randsum
  global permidx1,permidx2
  global counter,countermax
  global numauthenblock,authenbytearray
# Plausibility checks (some may be appropriately modified, if necessary).
  assert blocksize>=128 and blocksize%8==0 and blocksize%numsdiv==0
  assert polynormdegree>=2
  assert 1<=sigmamaskbitn<=8 and numnewpoolelem>=1
  assert psbitn>=4
  assert numauthenblock>=1
# Initialize Python's built-in PRNG.
  formsessionkey()
  random.seed(sessionkey)
# Number of bytes in a block.
  bytesblock=blocksize//8
# n of permutation polynomials mod 2**n.
  n=blocksize//numsdiv
# Number of bytes in n.
  bytesn=n//8
# Two needed constants.
  np1=n+1
  tpwnm1=(2**n)-1
# Number of coefficients of the permutation polynomials.
  numbcoeff=polynormdegree+1
# Size of the pool of permutation polynomials.
  poolsize=2**psbitn 
  pool=[]
  seed=[]
  use=[]
  rotn=[]
# Two index lists of the pool which will be dynamically pseudo-randomly shuffled
# and whose elements will be selected with the pointers ptr1 and ptr2 for
# selecting a permutation polynomial in the pool for the purpose to compute
# an n-bit pseudo-random number and to perform a mapping of n-bit integers
# (a substitution) respectively.
  idx1=[i for i in range(poolsize)]
  idx2=[i for i in range(poolsize)]
  for i in range(poolsize):
# Generation of a pseudo-random polynomial mod 2**n having numbcoeff
# coefficients.
    poly=[random.getrandbits(n) for i in range(numbcoeff)]
# Modify it such that the full period criteria are satisfied.
    permpoly=fullperiodcriteria(poly,numbcoeff)
# Add it to the pool.
    pool.append(permpoly)
# The initial seed for employing the permutation polynomial as a PRNG.
    seed.append(random.getrandbits(n))
# The pseudo-random value use specifies the usage of the permutation polynomial
# to do substitution, i.e. either in the normal or in the inverse manner. cf.
# evalpoly() further below.
    use.append(random.getrandbits(1))
# Number of bits to do rotation of the bits of the pseudo-random number computed
# using the permutation polynomial as a PRNG. cf. encryptbytearray().
    rotn.append(random.randint(0,n-1))
# Initialize pointers to idx1 and idx2.
  ptr1=ptr2=0
# Initialize sigma, the chaining value for the n-bit subdivisions of a block.
# (The chaining value propagates across block boundaries.)
  sigma=random.getrandbits(n)
# A mask to be applied to sigma to determine a dynamic event to renew a
# user-specified number (numnewpoolelem) of entries in the pool of permutation
# polynomials together with corresponding entries of the lists seed, use and
# rotn. Cf. encryptbytearray().
  sigmamask=(2**sigmamaskbitn)-1
# Initialize randsum, permidx1, permidx2 and counter. 
  randsum=0
  permidx1=[i for i in range(bytesblock)]
  permidx2=[i for i in range(bytesblock)]
  counter=0
# The byte sequence to be appended to that of plaintext in encryption processing
# for purposes of integrity check.
  authenbytearray=bytearray([random.getrandbits(8)
                            for i in range(numauthenblock*bytesblock)])
  return


# Substitution mapping via evaluation of a permutation polynomial pool[index]
# in the pool of polynomials, depending on the value of use[index]. Since one is
# in principle free to use either evalpermpoly() or invpermpoly() in encrpytion
# processing (but certainly it is required to do the other way round in
# decryption procesing), employing a psuedo-random value use[index] to do the
# decision contributes to the uncertainty facing the adversary.
#  
# use[index)=0: Employ evalpermpoly().
# use[index]=1: Employ invpermpoly().

def evalpoly(index,u):
  global polynormdedree,numbcoeff,permpoly
  global psbitn,poolsize,pool,seed
  global use,rotn,idx1,idx2,ptr1,ptr2
  if use[index]==0:
    v=evalpermpoly(pool[index],numbcoeff,u)
  else:
    v=invpermpoly(pool[index],numbcoeff,u)
  return(v)


# The inverse of evalpoly().

def invpoly(index,u):
  global polynormdedree,numbcoeff,permpoly
  global psbitn,poolsize,pool,seed
  global use,rotn,idx1,idx2,ptr1,ptr2
  if use[index]==0:
    v=invpermpoly(pool[index],numbcoeff,u)
  else:
    v=evalpermpoly(pool[index],numbcoeff,u)
  return(v)


# Encryption of ptbytearray to ctbytearray. Cf. sketch given in Prologue.

def encryptbytearray(ptbytearray):
  global blocksize,numsdiv,bytesn,bytesblock
  global np1,tpwnm1
  global polynormdedree,numbcoeff,permpoly
  global sigma,sigmamaskbitn,sigmamask,numnewpoolelem
  global psbitn,poolsize,pool,seed
  global use,rotn,idx1,idx2,ptr1,ptr2
  global randsum
  global permidx1,permidx2
  global counter,countermax
  global numauthenblock,authenbytearray
  assert len(ptbytearray)%bytesblock==0  
# Append authenbytearray to ptbytearray for purposes of integrity check.
  ptbytearray=ptbytearray+authenbytearray
  lptbytearray=len(ptbytearray)
  numblocks=lptbytearray//bytesblock
# Create am empty byte array.
  ctbytearray=bytearray(0)
  for blocki in range(numblocks):
# Obtain new indices for pre- and post-permutations of bytes of the block.
    random.shuffle(permidx1)
    random.shuffle(permidx2)
# Get the block of plaintext bytes.
    ptby=ptbytearray[blocki*bytesblock:(blocki+1)*bytesblock]
# Do a (pre-)permutation of the bytes with permidx1.
    temp=ptby[:]
    for i in range(bytesblock):
      ptby[i]=temp[permidx1[i]]
# Create an empty byte array.
    ctby=bytearray(0)
# Perform substitutions.
# For each of the numsdiv subdivisions of the block (i.e. corresponding to n
# bits in length, n being a parameter of the permutation polynomials):
    for gi in range(numsdiv):
# Use index1 (value of the index list idx1 at the pointer position ptr1) to
# select a permutation polynomial from the pool to compute a pseudo-random
# number of n bits and add it to randsum mod 2**n.
      index1=idx1[ptr1]
      seed[index1]=evalpoly(index1,seed[index1])
      randsum=(randsum+seed[index1])&tpwnm1
# Convert the group of bytes of the subdivision to an integer p.
      p=int.from_bytes(ptby[gi*bytesn:(gi+1)*bytesn],byteorder='big')
# xor p with randsum (a pseudo-random value) and sigma (a chaining value).
      h=p^randsum^sigma
# Apply evalpoly(), i.e. do a mapping (substitution) with the permutation
# polynomial in the pool indexed by index2 (value of the index list idx2 at the
# pointer position ptr2) and in accordance to use[index2].
      index2=idx2[ptr2]
      hh=evalpoly(index2,h)
# Rotate the bits of hh by rotn[index2], resulting in an integer c.
      c=((hh << rotn[index2])&tpwnm1)|(hh >> (n-rotn[index2]))
# Convert c to a byte array bys.
      bys=bytearray(c.to_bytes(bytesn,signed=False,byteorder='big'))
# Collect bys (resulting bytes of the current subsubdivision) into the byte
# array ctby.
      ctby+=bys
# Update the n-bit chaining value sigma via xoring it with p anc c (PCBC
# chaining).
      sigma^=p^c
# Update the pointers ptr1 and ptr2.
      ptr1=(ptr1+1)%poolsize                                
      ptr2=(ptr2+1)%poolsize
# Obtain new index lists idx1 and idx2 when counter reaches countermax.
      counter+=1
      if counter >= countermax:
        random.shuffle(idx1)
        random.shuffle(idx2)
        counter=0
# If the last sigmamaskbitn bits of sigma are all 0's (the probability of this
# occuring is approximately 1/(2**sigmamaskbitn)):
      if sigma&sigmamask == 0:
# Do numnewpoolelem attempts to update at pseudo-randoly chosen positions idx
# the entries of pool, seed, use and rotn. cf. initperpolysp().
        for i in range(numnewpoolelem):
          idx=random.getrandbits(psbitn)
          poly=[random.getrandbits(n) for i in range(numbcoeff)]
          permpoly=fullperiodcriteria(poly,numbcoeff)
          pool[idx]=permpoly
          seed[idx]=random.getrandbits(n)
          use[idx]=random.getrandbits(1)
          rotn[idx]=random.randint(0,n-1)
# Do a post-permutation on the block of bytes ctby obtained.
    temp=ctby[:]
    for i in range(bytesblock):
      ctby[i]=temp[permidx2[i]]
# Collect ctby (resulting bytes of the current block) into ctbytearray.
    ctbytearray+=ctby
  return(ctbytearray)


# Decryption of ctbytearray, the inverse of encryptby().

def decryptctbytearray(ctbytearray):
  global blocksize,numsdiv,bytesn,bytesblock
  global np1,tpwnm1
  global polynormdedree,numbcoeff,permpoly
  global sigma,sigmamaskbitn,sigmamask,numnewpoolelem
  global psbitn,poolsize,pool,seed
  global use,rotn,idx1,idx2,ptr1,ptr2
  global randsum
  global permidx1,permidx2
  global counter,countermax
  global numauthenblock,authenbytearray
  lctbytearray=len(ctbytearray)
  assert lctbytearray%bytesblock==0
  numblocks=lctbytearray//bytesblock
  ptbytearray=bytearray(0)
  for blocki in range(numblocks):
    random.shuffle(permidx1)
    random.shuffle(permidx2)
    ctby=ctbytearray[blocki*bytesblock:(blocki+1)*bytesblock]
    temp=ctby[:]
    for i in range(bytesblock):
      ctby[permidx2[i]]=temp[i]
    ptby=bytearray(0) 
    for gi in range(numsdiv): 
      index1=idx1[ptr1]
      seed[index1]=evalpoly(index1,seed[index1])
      randsum=(randsum+seed[index1])&tpwnm1                                                       
      c=int.from_bytes(ctby[gi*bytesn:(gi+1)*bytesn],byteorder='big')
      index2=idx2[ptr2]
      hh=((c<<(n-rotn[index2]))&tpwnm1)|(c>>rotn[index2])
      h=invpoly(index2,hh)
      p=h^randsum^sigma
      bys=bytearray(p.to_bytes(bytesn,signed=False,byteorder='big'))
      ptby+=bys                                  
      sigma^=p^c
      ptr1=(ptr1+1)%poolsize                              
      ptr2=(ptr2+1)%poolsize
      counter+=1
      if counter >= countermax:
        random.shuffle(idx1)
        random.shuffle(idx2)
        counter=0
      if sigma&sigmamask == 0:
        for i in range(numnewpoolelem):
          idx=random.getrandbits(psbitn)
          poly=[random.getrandbits(n) for i in range(numbcoeff)]
          permpoly=fullperiodcriteria(poly,numbcoeff)
          pool[idx]=permpoly
          seed[idx]=random.getrandbits(n)
          use[idx]=random.getrandbits(1)
          rotn[idx]=random.randint(0,n-1)
    temp=ptby[:]
    for i in range(bytesblock):
      ptby[permidx1[i]]=temp[i]  
    ptbytearray+=ptby
  lauthenbytearray=len(authenbytearray)
# Check that the last sequence of bytes of ptbytearry obtained is identical
# to authenbytearray employed by the sender (who uses the byte array computed in
# initpermpolysp()).
  assert ptbytearray[-lauthenbytearray:] == authenbytearray
# Discard authenbytearray, obtaining a decrypted byte array which should be
# the same as what the sender inputs into encryptbytearray().
  ptbytearray=ptbytearray[:-lauthenbytearray]
  return(ptbytearray)


# Transformation of a character string to a byte array having a total length
# that is a multiple of bytesblock. Filling is done, if needed, first with
# fillerchar and then with pseudo-random choices from alpha.
#
# plaintextstring: The input text string coded in latin-1. 
#
# fillerchar: A chosen special filler character to be used. This character is
#             assumed to have no occurrence in the given plaintext string
#             ptstring and is not alphabetical.

def ptstringtoptbytearray(ptstring,fillerchar):
  global blocksize,numdiv,bytesn,bytesblock
# alpha is used for filling.
  alpha="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
  assert fillerchar not in ptstring and fillerchar not in alpha
  lptstr=len(ptstring)
  q,r=divmod(lptstr,bytesblock)
  if r!=0:
# Saving and restoring the state of Python's built-in PRNG in order to avoid
# affecting the sequences of pseudo-random numbers that are used by the other
# functions.
    state=random.getstate()
    d=bytesblock-r
    ptstring+=fillerchar
    for i in range(0,d-1):
      ptstring+=random.choice(alpha)
    random.setstate(state)
  ptbytearray=bytearray(ptstring,'latin-1')
  return(ptbytearray)


# The inverse of plaintextstringtoptbytearray(). Correctness of fillerchar is
# assumed.

def ptbytearraytoptstring(ptbytearray,fillerchar):
  ordfc=ord(fillerchar)
  ptstring=""
  lptba=len(ptbytearray)
  for i in range(lptba):
    kk=ptbytearray[i]
    if kk==ordfc:
      break
    ptstring+=chr(kk)
  return(ptstring)


# Function to encrypt a plaintext string ptstring to a byte array ctbyarray.

def encryptptstringtoctbytearray(ptstring,fillerchar):
  ptbytearray=ptstringtoptbytearray(ptstring,fillerchar)
  ctbytearray=encryptbytearray(ptbytearray)
  return(ctbytearray)


# Function to decrypt a byte array ctbyarray to a plaintext string ptstring.

def decryptctbytearraytoptstring(ctbytearray,fillerchar):
  ptbytearray=decryptctbytearray(ctbytearray)
  ptstring=ptbytearraytoptstring(ptbytearray,fillerchar)
  return(ptstring)


# 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()


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



# 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. permpolysp.py and the
# examples 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 permpolysp.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.



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



# Illustrative example.


# The communication partners have to specify the same (identical) system
# parameter values, whose appropriate selection is users' own responsibility.
# We assume for the present illustrative example the following system
# parameter values:

# Parameters for forming a session-dependent seed for Python's built-in PRNG.
# secretkey is an integer in hexadecimal or decimal format, sessionkeyextension
# is a text string. See Epilogue.

secretkey=0x4a98717c70e997c2715e87c3fa1ef559

sessionkeyextension="STU 17.08.2016 TP582"


# Blocksize in bits, must be a multiple of 8.

blocksize=512

# Number of subdivisions of a block. blocksize%(numsdiv*8) must be 0. Each
# subdivision has thus a size in bits n = blocksize//numsdiv which is the size
# of the integers to be operated on by the permutation polynomials mod 2**n.

numsdiv=2

# Degree of the permutation polynomials.

polynormdegree=2

# Size of the pool of permutation polynomials is 2**psbitn.

psbitn=5

# Limit of the counter to determine a dynamic event to re-shuffle the index
# lists idx1 and idx2 in encryption processing. cf. encryptbytearray().

countermax=10

# Two parameter values used in encryptbytearray(). cf. also initpermpolysp().

sigmamaskbitn=4

numnewpoolelem=2

# Number of authentication blocks (for integrity check).

numauthenblock=1

# The filler character, see ptstringtoptbyarray().

fillerchar="&"


# Sender side specific processing.

plaintextstring=\
"Das hoechste Glueck ist immer das, welches unsere Maengel verbessert und "\
"unsere Fehler ausgleicht.  J. W. von Goethe"

initpermpolysp()

ctbytearray=encryptptstringtoctbytearray(plaintextstring,fillerchar)

writebinaryfile(ctbytearray,"xyz")


# Receiver side specific processing.

ctbytearray1=readbinaryfile("xyz")

initpermpolysp()

plaintextstring1=decryptctbytearraytoptstring(ctbytearray1,fillerchar)

print("Plaintext string received:")
print()
print(plaintextstring1)


# Check of correctness.

print()
print("Plaintext string of the sender is correctly obtained by the receiver:",\
      plaintextstring==plaintextstring1)



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



# Appendix.


# Users who are new to Python or who begin to use a new version of the Python
# software may like to know a bit about the statistical qualities of its
# built-in PRNG which is employed in PERMPOLYSP. The following code is intended
# to serve for that purpose. prnbitn is the number of bits of the PRNs to be
# generated and must be a multiple of 8. testseed is any arbitrary seed chosen
# for running the test. One may like to try a number of different prnbitn and
# testseed.

import math,random

# Maurer's Universal Test, see [6].
#
# [6] J-S. Coron, D. Naccache, An Accurate Evaluation of Maurer's Universal Test.
#     http://www.jscoron.fr/publications/universal.pdf


qq=2560
qqp1=qq+1
kk=256000
qqkkp1=qq+kk+1


def maurertest(bb):
  global qq,qqp1,kk,qqkkp1
  eftu=7.1836656
# y1 and y2 are for rho=0.01 and rho=0.001 respectively.
  y1=2.5758
  y2=3.2905
  t=[0 for i in range(256)]
  for i in range(1,qqp1,1):
    t[bb[i]]=i
  sum=0.0
  for i in range(qqp1,qqkkp1,1):
    sum+=math.log10(i-t[bb[i]])
    t[bb[i]]=i
  tu=(sum/kk)/math.log10(2.0)
  c=math.sqrt(0.3732189+(0.3730195*256)/kk)
  sigma=c*math.sqrt(3.2386622/kk)
  t11=eftu-y1*sigma
  t12=eftu+y1*sigma
  t21=eftu-y2*sigma
  t22=eftu+y2*sigma
  return(tu,t11,t12,t21,t22)


def maurertestresult(h,gg):
  global chnum
  global qq,qqp1,kk,qqkkp1
  if h*chnum < qqkkp1:
    print("Error in maurertestresult")
    exit(6)
  bb=[0 for k in range(h*chnum)]
  u=0
  k1=-1
  k2=chnum-1
  for i in range(h):
    g=gg[i]
    for k in range(k2,k1,-1):
      bb[u]=g&0xff
      g>>=8
      u+=1
    k1+=chnum
    k2+=chnum
  tu,t11,t12,t21,t22 = maurertest(bb)
  print("Maurer's Universal Test for L=8, rho=0.01 (Middle value is the "\
        "test statistic\nand should lie between the other two values): "\
        "%6.3f %6.3f %6.3f"%(t11,tu,t12))


def randtest(prnbitn,testseed):
  global chnum
  if prnbitn<8 or (prnbitn%8)!=0:
    print("Error randtext: Wrong prnbitn")
    exit(55555)
  chnum=prnbitn//8
  h=qqkkp1//chnum
  if h*chnum < qqkkp1:
    h+=1
  random.seed(testseed)
  tpwbitnm1=2**prnbitn-1
  print("PRNs generated with random.randint():")
  gg=[random.randint(0,tpwbitnm1) for i in range(h)]
  maurertestresult(h,gg)
  print("PRNs generated with random.getrandbits():")
  gg=[random.getrandbits(prnbitn) for i in range(h)]
  maurertestresult(h,gg)
  return


# Example of user chosen test parameter:

prnbitn=128

testseed="My arbitrarily chosen seed"

print()
print()
print("Test of Python's PRNG, parameters used:")
print("prnbitn:",prnbitn)
print("testseed:",testseed)
randtest(prnbitn,testseed)



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



# 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 (which could be manifold in practical situations).

# In the way personally preferred by the present author, the parameter
# "sessionkey" employed to seed Python's PRNG at initialization time consists of
# two components: "secretkey", which should have sufficient entropy, e.g.
# stemming from dice throws, and which could be used for a certain longer time
# period before being renewed, and "sessionkeyextension", which is dynamic, i.e.
# different for different sessions and which need not be secret (since it is its
# variability that is important in the present context), being composed of
# certain variable session-dependent data, e.g. date, time, subject, message
# serial number, etc. In case there is no systematic scheme of derivation of
# "sessionkeyextension" to be used by the receiver, it can be sent in the clear
# to him. In view of the high dynamics of our sessionkey and the comparatively 
# low volumes of communications of our targeted users, our use of Python's 
# built-in PRNG is obviously entirely secure in practice. For a utility to 
# convert dice throws to a hexadecimal sequence, see DICE.

# 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 contains some comments of general interest.



Return to main page