AES CTR implementation - python

I'm trying to implement CTR mode by myself (only decryption for now), using only AES built-in functions from pycrypto. It means that I'm not supposed to use mode=AES.MODE_CTR. However, I know that using AES.MODE_CTR would be more simple, but I'm doing this as a learning experience.
I'm not sure about how to use AES as a PRF, in order to use it in a CTR cryptography algorithm.
What am I doing wrong?
(non-parallalel version)
from Crypto.Cipher import AES
ciphers = ["69dda8455c7dd4254bf353b773304eec0ec7702330098ce7f7520d1cbbb20fc3" + \
"88d1b0adb5054dbd7370849dbf0b88d393f252e764f1f5f7ad97ef79d59ce29f5f51eeca32eabedd9afa9329", \
"770b80259ec33beb2561358a9f2dc617e46218c0a53cbeca695ae45faa8952aa" + \
"0e311bde9d4e01726d3184c34451"]
key = "36f18357be4dbd77f050515c73fcf9f2"
class IVCounter(object):
def __init__(self, value):
self.value = value
def increment(self):
# Add the counter value to IV
newIV = hex(int(self.value.encode('hex'), 16) + 1)
# Cut the negligible part of the string
self.value = newIV[2:len(newIV) - 1].decode('hex') # for not L strings remove $ - 1 $
return self.value
def __repr__(self):
self.increment()
return self.value
def string(self):
return self.value
class CTR():
def __init__(self, k):
self.key = k
def __strxor(self, a, b): # xor two strings of different lengths
if len(a) > len(b):
return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a[:len(b)], b)])
else:
return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b[:len(a)])])
def __split_len(self, seq, lenght):
return [seq[i:i+lenght] for i in range(0, len(seq), lenght)]
def __AESdecryptor(self, k, cipher):
decryptor = AES.new(k, AES.MODE_ECB)
return decryptor.decrypt(cipher)
def decrypt(self, cipher):
# Split the CT in blocks of 16 bytes
blocks = self.__split_len(cipher.decode('hex'), 16)
# Takes the initiator vector
self.IV = IVCounter(blocks[0])
blocks.remove(blocks[0])
# Message block
msg = []
# Decrypt
for b in blocks:
aes = self.__AESdecryptor(self.key.decode('hex'), self.IV.string())
msg.append(self.__strxor(b, aes))
self.IV.increment()
return ''.join(msg)
def main():
decryptor = CTR(key)
for c in ciphers:
print 'msg = ' + decryptor.decrypt(c)
if __name__ == '__main__':
main()
This code was supposed to do the same that the code below, but it is not decoding as it should be.
import Crypto.Util.Counter
ctr_e = Crypto.Util.Counter.new(128, initial_value=long(IV.encode('hex'), 16))
decryptor = AES.new(key.decode('hex'), AES.MODE_CTR, counter=ctr_e)
print decryptor.decrypt(''.join(blocks))

# Decrypt
for b in blocks:
aes = self.__AESdecryptor(self.IV.string(), self.key.decode('hex'))
msg.append(self.__strxor(b, aes))
self.IV.increment()
return ''.join(msg)
AES CTR mode uses AES's forward transformation for both encryption and decryption. That is, in both cases, encrypt the counter and then perform the XOR. When I say the 'forward transformation', I mean you always perform AES_Encrypt(counter) (and never perform AES_Decrypt(counter)).
You perform the XOR on both the plain text and the cipher text, irregardless of whether you are encrypting or decrypting. text XOR encrypt(counter) is the encryption or decryption operation. That's a stream cipher.
self.IV.string() is not the AES key. Its the value that is encrypted under the key. Once encrypted, it is XOR'd with the {plain|cipher} text.

I've finally got this code working well, and the mistake was very simple. I shouldn't have used decrypt AES function, I should have used encrypt AES function (as noloader had said, and I'd not understood him very well at the first time). Thanks for everybody who helped and here is the fixed code:
from Crypto.Cipher import AES
ciphers = ["69dda8455c7dd4254bf353b773304eec0ec7702330098ce7f7520d1cbbb20fc3" + \
"88d1b0adb5054dbd7370849dbf0b88d393f252e764f1f5f7ad97ef79d59ce29f5f51eeca32eabedd9afa9329", \
"770b80259ec33beb2561358a9f2dc617e46218c0a53cbeca695ae45faa8952aa" + \
"0e311bde9d4e01726d3184c34451"]
key = "36f18357be4dbd77f050515c73fcf9f2"
class IVCounter(object):
def __init__(self, value):
self.value = value
def increment(self):
# Add the counter value to IV
newIV = hex(int(self.value.encode('hex'), 16) + 1)
# Cut the negligible part of the string
self.value = newIV[2:len(newIV) - 1].decode('hex') # for not L strings remove $ - 1 $
return self.value
def __repr__(self):
self.increment()
return self.value
def string(self):
return self.value
class CTR():
def __init__(self, k):
self.key = k.decode('hex')
def __strxor(self, a, b): # xor two strings of different lengths
if len(a) > len(b):
return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a[:len(b)], b)])
else:
return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b[:len(a)])])
def __split_len(self, seq, lenght):
return [seq[i:i+lenght] for i in range(0, len(seq), lenght)]
def __AESencryptor(self, cipher):
encryptor = AES.new(self.key, AES.MODE_ECB)
return encryptor.encrypt(cipher)
def decrypt(self, cipher):
# Split the CT into blocks of 16 bytes
blocks = self.__split_len(cipher.decode('hex'), 16)
# Takes the initiator vector
self.IV = IVCounter(blocks[0])
blocks.remove(blocks[0])
# Message block
msg = []
# Decrypt
for b in blocks:
aes = self.__AESencryptor(self.IV.string())
msg.append(self.__strxor(b, aes))
self.IV.increment()
return ''.join(msg)
def main():
decryptor = CTR(key)
for c in ciphers:
print 'msg = ' + decryptor.decrypt(c)
if __name__ == '__main__':
main()

Related

AES/ECB/PKCS5 padding issue in Python

Here is my code for encryption
BS = cryptoAES.block_size
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
unpad = lambda s : s[:-ord(s[len(s)-1:])]
class AESCipher:
def __init__(self):
self.key = b'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
self.iv = b'123edfr4##90&65$'
def encrypt(self, data):
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
_pad = pad(data).encode('utf-8')
response = base64.b64encode(self.iv + cipher.encrypt(_pad)).decode('utf-8')
print('+++++++++++',response)
Key Size in Bits:256
Cipher Mode of Encryption:CBC
input : '164386'
after encryption output will be: MTIzZWRmcjRAIzkwJjY1JES7OMmdSrtGJX+An1UoVNY=
When I try to decrypt using online tool(tool) output will be öÁéáfsÊaׇÒH164386.
required output is 164386.
Full code is here:
import base64
from Crypto.Cipher import AES
from Crypto.Cipher import AES as cryptoAES
from base64 import b64decode
from base64 import b64encode
class Globals:
def __init__(self):
self.KEY = b'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
self.IV = b'123edfr4##90&65$'
BS = cryptoAES.block_size
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
unpad = lambda s : s[:-ord(s[len(s)-1:])]
class AESCipher:
def __init__(self):
self.key = b'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
self.iv = b'123edfr4##90&65$'
def encrypt(self, data):
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
_pad = pad(data).encode('utf-8')
response = base64.b64encode(self.iv + cipher.encrypt(_pad)).decode('utf-8')
print('+++++++++++',response)
def test():
app_id = '164386'
response = AESCipher().encrypt(app_id)
return response
output = test()

How to add another parameter for message in hmac?

i want to write a hmac (hash-based message authentication code) in python. So far i managed to write the basic hmac but i want to add another parameter in the message. For example, message=(mac_address || index_value). Can somebody show me how to do it? And how can i save the output in another list (e.g. digest_hmac_list)?
from hashlib import shake_256
from zlib import crc32, adler32
class HMAC:
def __init__(self, key, message, hash_h=shake_256):
""" key and message must be byte object """
self.i_key_pad = bytearray()
self.o_key_pad = bytearray()
self.key = key
self.message = message
self.blocksize = 64
self.hash_h = hash_h
self.init_flag = False
def init_pads(self):
""" creating inner padding and outer padding """
for i in range(self.blocksize):
self.i_key_pad.append(0x36 ^ self.key[i])
self.o_key_pad.append(0x5c ^ self.key[i])
def init_key(self):
""" key regeneration """
if len(self.key) > self.blocksize:
self.key = bytearray(shake_256(key).digest())
elif len(self.key) < self.blocksize:
i = len(self.key)
while i < self.blocksize:
self.key += b"\x00"
i += 1
def digest(self):
if self.hash_h == adler32 or self.hash_h == crc32:
return self.hash_h(bytes(self.o_key_pad)+str(self.hash_h(bytes(self.i_key_pad)+self.message)).encode())
""" returns a digest, byte object. """
""" check if init_flag is set """
if self.init_flag == False:
self.init_key()
self.init_pads()
""" hold init_flag for good. """
self.init_flag = True
return self.hash_h(bytes(self.o_key_pad)+self.hash_h(bytes(self.i_key_pad)+self.message).digest()).digest()
def hexdigest(self):
if self.hash_h == adler32 or self.hash_h == crc32:
return hex(self.hash_h(bytes(self.o_key_pad)+str(self.hash_h(bytes(self.i_key_pad)+self.message)).encode()))[2:]
""" returns a digest in hexadecimal. """
""" check if init_flag is set """
if self.init_flag == False:
""" init key and padding. """
self.init_key()
self.init_pads()
""" set init_flag for good. """
self.init_flag = True
I fixed some small issues in Your code and made it so you can hash 2 different (mac || index) with same key and save it in a self.digest_all_list. I commented out all these things in the code.
from hashlib import shake_256
from zlib import crc32, adler32
class HMAC:
def __init__(self, key, message, hash_h=shake_256):
""" key and message must be byte object """
self.i_key_pad = bytearray()
self.o_key_pad = bytearray()
self.key = key
self.message = message
self.blocksize = 64
self.hash_h = hash_h
self.init_flag = False
# This will contain all hashed messages
self.digest_hmac_list = []
def init_pads(self):
""" creating inner padding and outer padding """
for i in range(self.blocksize):
self.i_key_pad.append(0x36 ^ self.key[i])
self.o_key_pad.append(0x5c ^ self.key[i])
def init_key(self):
""" key regeneration """
if len(self.key) > self.blocksize:
self.key = bytearray(shake_256(self.key).digest(self.blocksize))
elif len(self.key) < self.blocksize:
i = len(self.key)
while i < self.blocksize:
self.key += b"\x00"
i += 1
def digest(self, message = None):
# If you want to Hash 2 different message with same key(so same class instance)
# pass message to digest and default to self.message
if message:
self.message = bytearray(message, encoding="ascii")
if self.hash_h == adler32 or self.hash_h == crc32:
return self.hash_h(bytes(self.o_key_pad)+str(self.hash_h(bytes(self.i_key_pad)+self.message)).encode())
""" returns a digest, byte object. """
""" check if init_flag is set """
if self.init_flag == False:
self.init_key()
self.init_pads()
""" hold init_flag for good. """
self.init_flag = True
# You Forget to specify the size of the Hash shake_256 allow for arbitrary output(Not like SHA-2)
# , I chosen 64 byte you can you chose whatever you want
self.digest_hmac_list.append(self.hash_h(bytes(self.o_key_pad) + self.hash_h(bytes(self.i_key_pad) + self.message).digest(self.blocksize)).digest(self.blocksize))
return self.digest_hmac_list[-1]
def hexdigest(self, message = None):
# If you want to Hash 2 different message with same key(so same class instance)
# pass message to digest and default to self.message
if message:
self.message = bytearray(message, encoding="ascii")
# Checking must be Done First So you can initialize all required parts then hash the message
""" check if init_flag is set """
if self.init_flag == False:
""" init key and padding. """
self.init_key()
self.init_pads()
""" set init_flag for good. """
self.init_flag = True
if self.hash_h == adler32 or self.hash_h == crc32:
self.digest_hmac_list.append(hex(self.hash_h(bytes(self.o_key_pad) + str(self.hash_h(bytes(self.i_key_pad) + self.message)).encode())[2:]))
return self.digest_hmac_list[-1]
""" returns a digest in hexadecimal. """
# NOTE: You are Not hashing anything if the default Hash function is shake_256, add
# code here to add hexHashing for default
# message is mac then post pended with Index if that what I understand
index = "0"
mac = "FF0A8CD1DAAB"
key = "This is key"
cl = HMAC(bytearray(key, encoding="ascii"), bytearray(mac + index, encoding="ascii"), shake_256)
print(cl.digest())
print("=="*10)
index = "1"
print(cl.digest(mac + index))
print("=="*10)
print(cl.digest_hmac_list)

Scrambling numbers

I am trying to program an algorithm that scrambles and "unscrambles" integer numbers.
I need two functions forward and backward
backward(number): return a "random" number between 0 and 9, the same input number always returns the same output
forward(number): return the input to backward that returns number
I managed to solve the problem like this:
from random import randint
class Scrambler:
def __init__(self):
self.mapping = [i for i in range(10)]
# scramble mapping
for i in range(1000):
r1 = randint(0, len(self.mapping) - 1)
r2 = randint(0, len(self.mapping) - 1)
temp = self.mapping[r1]
self.mapping[r1] = self.mapping[r2]
self.mapping[r2] = temp
def backward(self, num):
return self.mapping[num]
def forward(self, num):
return self.mapping.index(num)
if __name__ == '__main__':
s = Scrambler()
print(s.mapping)
for i in range(len(s.mapping)):
print(i, s.forward(i), s.backward(i), s.forward(s.backward(i)), s.backward(s.forward(i)))
Is there a way to do this without using the mapping list?
Can i calculate the return value of the functions forward and backward?
The "randomness" of the numbers does not need to be perfect.
I think your current solution is better than coming up with a function each time. It is a good solution.
Here is a generic solution for a generic key. You'd make your version using the Cipher.random_range method I've stuck on.
import random
class Cipher:
def __init__(self, key):
"""
key is a dict of unique values (i.e. bijection)
"""
if len(set(key.values())) != len(key):
raise ValueError('key values are not unique')
self._encoder = key.copy()
self._decoder = {v: k for k, v in key.items()}
#classmethod
def random_range(cls, max):
lst = list(range(max))
random.shuffle(lst)
return cls(dict(enumerate(lst)))
def encode(self, num):
return self._encoder[num]
def decode(self, num):
return self._decoder[num]

KEM/DEM using cryptosystem NTRU on Sage

First of all I must say my knowledge with using Sage math is really very limited, but I really want to improve and to be able to solve these problems I'm having. I have been asked to implement the following:
Use an Sage implementation of the cryptosystem NTRU and the library “cryptography” to build a KEM/DEM system with security of 128 bits, generated key of 128 bits and, on the DEM phase, use the cipher AES of 128 bits.
While trying to solve I came across an implementation of NTRU-Prime in sage and wanted to use it to solve this problem:
My Attemp:
p = 739; q = 9829; t = 204
Zx.<x> = ZZ[]; R.<xp> = Zx.quotient(x^p-x-1)
Fq = GF(q); Fqx.<xq> = Fq[]; Rq.<xqp> = Fqx.quotient(x^p-x-1)
F3 = GF(3); F3x.<x3> = F3[]; R3.<x3p> = F3x.quotient(x^p-x-1)
import itertools
def concat(lists):
return list(itertools.chain.from_iterable(lists))
def nicelift(u):
return lift(u + q//2) - q//2
def nicemod3(u): # r in {0,1,-1} with u-r in {...,-3,0,3,...}
return u - 3*round(u/3)
def int2str(u,bytes):
return ''.join([chr((u//256^i)%256) for i in range(bytes)])
def str2int(s):
return sum([ord(s[i])*256^i for i in range(len(s))])
def encodeZx(m): # assumes coefficients in range {-1,0,1,2}
m = [m[i]+1 for i in range(p)] + [0]*(-p % 4)
return ''.join([int2str(m[i]+m[i+1]*4+m[i+2]*16+m[i+3]*64,1) for i in range(0,len(m),4)])
def decodeZx(mstr):
m = [str2int(mstr[i:i+1]) for i in range(len(mstr))]
m = concat([[m[i]%4,(m[i]//4)%4,(m[i]//16)%4,m[i]//64] for i in range(len(m))])
return Zx([m[i]-1 for i in range(p)])
def encodeRq(h, nBits = 9856):
h = [lift(h[i]) for i in range(p)] + [0]*(-p % 3)
h = ''.join([int2str(h[i]+h[i+1]*10240+h[i+2]*10240^2,5) for i in range(0,len(h),3)])
return h[0:(nBits/8)]
def decodeRq(hstr):
h = [str2int(hstr[i:i+5]) for i in range(0,len(hstr),5)]
h = concat([[h[i]%10240,(h[i]//10240)%10240,h[i]//10240^2] for i in range(len(h))])
if max(h) >= q: raise Exception("pk out of range")
return Rq(h)
def encoderoundedRq(c,nBits):
c = [1638 + nicelift(c[i]/3) for i in range(p)] + [0]*(-p % 2)
c = ''.join([int2str(c[i]+c[i+1]*4096,3) for i in range(0,len(c),2)])
return c[0:1109]
def decoderoundedRq(cstr):
c = [str2int(cstr[i:i+3]) for i in range(0,len(cstr),3)]
c = concat([[c[i]%4096,c[i]//4096] for i in range(len(c))])
if max(c) > 3276: raise Exception("c out of range")
return 3*Rq([c[i]-1638 for i in range(p)])
def randomR(): # R element with 2t coeffs +-1
L = [2*randrange(2^31) for i in range(2*t)]
L += [4*randrange(2^30)+1 for i in range(p-2*t)]
L.sort()
L = [(L[i]%4)-1 for i in range(p)]
return Zx(L)
def keygen():
while True:
g = Zx([randrange(3)-1 for i in range(p)])
if R3(g).is_unit(): break
f = randomR()
h = Rq(g)/(3*Rq(f))
pk = encodeRq(h)
return pk,encodeZx(f) + encodeZx(R(lift(1/R3(g)))) + pk
import hashlib
def hash(s): h = hashlib.sha512(); h.update(s); return h.digest()
def encapsulate(pk):
h = decodeRq(pk)
r = randomR()
hr = h * Rq(r)
m = Zx([-nicemod3(nicelift(hr[i])) for i in range(p)])
c = Rq(m) + hr
fullkey = hash(encodeZx(r))
return fullkey[:32] + encoderoundedRq(c,128),fullkey[32:]
def decapsulate(cstr,sk):
f,ginv,h = decodeZx(sk[:185]),decodeZx(sk[185:370]),decodeRq(sk[370:])
confirm,c = cstr[:32],decoderoundedRq(cstr[32:])
f3mgr = Rq(3*f) * c
f3mgr = [nicelift(f3mgr[i]) for i in range(p)]
r = R3(ginv) * R3(f3mgr)
r = Zx([nicemod3(lift(r[i])) for i in range(p)])
hr = h * Rq(r)
m = Zx([-nicemod3(nicelift(hr[i])) for i in range(p)])
checkc = Rq(m) + hr
fullkey = hash(encodeZx(r))
if sum([r[i]==0 for i in range(p)]) != p-2*t: return False
if checkc != c: return False
if fullkey[:32] != confirm: return False
return fullkey[32:]
print("Exe 2")
print("")
pk,sk = keygen()
c,k = encapsulate(pk)
k == decapsulate(c,sk)
print("")
print("{:d} bytes in public key that is {:d} bits".format(len(pk),len(pk)*8))
print("{:d} bytes in secret key that is {:d} bits".format(len(sk),len(sk)*8))
print("{:d} bytes in ciphertext that is {:d} bits".format(len(c),len(c)*8))
print("{:d} bytes in shared secret that is {:d} bits".format(len(k),len(k)*8))
Now I know I can use this to get to the solution of the question mentioned above. I assume the key mentioned in the question is the private key because that is the one we generate (am I right?) so I know I have to edit this function:
def keygen():
while True:
g = Zx([randrange(3)-1 for i in range(p)])
if R3(g).is_unit(): break
f = randomR()
h = Rq(g)/(3*Rq(f))
pk = encodeRq(h) #Encode private key with 128 bits
return pk,encodeZx(f) + encodeZx(R(lift(1/R3(g)))) + pk
In order to do this I tried editing the function encodeRq used on this one to encode 128 bits only but that brought up lots of compiling errors that I just couldn't understand. But at least am I right to assume it's here where I have to set my key to be generated with 128 bits?
I believe the KEM mentioned in the question is handled with the function encapsulate and believe that I don't have to change anything there (am I right?)
The biggest problem is really the DEM phase wich I believe is being implemented on the function decapsulate (am I right?) but how should I change this to use AES? How do I do it on sage? any library I should know about?
I am a bit lost here and just want to know if my assumptions are correct and to be indicated on the right path. Thanks for any answer in advance.

Python MyHashTable class: search method with linear probing

I need help implementing a method for my "MyHashTable" class:
def search(self, search_key):
The method is supposed to use linear probing to handle collision resolution. If the search_key is in the hash table then the method returns the slot number of the slot containing that search_key. If the search_key is not in the hash table, the method returns -1
My class looks like this:
class MyHashTable:
def __init__(self, capacity):
self.capacity = capacity
self.slots = [None] * self.capacity
def __str__(self):
return str(self.slots )
def __len__(self):
count = 0
for i in self.slots:
if i != None:
count += 1
return count
def hash_function(self, key):
i = key % self.capacity
return i
def insert(self, key):
slot = self.hash_function(key)
orig = slot
while True:
if self.slots[slot] is None:
self.slots[slot] = key
return slot
if self.slots[slot] == key:
return -2
slot = (slot + 1) % self.capacity
if slot == orig:
return -1
def search(self, search_key):
Any help or tutorial links would be awesome.
Thanks
You are only using a single list to store all the values, if you wanted a hash table you might use a list of lists where each list was a bucket but if you just want to check if the element is in your hash table with your own code:
def search(self, search_key):
hsh = self.hash_function(search_key)
if self.slots[hsh] is None:
return -1
while hsh < self.capacity:
if self.slots[hsh] == search_key:
return hsh
hsh += 1
return -1
You also have to handle the case where you have multiple collisions so we need at worst to check every element in the hash table to find the correct value:
def search(self, search_key):
hsh = self.hash_function(search_key)
if self.slots[hsh] is None:
return -1
for i in range(self.capacity):
mod = (hsh + i) % self.capacity
if self.slots[mod] == search_key:
return mod
return -1
The first while loop will probe one value over at a time but if we have wrapped around the list from multiple collisions it would miss elements at the start so using range and mod = (hsh + i) % self.capacity makes sure we check all entries like the example below.
m = MyHashTable(5)
m.insert(13) # 13 % 5 = 3
m.insert(73) # 83 % 5 = 3
m.insert(93) # 93 & 5 = 3
print(m.search(13)) # 3
print(m.search(73)) # 4
print(m.search(93)) # 0
print(m.search(2)) # -1
You can make your len method O(1) by keeping track of when you add a unique value to your hash table, there is also a nice wiki page on Open_addressing parts of which you can adopt into your code and it will help you create a proper mapping of keys to values and resized your hash table when needed. If you want to store more than just numbers you need to use a different hash function, I just use hash but you can use whatever you like. Also using in when your hash table is full and the key does not exist will cause an infinite loop so you will need to handle that case:
class MyHashTable:
def __init__(self, capacity):
self.capacity = capacity
self.slots = [None] * self.capacity
self.count = 0
def __str__(self):
return str(self.slots)
def __contains__(self, item):
return self.search(item) != -1
def __len__(self):
return self.count
def hash_function(self, key):
return hash(key) % self.capacity
def find_slot(self, key):
slot = self.hash_function(key)
while self.slots[slot] is not None and self.slots[slot] != key:
slot = (slot + 1) % self.capacity
return slot
def insert(self, key):
slot = self.find_slot(key)
if self.slots[slot] != key:
self.slots[slot] = key
self.count += 1
def search(self, key):
i = self.find_slot(key)
if self.slots[i] is not None:
return i
return -1
Add a __contains__ will also allow you to use in to test for membership:
m = MyHashTable(5)
m.insert("foo")
m.insert(73)
m.insert(93)
m.insert(1)
print(m.search(73))
print(m.search(93))
print(m.search(1))
print(m.search("foo"))
m.insert(73)
print(m.slots)
print(len(m))
print("foo" in m)
print(5 in m)
Output:
3
4
1
0
['foo', 1, None, 73, 93]
4
True
False

Categories