🔐

【Go】Goで学ぶ暗号技術 ~CTRモード~

に公開

はじめに

最近暗号技術について興味があり、少しずつ学習をしています。
第1回はECBモード、第2回はCBCモード、第3回はCFBモード、第4回はOFBモードを取り上げました。
今回はCTRモードを紹介します。

第1回
https://zenn.dev/tmyhrn/articles/65ad6aa32964a5
第2回
https://zenn.dev/tmyhrn/articles/19542a7057f1c6
第3回
https://zenn.dev/tmyhrn/articles/b7cee9a8951a5e
第4回
https://zenn.dev/tmyhrn/articles/430c893f2016e7

✅この記事でわかること

  • CTRモードの特徴
  • CTRモードの仕組み(暗号化・復号化の流れ)
  • CTRモードを使う上での注意点

📝CTRモードの特徴

  • Counterの略
  • 「Nonce + Counter」を暗号化する
    • Nonce = Number used onceの略
    • IV(初期化ベクトル)同様、ランダムな値のことを指す
  • Counterがインクリメントされるのを利用して暗号化

📊CTRモード図解

暗号化

  1. 暗号化した「Nonce + Counter」と平文をXOR
  2. Counterをインクリメントし、その値を暗号化し平文とXOR

復号化

  1. 暗号化した「Nonce + Counter」と暗号ブロックをXOR
  2. Counterをインクリメントし、その値を暗号化し暗号ブロックとXOR

⚠️CTRモードの注意点

  • Nonceの再利用はNG
    crypto/randなどで毎回異なるNonceを生成
  • Nonce + Counterの合計が16バイトになるようにする
  • 認証(改ざん検出)機能がない
    →認証付き暗号(AEAD)の利用も検討する

🛠Goでの実装方法

暗号化の手順

  1. 「Nonce + Counter」を生成
  2. NewCTRで暗号化ストリームを作成
  3. XORKeyStreamで平文を暗号化

復号化の手順

  1. NewCTRで復号ストリームを作成
  2. XORKeyStreamで復号化

実装

main.go
package main

import (
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "encoding/hex"
    "fmt"
    "io"
    "log"
)

// CTR暗号化
func encrypTCTR(key, plainText []byte) ([]byte, []byte, error) {
    if len(key) != 16 && len(key) != 24 && len(key) != 32 {
        return nil, nil, fmt.Errorf("invalid key length: must be 16, 24, or 32 bytes, got %d bytes", len(key))
    }
    block, err := aes.NewCipher(key)
    if err != nil {
        return nil, nil, err
    }
    nonce := make([]byte, aes.BlockSize)
    if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
        return nil, nil, err
    }
    encryptedText := make([]byte, len(plainText))
    stream := cipher.NewCTR(block, nonce)
    stream.XORKeyStream(encryptedText, plainText)
    
    return encryptedText, nonce, nil
}

// CTR復号化
func decryptedCTR(key, nonce, encryptedText []byte) ([]byte, error) {
    if len(key) != 16 && len(key) != 24 && len(key) != 32 {
        return nil, fmt.Errorf("invalid key length: want %d bytes, got %d bytes", aes.BlockSize, len(key))
    }
    if len(nonce) != aes.BlockSize {
        return nil, fmt.Errorf("invalid nonce length: want %d bytes, got %d bytes", aes.BlockSize, len(nonce))
    }
    block, err := aes.NewCipher(key)
    if err != nil {
        return nil, err
    }
    decryptedText := make([]byte, len(encryptedText))
    stream := cipher.NewCTR(block, nonce)
    stream.XORKeyStream(decryptedText, encryptedText)
    
    return decryptedText, nil
}

func main() {
    key := []byte("ctr-mode-example")
    plainText := []byte("Hello, CTR mode!")
    fmt.Println("PlainText:", string(plainText))
    
    encryptedText, nonce, err := encrypTCTR(key, plainText)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("EncryptedText:", hex.EncodeToString(encryptedText))
    fmt.Println("Nonce", hex.EncodeToString(nonce))
    
    decryptedText, err := decryptedCTR(key, nonce, encryptedText)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("DecryptedText:", string(decryptedText))
}

出力結果例

ターミナル
PlainText: Hello, CTR mode!
EncryptedText: e52a1d0056cb4c1526c5d686f35c4342
Nonce 3fc3d1ae7794196d6b11395c014139b7
DecryptedText: Hello, CTR mode!

まとめ

今回はCTRモードについて書いていきました。
IVに似ている「Nonce + Counter」というものを利用することを知りました。
次回は、GCMモードについて書いていこうと思います。

参考

https://pkg.go.dev/crypto/aes
https://pkg.go.dev/crypto/cipher
https://zenn.dev/naoki_kuroda/articles/91e32c41e640a1
https://blog.kota-yata.com/posts/ciphermode/
https://www.ochappa.net/posts/block-enc-mode

Discussion