LoginSignup
5
2

More than 1 year has passed since last update.

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

Posted at

概要

職場でこんなトラブルに遭遇しました。
「英数字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
5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2