見出し画像

【Python】6ケタの英数字が5ケタになってしまう問題をシミュレート

概要

職場でこんなトラブルに遭遇しました。
「英数字6ケタのパスワードが、5ケタで生成されており、エラーになった」

Pythonを使って、このエラーを再現してみました。

原因

「英数字6ケタ」で大量にランダム生成されたパスワード文字列のうち、ひとつだけ「数字のみ5ケタ」になっていました。
"012345"のように、先頭が0・残りが数字で生成されたため、Excelファイルでやりとりする際に先頭のゼロが省略されて"12345"になったようです。
(なんでmkpasswdのオプションで指定しないの…)


発生確率

この事象が起きる確率を計算してみます。

まず、ある1ケタの取りうる値は、[0-9, a-z]なので、10+26=36種類です。
つまり、0になる確率は、1/36。
一方、数字0-9になる確率は、10/36です。

今回の事象は、先頭1ケタが0・残りの5ケタが数字になる時なので、
(1/36) * (10/36) * (10/36) * (10/36) * (10/36) * (10/36)
= 0.0000459393658… となります。

パーセントにすると、0.0046%。
メガバンクの普通預金金利=0.001%の4倍強ですね。
(パーセントの意味合いが異なりますが)
分数にすると、1/21767.8になります。


再現

このパスワード生成をPythonで再現してみます。
事象が発生するまでパスワード生成を繰り返して、何回目で発生したかを記録したり、複数回発生させた平均値を出したりしてみます。


コード

import re
import random
from statistics import mean

#可変値
turns = 10
passwd_len = 6

#エラー率の計算(%)
err =  100 * (1 / 36) * (10 / 36) ** (passwd_len - 1)

'''
#ループを使って数字と小文字のリストを作成
#→固定値にしたのでコメントアウト
char_list = []

#数字
for j in range(10):
    char_list.append(str(j))

#小文字アルファベット
for i in range(97, 123):
    char_list.append(chr(i))
'''

#固定値。数字と小文字のリスト
char_list = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',]

#エラーになった時の試行回数を格納するリスト
challenge_count = []

print('文字列の長さ:' + str(passwd_len))
print(f'エラー率:{err:.8f}%')
print(str(round(100 / err, 1)) + '回に1回はエラーになる計算')
print('--------')

for turn in range(turns):
    i = 1
    while True:
        passwd = ''.join(random.choices(char_list, k=passwd_len))
        if passwd[0] == '0' and len(re.findall('[0-9]', passwd)) == passwd_len:
            print(passwd, ': ', i, '回目')
            challenge_count.append(i)
            break
        i += 1

print('--------')
print('試行回数 :',turn + 1)
print('最小値 :',min(challenge_count))
print('最大値 :',max(challenge_count))
print('平均値 :',mean(challenge_count))


実行結果

文字列の長さ:6
エラー率:0.00459394%
21767.8回に1回はエラーになる計算
--------
061109 :  63096 回目
085726 :  17000 回目
075952 :  9555 回目
078113 :  28085 回目
061468 :  17485 回目
042766 :  13637 回目
045780 :  9737 回目
023303 :  11550 回目
092385 :  19823 回目
039489 :  31942 回目
--------
試行回数 : 10
最小値 : 9555
最大値 : 63096
平均値 : 22191


文字列の長さを増やしてみる(6→8)

文字列の長さ:8
エラー率:0.00035447%
282111.0回に1回はエラーになる計算
--------
09152768 :  743009 回目
01711507 :  66256 回目
09156634 :  65775 回目
09544840 :  892213 回目
09402244 :  497038 回目
05226558 :  43195 回目
00636774 :  682084 回目
09395383 :  7369 回目
07805687 :  564570 回目
02889286 :  442124 回目
--------
試行回数 : 10
最小値 : 7369
最大値 : 892213
平均値 : 400363.3


試行回数を増やしてみる(10→1000)

※実行結果のprintを一時的にコメントアウト

for turn in range(turns):
    i = 1
    while True:
        passwd = ''.join(random.choices(char_list, k=passwd_len))
        if passwd[0] == '0' and len(re.findall('[0-9]', passwd)) == passwd_len:
            #print(passwd, ': ', i, '回目')
            challenge_count.append(i)
            break
        i += 1

平均値が理論上の値に近づいたことと、最小値・最大値のブレが大きくなったことが確認できます。最小値4ってすごいな。

文字列の長さ:6
エラー率:0.00459394%
21767.8回に1回はエラーになる計算
--------
--------
試行回数 : 1000
最小値 : 4
最大値 : 161107
平均値 : 21707.447


所感

  • パスワードの長さや試行回数を増やすと、処理時間がだいぶ長くなります。処理時間の視える化や、処理を軽くするための修正が必要。

  • 宝くじやガチャのシミュレータなんかがよく公開されていますが、今回のプログラムを応用して作れそうです。

  • 結果をグラフで表示して分析とかできればいいんですが、Pythonの知識以上に数学・統計の知識が求められそうです。

  • 0.0046%の確率でも、何度も繰り返せばエラーは必ず起きます。そうならないように、mkpasswdのオプションを指定しておきましょう。

mkpasswd -l 6 -d 2 -C 0 -s 0



#最近の学び #Python #パスワード #パスワード生成 #確率 #偶然 #ケタ

この記事が参加している募集

最近の学び

いつも図書館で本を借りているので、たまには本屋で新刊を買ってインプット・アウトプットします。