# パスワードの正規表現について深掘る ## 今回深掘るパスワードの正規表現 パスワードの正規表現は以下のように表せる。 この正規表現にはどんな意味があるか? ``` \A(?=.*?[a-z])(?=.*?[A-Z])(?=.*?\d)(?=.*?[\W_])[!-~]+\z ``` ## パスワードの正規表現中に出てくる記号 この正規表現に出てくる記号を以下にまとめた。 | メタ文字 | 意味 | | ---- | ---- | | \A | 行頭を表す | |●(?=○) | 後ろに○がついている時だけ●でマッチ(●がないなら、直前の空文字にマッチ)| | . | 任意の1文字 | | * | 直前の文字が0文字以上 | | + | 直前の文字が1文字以上 | | .* | 任意の1文字が0文字以上 | | .*? | 任意の1文字が0文字以上(短いマッチ) | | [a-z] | aからzで1文字 | | [A-Z] | AからZで1文字 | | \d | 0から9の中で1文字 | | \W | アルファベット、アンダーバー、数字以外の文字 | | [○●] | ○または●のどちらか1文字| | [○●]+ | ○または●のどちらかの文字が1文字以上| | \z | 行末 |[!-~]|!からzの中で1文字([a-z]とやってることは同じ)| [!-~]は文字コードの!から\~の中で1文字を表す。 [![Image from Gyazo](https://i.gyazo.com/170fdd38e48655ccef15b1a554c1f755.png)](https://gyazo.com/170fdd38e48655ccef15b1a554c1f755) ## 検証 色々検証してみる。 ### b(?=[a-z]) bという文字列の後ろにaからzで1文字ある場合に、bでマッチする正規表現である。よって、文字列Abc#1234の場合、bにマッチする。 [![Image from Gyazo](https://i.gyazo.com/87e8745b36aa321583a395bc38e7d0ba.png)](https://gyazo.com/87e8745b36aa321583a395bc38e7d0ba) ### \A(?=.*?[a-z]) .\*?[a-z]は、任意の1文字が0文字以上で末尾の1文字がa~zのどれかという意味。●(?=○)と組み合わせると、●がないので、直前の空文字にマッチする。 [![Image from Gyazo](https://i.gyazo.com/ac8e42f04871585261663332aa7a6854.png)](https://gyazo.com/ac8e42f04871585261663332aa7a6854) パスワードの正規表現を作るときに、なぜ\A(?=.\*?[a-z])入っているのか?、理由は、入れることによって、ユーザーが入力したパスワードの中に小文字のアルファベットが含まれているかわかるからである。含まなかった場合、No matchesとエラーが出る。 もし、Abc#1234のbcを大文字にして、ABC#1234とすると空文字がマッチしなくなる。なぜマッチしなくなったかというと、.\*?[a-z]が文字列ABC#1234を満たさないからである。このことから、ユーザーが入力した文字列が正規表現にマッチするなら、小文字のアルファベットが含まれることがわかる。 [![Image from Gyazo](https://i.gyazo.com/b24b97288f747904dbec3b00a8363da4.png)](https://gyazo.com/b24b97288f747904dbec3b00a8363da4) ABC#1234の末尾にcをつけると空文字にマッチするようになる。なぜマッチするのかというと、.\*?[a-z]は、ABC#1234cにマッチする正規表現であるからである。マッチするので、●(?=○)と組み合わせると、その直前の空文字がマッチするようになる。 [![Image from Gyazo](https://i.gyazo.com/91dd477eac48e71a01d01903bda7e82b.png)](https://gyazo.com/91dd477eac48e71a01d01903bda7e82b) ### \A(?=.*?[a-z])(?=.*?[A-Z]) これは、ユーザーが入力したパスワードに小文字のアルファベットが入っていて、かつ、大文字のアルファベットが入っているかをチェックする正規表現である。満たしていたら、直前の文字(空文字)にマッチする。(?=.*?[a-z])は任意の1文字が0文字以上で末尾の1文字がaからzのどれかという意味である。 (?=.\*?[A-Z])は任意の1文字が0文字以上で末尾の1文字がA~Zのどれかという意味である。 [![Image from Gyazo](https://i.gyazo.com/9d94a15675f1202e945f680977288222.png)](https://gyazo.com/9d94a15675f1202e945f680977288222) もし、Abc#1234をABC#1234とするとNo matchesとエラーが出る。理由は、(?=.*?[a-z])を満たす文字列が存在しないからである。 [![Image from Gyazo](https://i.gyazo.com/64bb39f7e1153268f17331db0eb38e51.png)](https://gyazo.com/64bb39f7e1153268f17331db0eb38e51) ### \A(?=.*?[a-z])(?=.*?[A-Z])(?=.*?\d)(?=.*?[\W_]) この正規表現は、ユーザーが入力したパスワードに、 - 小文字のアルファベットが含まれる - 大文字のアルファベットが含まれる - 数字が含まれる - 記号または_が含まれる を表す。マッチしていた場合、直前の空文字にマッチする。 [![Image from Gyazo](https://i.gyazo.com/706fbae8b1d7e9e1960b4178a9727ce9.png)](https://gyazo.com/706fbae8b1d7e9e1960b4178a9727ce9) ### [!-~]+ [!-~]は、!からzの中で1文字を表す。[!-z]+は!からzの中の1文字で1文字以上を表す。これがあることによって、ユーザーが入力したパスワードがマッチするようになる。 [![Image from Gyazo](https://i.gyazo.com/08cf7be6ac4f605840102aab9ad4ad97.png)](https://gyazo.com/08cf7be6ac4f605840102aab9ad4ad97) ### \A(?=.*?[a-z])(?=.*?[A-Z])(?=.*?\d)(?=.*?[\W_])[!-~]+\z この正規表現は、ユーザーが入力したパスワードに、 - 小文字のアルファベットが含まれる - 大文字のアルファベットが含まれる - 数字が含まれる - 記号または_が含まれる を表す。そして、パスワードに!~z内の文字を使っているかもチェックしている。 [![Image from Gyazo](https://i.gyazo.com/d3cf78abfbfeddb02286f99fb2a56115.png)](https://gyazo.com/d3cf78abfbfeddb02286f99fb2a56115) ## 以上を踏まえて、どう改善していくか この正規表現をどう改善していくか。 ``` \A(?=.*?[a-z])(?=.*?[A-Z])(?=.*?\d)(?=.*?[\W_])[!-~]+\z ``` ### ユーザーが記号を入力しなくても良くする これに関しては、(?=.*?[\W_])を抜けば良いと思われる。抜くことによって、記号を入力しても入力しなくてもユーザーが入力したパスワードが通るようになる。記号をマッチさせるかは、[!-~]+を[a-z\d]+に変えたりして調整すれば大丈夫です。 ``` \A(?=.*?[a-z])(?=.*?[A-Z])(?=.*?\d)[!-~]+\z ``` - (?=.*?[\W_])を抜いた場合で、記号を入力しない場合 [![Image from Gyazo](https://i.gyazo.com/6e0ae2e17784bffd4070209fe09bd925.png)](https://gyazo.com/6e0ae2e17784bffd4070209fe09bd925) - (?=.*?[\W_])を抜いた場合で、記号を入力した場合 [![Image from Gyazo](https://i.gyazo.com/53a3d867ce66a6e584c01561fb4e9bd3.png)](https://gyazo.com/53a3d867ce66a6e584c01561fb4e9bd3) どちらもマッチする。 ### 大文字onlyでも小文字onlyでも大文字と小文字が混ざっていても通るようにしたい この場合は(?=.*?[A-Z])を消して、[a-z]を[a-zA-z]にすればいけそうです。以下のような正規表現にすれば大丈夫です。 ``` \A(?=.*?[a-zA-Z])(?=.*?\d)[!-~]+\z ``` - 大文字only [![Image from Gyazo](https://i.gyazo.com/eec19f1e13352a275d5c49e3a73c553e.png)](https://gyazo.com/eec19f1e13352a275d5c49e3a73c553e) - 小文字only [![Image from Gyazo](https://i.gyazo.com/3a2e8671baa151e7f0e1eb5f6e424147.png)](https://gyazo.com/3a2e8671baa151e7f0e1eb5f6e424147) - 大文字と小文字が混ざっている [![Image from Gyazo](https://i.gyazo.com/bfba0cee00071c9cffc270818b20aef8.png)](https://gyazo.com/bfba0cee00071c9cffc270818b20aef8) 全てマッチします。 ## 結論 この正規表現にすると良いかもしれません! ``` \A(?=.*?[a-zA-Z])(?=.*?\d)[!-~]+\z ``` この正規表現にすることで、以下がチェックできます。 - パスワードに大文字または小文字が含まれる。 - パスワードに数字が含まれる。 - パスワードが!から~の中の1文字以上で構成されている。