タイダログ

もっと怠けますか? (y/n)

プログラミングで情報Ⅰを学ぶ

情報Ⅰの授業でプログラミングを学ぶ(=プログラミングが目的)ということではなく、プログラミングを通して情報Ⅰの内容を学ぶ(=プログラミングは手段)ということを考えてみました。

コードはPython版とVBA版を載せています。F#版も載せようか……。

実践報告ではなく、あくまで考えてみただけですので、やってみたら上手くいかないかもしれません。ご了承ください。すでに実践されていたらぜひ教えてください!

随時更新します。

作業環境

情報社会の問題解決

まだ思い付きの段階ですが、ブルートフォース攻撃を再現してみると面白いかもしれません。パスワードの桁数や、英大文字+小文字+数字の組み合わせ方でどれだけパスワードが強固になるか、実感できそうです。また今度書きます。(2022/12/04 表現を訂正)

情報セキュリティ(ブルートフォース攻撃

(2022/12/04 追記 ここから)

書きました。もちろん、実際にどこかのサービスにブルートフォース攻撃を仕掛けるわけではありません。英大文字+小文字+数字の組み合わせを順番に作り、パスワード(仮)と一致させるだけです。

いきなり「英大文字+小文字+数字8桁」のような複雑なことをするのは大変ですので、まずは「数字1桁」で基本的な方法を確認し、そこから文字の種類や桁数を増やしていきましょう。

数字1桁のパスワードを解読する手順は、以下のように考えられます。

  1. 解読対象のパスワードを指定する
  2. パスワードに用いる文字のリストを作成する
  3. リストから文字を1つ取り出し、パスワードと一致するか確認する
    • 一致した場合、処理を終了する
    • 一致しなかった場合、リストから次の文字を1つ取り出し、パスワードと一致するか確認する
  4. リストの要素数だけ 3 を繰り返す

それをコードにするとこうなります。

# 解読対象のパスワード
pw = "9"

# パスワードに用いる文字のリスト
chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']

# パスワードが見つかったかどうかを示すフラグ
found = False

print("開始!")

i1 = 0

# 変数 i1 がリストの要素数未満で、なおかつ変数 found が False の間ループ
while i1 < len(chars) and found == False:
    # リストのインデックス i1 の要素を、パスワード候補として変数に代入
    candidate = chars[i1]
   
    # パスワード候補とパスワードが一致した場合
    if candidate == pw:
        print(f"""パスワードの解読に成功しました! パスワードは "{candidate}" です!""")
        
        # ループを終了させるため、フラグを True に設定
        found = True
    # リストの次の要素を取得するため、変数 i1 を1増やす
    i1 += 1

# 最後までパスワードが見つからなかった場合
if found == False:
    print(f"パスワードの解読に失敗しました……")

実行結果

開始!
パスワードの解読に成功しました! パスワードは "9" です!

次に、解読に要した時間を求めるコードを追加します。これは単に (解読が終了した時刻) - (解読を始めた時刻) で求められます。コメントに★が付いているのが追加部分です。

# ★モジュールをインポート
import time
import datetime

# パスワードに用いる文字のリスト
chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']

# 解読対象のパスワード
pw = "9"

# パスワードが見つかったかどうかを示すフラグ
found = False

print("開始!")

# ★解読を始めた時刻を取得
starting_time = datetime.datetime.fromtimestamp(time.time())

i1 = 0

# 変数 i1 がリストの要素数未満で、なおかつ変数 found が False の間ループ
while i1 < len(chars) and found == False:
    # リストのインデックス i1 の要素を、パスワード候補として変数に代入
    candidate = chars[i1]
   
    # パスワード候補とパスワードが一致した場合
    if candidate == pw:
        # ★解読が終了した時刻を取得
        ending_time = datetime.datetime.fromtimestamp(time.time())
        
        # ★(解読が終了した時刻) - (解読を始めた時刻)
        passed_time = ending_time - starting_time

        # ★解読に要した秒数とパスワードを表示
        print(f"""{passed_time.total_seconds()} 秒でパスワードを解読しました! パスワードは "{candidate}" です!""")
        
        # ループを終了させるため、フラグを True に設定
        found = True
    i1 += 1

# 最後までパスワードが見つからなかった場合
if found == False:
    # ★解読が終了した時刻を取得
    ending_time = datetime.datetime.fromtimestamp(time.time())
    # ★(解読が終了した時間) - (解読を始めた時間)
    passed_time = ending_time - starting_time
    # ★解読に要した秒数を表示
    print(f"{passed_time.total_seconds()} 秒かけてもパスワードを解読できませんでした……。使用する文字の種類や桁数を変更して再度実行してください。")

実行結果

開始!
0.000255 秒でパスワードを解読しました! パスワードは "9" です!

ここまでが基本的な方法です。あとは、使用する文字の種類を増やすならリストの要素を増やし、桁数を増やすならループを増やします。「英小文字+数字4桁」の場合、このようになります。

import time
import datetime

# パスワードに用いる文字のリスト(英小文字+数字)
chars = ['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']

pw = "zzzz"
found = False

print("開始!")
starting_time = datetime.datetime.fromtimestamp(time.time())

# 左から1桁目の文字を変更するためのループ
i1 = 0
while i1 < len(chars) and found == False:
    
    # 左から2桁目の文字を変更するためのループ
    i2 = 0
    while i2 < len(chars) and found == False:
        
        # 左から3桁目の文字を変更するためのループ
        i3 = 0
        while i3 < len(chars) and found == False:
           
            # 左から4桁目の文字を変更するためのループ
            i4 = 0
            while i4 < len(chars) and found == False:
                
                # パスワード候補を作成
                candidate = chars[i1] + chars[i2] + chars[i3] + chars[i4]
                if candidate == pw:
                    ending_time = datetime.datetime.fromtimestamp(time.time())
                    passed_time = ending_time - starting_time
                    print(f"""{passed_time.total_seconds()} 秒でパスワードを解読しました! パスワードは "{candidate}" です!""")
                    found = True
                i4 += 1
            i3 += 1
        i2 += 1
    i1 += 1

if found == False:
    ending_time = datetime.datetime.fromtimestamp(time.time())
    passed_time = ending_time - starting_time
    print(f"{passed_time.total_seconds()} 秒かけてもパスワードを解読できませんでした……。使用する文字の種類や桁数を変更して再度実行してください。")

実行結果

開始!
1.065089 秒でパスワードを解読しました! パスワードは "zzzz" です!

文字の種類を増やすグループと桁数を増やすグループに分かれて、それぞれを増やした場合のパスワードの解読時間の変化を確かめてみたら面白そうじゃないですか。

その結果から、生徒が短いパスワードの危険性を実感してくれれば幸いです。

VBA版はこのようなコードになります。

Sub LearnBruteForceAttack()
    Dim chars As Variant
    chars = Array("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")
    
    Dim pw As String
    pw = "zzzz"
    
    Dim found As Boolean
    found = False
    
    MsgBox "開始!"
    Dim startingTime As Double
    startingTime = Timer()
    
    Dim i1 As Long, i2 As Long, i3 As Long, i4 As Long
    Dim endingTime As Double, passedTime As Double
    
    i1 = 0
    Do While i1 < UBound(chars) + 1 And found = False
        
        i2 = 0
        Do While i2 < UBound(chars) + 1 And found = False
            
            i3 = 0
            Do While i3 < UBound(chars) + 1 And found = False
               
                i4 = 0
                Do While i4 < UBound(chars) + 1 And found = False
                    
                    Dim candidate As String
                    candidate = chars(i1) & chars(i2) & chars(i3) & chars(i4)
                    If candidate = pw Then
                        endingTime = Timer()
                        passedTime = endingTime - startingTime
                        MsgBox passedTime & " 秒でパスワードを解読しました! パスワードは " & candidate & " です!"
                        found = True
                    End If
                    
                    i4 = i4 + 1
                Loop
                
                i3 = i3 + 1
            Loop
            
            i2 = i2 + 1
        Loop
        
        i1 = i1 + 1
    Loop
    
    If found = False Then
        endingTime = Timer()
        passedTime = endingTime - startingTime
        MsgBox passedTime & " 秒かけてもパスワードを解読できませんでした……。使用する文字の種類や桁数を変更して再度実行してください。"
    End If
End Sub

(2022/12/04 追記 ここまで)

コミュニケーションと情報デザイン

文字のデジタル化(文字コード

「コンピュータ上では文字に番号を割り当てて扱っているよ」と説明するだけでは、生徒諸君からは「?」という、文字コード 111111(2) な反応が返ってくるだけだと予想されますので、プログラム上で文字と数値を変換して確認してもらおうと思います。

ある文字の文字コードを取得するところから始めましょう。Python では ord() 関数で文字の Unicode コードポイント(文字コード)を10進数で取得できます。

my_code = ord("A") # 文字 -> 10進数
print(my_code)

実行結果

65

これを format() 関数で2進数と16進数に変換します。bin() 関数や hex() 関数でも変換できますが、それだとプレフィックス 0b0x が付いて生徒が混乱しそうなのと、桁数の指定ができないからです。format() 関数だと指定できます。

my_chr = "A"
my_code = ord(my_chr) # 文字 -> 10進数
my_bin = format(my_code, "07b") # 10進数 -> 2進数7桁
my_hex = format(my_code, "02x") # 10進数 -> 16進数2桁
print(f"Unicode での {my_chr} の文字コードは、2進数で {my_bin}、16進数で {my_hex} です。")

実行結果

Unicode での A の文字コードは、2進数で 1000001、16進数で 41 です。

今度は逆に、番号から文字にしてみましょう。chr() 関数を使います。

my_code = 65
my_chr = chr(my_code) # 10進数 -> 文字
print(f"Unicode で文字コードが10進数の {my_code} になる文字は {my_chr} です。")

実行結果

Unicode で文字コードが10進数の 65 になる文字は A です。

こんな形で、文字に番号を振っていることを理解してもらったところで、for文による一括出力に挑戦しましょう。大文字のアルファベットを連続で出力する場合、A のコードが 65(10)Z のコードが 90(10) ですので、65~90 でforループを回します。

for i in range(65, 91):
    my_chr = chr(i) # 10進数 -> 文字
    my_bin = format(i, "07b") # 10進数 -> 2進数7桁
    my_hex = format(i, "02x") # 10進数 -> 16進数2桁
    print(f"Unicode で文字コードが10進数の {i}、2進数の {my_bin}、16進数の {my_hex} になる文字は {my_chr} です。")

実行結果

Unicode で文字コードが10進数の 65、2進数の 1000001、16進数の 41 になる文字は A です。
Unicode で文字コードが10進数の 66、2進数の 1000010、16進数の 42 になる文字は B です。
Unicode で文字コードが10進数の 67、2進数の 1000011、16進数の 43 になる文字は C です。
(略)
Unicode で文字コードが10進数の 90、2進数の 1011010、16進数の 5a になる文字は Z です。

VBA版はこのようなコードになります。

Sub LearnUnicode()
    Activesheet.Cells.ClearContents

    Dim i As Long, myChr As String, myBin As String, myHex As String
    For i = 65 To 90
        myChr = Chr(i)
        myBin = WorksheetFunction.Base(i, 2, 7)
        myHex = WorksheetFunction.Base(i, 16, 2)
        ActiveSheet.Cells(i - 64, 1).Value = "Unicode で文字コードが10進数の " & i & "、2進数の " & myBin & "、16進数の " & myHex & " になる文字は " & myChr & " です。"
    Next i
End Sub

F#版です。

let toBin (input : int) =
    System.Convert.ToString(input, 2)

let toHex (input : int) =
    System.Convert.ToString(input, 16)

[65..90]
|> List.iter (fun x -> printfn "Unicode で文字コードが10進数の %d、2進数の %s、16進数の %s になる文字は %c です。" x (toBin x) (toHex x) (char x))

画像のデジタル化(RGB値)

色をRGB値で表現することも、プログラムで確認してみましょう。Python版では HTML と CSS を使うので少し難しいかもしれません。

まず、こんなコードを書きます。IPython モジュールを使って HTML の <span> 要素を生成し、画面に出力するコードです。<span> 要素内の style 属性でRGB値を使って背景色を指定します。変数 rgb の値を変えると背景色が変わります。ここを触ることで、RGB値と色の関係を学べると思います。

f文字列の引用符 ' を三重にすると、f文字列内で改行できます。f'...'f'''...''' です。

from IPython.display import display, HTML

# RGB値を決める
r = 128
g = 0
b = 0

# f文字列を使って、HTMLのspanタグにr, g, b を埋め込む
span = f'''
<span style="background-color: rgb({r}, {g}, {b});">
    R: {r}, G: {g}, B: {b}
</span>'''

# span の文字列をHTMLの要素に変換し、画面に出力する
display(HTML(span))

RGB値 (1)

RGB値も数値なので、for文で回しましょう。先ほどのコードにfor文を付け足して、変数 r にカウンタ変数 i を代入しているだけです。「黒から赤のグラデーションを作ろう」などとお題を出すと楽しそうですね(妄想)。

from IPython.display import display, HTML
for i in range(0, 256):
    # RGB値を決める
    r = i
    g = 0
    b = 0

    # f文字列を使って、HTMLのspanタグにr, g, b を埋め込む
    span = f'''
    <span style="background-color: rgb({r}, {g}, {b});">
        R: {r}, G: {g}, B: {b}
    </span>'''

    # span の文字列をHTMLの要素に変換し、画面に出力する
    display(HTML(span))

画像ではわかりづらいですが、スクロールするとグラデーションがかかっています。

RGB値 (2)

ここまで来たら、せっかくですので文字色と背景色の組み合わせを見ましょう。<span> 要素の style 属性で文字色に白を指定し、背景色は先ほど同様グラデーションにします。背景色と文字色のコントラストによって文字の見やすさが変わることが確認できると思います。

from IPython.display import display, HTML
for i in range(0, 256):
    # RGB値を決める
    r = i
    g = 0
    b = 0

    # f文字列を使って、HTMLのspanタグにr, g, b を埋め込む
    span = f'''
    <span style="color: #ffffff; background-color: rgb({r}, {g}, {b});">
        R: {r}, G: {g}, B: {b}
    </span>'''

    # span の文字列をHTMLの要素に変換し、画面に出力する
    display(HTML(span))

こちらも画像ではわかりづらいですが、スクロールするとグラデーションがかかっています。

RGB値 (3)

VBA版です。Python版と違ってセルに色を付けるだけで済むので楽です。

Sub LearnRgb()
    ' セルの値と書式を初期化
    Activesheet.Cells.ClearContents
    Activesheet.Cells.ClearFormats

    Dim i As Long, r As Long, g As Long, b As Long
    For i = 0 To 255
        ' RGB値を決める
        r = i
        g = 0
        b = 0

        ' セルの背景色を指定
        Activesheet.Cells(i + 1, 1).Interior.Color = RGB(r, g, b)

        Activesheet.Cells(i + 1, 1).Value = "R: " & r & ", G: " & g & ", B: " & b
    Next i
End Sub

ColorIndex プロパティを使うのはやめましょうね。

Sub LearnRgb2()
    ' セルの値と書式を初期化
    Activesheet.Cells.ClearContents
    Activesheet.Cells.ClearFormats

    Dim i As Long, r As Long, g As Long, b As Long
    For i = 0 To 255
        ' RGB値を決める
        r = i
        g = 0
        b = 0

        ' セルの文字色と背景色を指定
        Activesheet.Cells(i + 1, 1).Font.Color = RGB(255, 255, 255)
        Activesheet.Cells(i + 1, 1).Interior.Color = RGB(r, g, b)

        Activesheet.Cells(i + 1, 1).Value = "R: " & r & ", G: " & g & ", B: " & b
    Next i
End Sub

Fable を使えば F# でもできます。

コンピュータとプログラミング

シミュレーションをプログラムで行うという話を各所で聞くので、そんなことをしたらいいと思いますがまだ考えていません。また今度書きます。

情報通信ネットワークとデータの活用

以前、Yahoo! JAPAN が提供する 日本語形態素解析の Web API で遊んだら楽しかったので、テキストマイニングなどしたら面白そうです。クラスでアンケートを行って、結果を Web API で分析するとか。ただPOSTメソッドでリクエストするのでハードルが高い気がします。

Yahoo! JAPAN気象情報APIはGETメソッドですので簡単かもしれません。文部科学省の「高等学校情報科『情報Ⅰ』教員研修用教材」にも名前が載っています。

ただいずれにせよ Yahoo! JAPAN の Web API はユーザ登録が必要ですので、その辺りが煩雑です。代わりに Open-Meteo という天気予報 Web API が登録不要で、欲しい情報を細かく指定できるので使ってみるといいかもしれません。こちらの記事で詳しくまとめてくださっています。

paiza.hatenablog.com

「プログラム書かんのかい」というツッコミが入りそう。いつか書きます。

結び

「プログラミングで情報Ⅰを学ぶ」と大きく銘打っておきながら、情報デザインの単元のプログラムしか書けておりません。「プログラミングで情報のデジタル化を学ぶ」に改題しようかとも思いましたが、今後の自分に期待してそのままにしておきます。というわけで随時追記します。

良いアイデアをお持ちの方はぜひお知らせください! 待ってまーす!

(2022/12/04 追記 ここから)

情報セキュリティも書きました。これでタイトルに偽りなし。

(2022/12/04 追記 ここまで)

参考

更新