Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ECDF plot from a truncated MD5

In this link, it says that truncated MD5 is uniformly distributed. I wanted to check it using PySpark and I created 1,000,000 UUIDs in Python first as shown below. Then truncated the first three characters from MD5. But the plot I get is not similar to the cumulative distribution function of a uniform distribution. I tried with UUID1 and UUID4 and the results are similar. What is the right way of conforming the uniform distribution of truncated MD5?

import uuid
import numpy as np
import matplotlib.pyplot as plt
from statsmodels.distributions.empirical_distribution import ECDF
import pandas as pd
import pyspark.sql.functions as f
%matplotlib inline

### Generate 1,000,000 UUID1 

uuid1 = [str(uuid.uuid1()) for i in range(1000000)]  # make a UUID based on the host ID and current time
uuid1_df = pd.DataFrame({'uuid1':uuid1})
uuid1_spark_df =  spark.createDataFrame(uuid1_df)
uuid1_spark_df = uuid1_spark_df.withColumn('hash', f.md5(f.col('uuid1')))\
               .withColumn('truncated_hash3', f.substring(f.col('hash'), 1, 3))

count_by_truncated_hash3_uuid1 = uuid1_spark_df.groupBy('truncated_hash3').count()

uuid1_count_list = [row[1] for row in count_by_truncated_hash3_uuid1.collect()]
ecdf = ECDF(np.array(uuid1_count_list))
plt.figure(figsize = (14, 8))
plt.plot(ecdf.x,ecdf.y)
plt.show()

enter image description here

EDIT: I added the histogram. As you can see below, it looks more like normal distribution.

  plt.figure(figsize = (14, 8))
  plt.hist(uuid1_count_list)
  plt.title('Histogram of counts in each truncated hash')
  plt.show()

enter image description here

like image 576
Fisseha Berhane Avatar asked Oct 22 '18 20:10

Fisseha Berhane


2 Answers

Here is a quick-and-dirty way to demonstrate this:

import hashlib
import matplotlib.pyplot as plt
import numpy as np
import random

def random_string(n):
    """Returns a uniformly distributed random string of length n."""
    return ''.join(chr(random.randint(0, 255)) for _ in range(n))

# Generate 100K random strings
data = [random_string(10) for _ in range(100000)]
# Compute MD5 hashes
md5s = [hashlib.md5(d.encode()).digest() for d in data]
# Truncate each MD5 to the first three characters and convert to int
truncated_md5s = [md5[0] * 0x10000 + md5[1] * 0x100 + md5[2] for md5 in md5s]

# (Rather crudely) compute and plot the ECDF    
hist = np.histogram(truncated_md5s, bins=1000)
plt.plot(hist[1], np.cumsum([0] + list(hist[0])))

ECDF

like image 122
NPE Avatar answered Oct 26 '22 10:10

NPE


The issue with my analysis above was that I was plotting the histogram of the count of truncated hash. The correct way should be to convert the truncated hash from hex to decimal and see the distribution of the decimals.

import uuid
import numpy as np
import matplotlib.pyplot as plt
from statsmodels.distributions.empirical_distribution import ECDF
import pandas as pd
import pyspark.sql.functions as f
from pyspark.sql.types import IntegerType
%matplotlib inline

### Generate 1,000,000 UUID1 

uuid1 = [str(uuid.uuid1()) for i in range(1000000)]  
uuid1_df = pd.DataFrame({'uuid1':uuid1})
uuid1_spark_df =  spark.createDataFrame(uuid1_df)
uuid1_spark_df = uuid1_spark_df.withColumn('hash', f.md5(f.col('uuid1')))\
           .withColumn('truncated_hash3', f.substring(f.col('hash'), 1, 3))\
           .withColumn('truncated_hash3_base10', f.conv('truncated_hash3', 16, 10).cast(IntegerType()))


truncated_hash3_base10_list = [row[0] for row in 
uuid1_spark_df.select('truncated_hash3_base10').collect()]
pd_df = uuid1_spark_df.select('truncated_hash3_base10').toPandas()
ecdf = ECDF(truncated_hash3_base10_list)
plt.figure(figsize = (8, 6))
plt.plot(ecdf.x,ecdf.y)
plt.show()

plt.figure(figsize = (8, 6))
plt.hist(truncated_hash3_base10_list)
plt.show()

enter image description here

like image 24
Fisseha Berhane Avatar answered Oct 26 '22 11:10

Fisseha Berhane