djangoで画像をアップロードする機能を実装できてそのまま満足してはいけません。
単なるアップロードではセキュリティ上非常に脆弱なリスクを負うことになります。
今回はハッシュ化を実行すべき理由とファイル名をハッシュ化させるコードを紹介します。
・OS:Mac
・django 2.2
・python 3.8
ちなみに以前紹介した「django画像アップロード機能」を実装する方法記事の拡張機能になります。
コンテンツ
ハッシュ化とは
そもそもハッシュ化とは、特定の文字列や数字の羅列を一定のルール(ハッシュ関数)に基づいた計算手順によって別の値(ハッシュ値)に置換させることをさします。
例えば下記の例のようにパスワードはハッシュ化されます。
パスワード:ninjya →ハッシュ化→ ハッシュ化されたパスワード:364b388604d127d6
暗号化との違いは元に戻せるかどうかで、ファイル名をハッシュ化すると金輪際元には戻せない置き換えとなりセキュリティが非常に強固なものとなります。
ファイル名を変えるべき理由
変えるべき理由としては、
1.重複を防ぐ
2.Chromeのようなブラウザで「ページのソースを表示する」等で画像のパスとファイル名が見えてしまう
の2点があります。
より重要なのは2.なのですが、それでは論より証拠ということで、実際にページのソースを表示してみます。
下記画像はlocal環境で作成したページになります。
右下に見えるのがimgタグで表示した画像になります。
こちらはブラウザはChromeを利用します。
そして画像の画面で右クリックのページのソースを表示をクリックします。
実際にimgタグの中にsrcリンクのパスとファイル名がしっかりと表示されていることがわかります。
ここから推測できることは、パスとファイル名の規則性が分かれば、その脆弱性をついて内部の実際には表示しない・されたくない画像を表示・取得できたりされる可能性があります。
これは非常に危険です。
ちなみに上記の方法でamazonなんかのページのソースを表示すると、すごく複雑なファイル名として表示されているのがわかります。
例えばこれが実際のamazonのimgタグのsrc属性の値です。
ファイル名は見ての通り容易に推測できないような名前となっています。
ファイル名を変更するにはUUIDという手段もある
UUIDとはUniversally Unique Identifierの略で16進法による表示であります。
pythonのUUIDの使い方はこちらが参考になります。
参考 https://docs.python.org/ja/3/library/uuid.html
実際のコードで動かすとこうなります。
1 2 |
import uuid print(uuid.uuid4()) #efee7a72-667e-4675-83e1-ad0e29610609 |
毎回異なる表示になります。
ファイル名のハッシュ化する方法
それでは実際のハッシュ化をさせるpythonコードを紹介します。
1 2 3 4 5 6 7 |
import os import hashlib file_name = 'test.png' extension = str(file_name).split('.')[-1] hs_filename = '%s.%s' % (hashlib.md5(file_name.encode()).hexdigest(), extension) print(hs_filename) #364be8860e8d72b4358b5e88099a935a.png |
4行目のstr(”)の中に格納するファイル名を入れて実際に動かしてみます。
こちらの場合printで出力される値は 毎回同様の364be8860e8d72b4358b5e88099a935a.png になります。
5行目はファイル形式を抽出するコードになります。
例えば、test.pngならpngを抽出、test.jpegならjpegを抽出します。
6行目のhashlib.md5()は与えられた値に対して適当な値を返す関数です。
参考:https://docs.python.org/ja/3/library/hashlib.html
hexdigest()は長さ 32 の文字列になり、 16 進表記文字しか含みません。この文字列は電子メールやその他のバイナリを受け付けない環境でダイジェストを安全にやりとりするために使うことができます。
djangoのmodelsに実装する
先に説明したハッシュ化のコードを実際にmodelsに組み込みます。
組み込むことで画像が保存される際にファイル名を自動的にハッシュ化します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
import os import hashlib from django.db import models from datetime import datetime def _user_profile_avator_upload_to(instance, filename): current_time = datetime.now() pre_hash_name = '%s%s%s' % (instance.user_id, filename, current_time) extension = str(filename).split('.')[-1] hs_filename = '%s.%s' % (hashlib.md5(pre_hash_name.encode()).hexdigest(), extension) saved_path = 'images/' return '%s%s' % (saved_path, hs_filename) class UserProfile(models.Model): user_id = models.IntegerField( verbose_name = 'user_id', null = False, ) avator = models.ImageField( verbose_name = 'avator', # upload_to = 'images/', upload_to = _user_profile_avator_upload_to, default = 'images/default_icon.png' ) birthday = models.DateField( verbose_name = 'birthday', null=True, blank=False ) def __int__(self): return self.user_id |
17行目のUserProfileがクラス名でavatorフィールドが画像の保存に関してのフィールドになります。
コメントアウトした、upload_to = ‘images/’のままならファイル名が直でそのまま保存されていました。
しかしupload_to = _user_profile_avator_upload_to,とすることで内部関数を呼び出します。
8行目で呼び出された関数は、instance(自分自身のオブジェクト)とfilename(ファイル名)を与えられます。
9行目で現在時刻を呼び出しているのは、現在時刻をハッシュ関数に組み込むことでほぼ2度と同じような名前のファイル名を作成できないようにするためです。
10行目でハッシュ化する前の前段階として、与えられたパーツを全て連結させます。
連結させたpre_hash_nameをハッシュ化するコードが12行目になります。
こちらで実際に動作を動かすと下記のようなファイル名を持つデータが生成されました。
以上になります。
プログラミング学習を効率良く進めるには…
私ヒロヤンがプログラミングを始めた頃は以下のような感じでした。
そしてネットで調べていくうちに膨大な時間が過ぎていきました。
私ヒロヤンの実体験より、プログラミングを効率的に学ぶために大切なことは以下のことだと考えています。
1. いつまでもダラダラとやらないで、目標を決定して短期集中する
2. マンツーマンで、わからない箇所は直ぐに質問をして即レスをもらう
.proでは私ヒロヤンが学習してきたプログラミング経験0からのpython/django、その他webサイト・サービス開発のコースが用意されています。
カウンセリング自体は無料なので話を聞いてみるだけでもいかがでしょうか?
また以下のリンク先ではdjangoを教えてくれるスクールをまとめ紹介しています。
コメントを残す