🔐

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

に公開

はじめに

最近暗号技術について興味があり、少しずつ学習をしています。
第1回はECBモードを、第2回はCBCモードを取り上げました。
今回はCFBモードを紹介します。
※今回の実装では、非推奨となっているメソッドを利用しています。あらかじめご了承ください。

第1回
https://zenn.dev/tmyhrn/articles/65ad6aa32964a5

第2回
https://zenn.dev/tmyhrn/articles/19542a7057f1c6

✅この記事でわかること

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

📝CFBモードについて

  • Cipher Feedbackの略
  • CBCモードと同じく、IV(初期化ベクトル)を使って暗号化する
  • ブロック暗号をストリーム暗号のように使えるため、パディング(データ長の調整)が不要

📊CFBモード図解

暗号化

  1. 最初は暗号化したIVと平文をXOR
  2. 出力された暗号ブロックを暗号化し、さらに平文とXORを繰り返す

復号化

  1. 最初は暗号化したIVと暗号ブロックをXOR
  2. 前に使用した暗号ブロックを暗号化し、さらに暗号ブロックとXORを繰り返す

⚠️CFBモードの注意点

  • パディング不要だが、IVの管理は重要
    →暗号文にIVを付加して一緒に保存・送信するのが一般的
  • 鍵とIVは16バイト(AESブロックサイズ)で固定長
  • crypto/cipherパッケージのNewCFBEncrypterNewCFBDecrypterは非推奨
    Go 1.24のリリース時に追加されたらしい

// 非推奨のコメント(Go公式より)
Deprecated: CFB mode is not authenticated, which generally enables active
attacks to manipulate and recover the plaintext. It is recommended that
applications use [AEAD] modes instead. The standard library implementation of
CFB is also unoptimized and not validated as part of the FIPS 140-3 module.
If an unauthenticated [Stream] mode is required, use [NewCTR] instead.

非推奨の理由

  1. CFBモードには認証機能がないため、改竄されても検知できない
    →AEADモード(認証付き暗号 / Authenticated Encryption with Associated Data)を使う
    →GCMモードが適している
  2. 実装が最適化されておらず、FIPS認証もない
    →認証されていなくても使いたい場合はNewCTRを使うこと

🛠Goでの実装方法

暗号化の手順

  1. ランダムなIVを生成
  2. NewCFBEncrypterで暗号化ストリームを作成
  3. XORKeyStreamで平文を暗号化

復号化の手順

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

実装

main.go
package main

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

// CFBモード暗号化
func encryptCFB(key, plainText []byte) ([]byte, []byte, error) {
    block, err := aes.NewCipher(key)
    if err != nil {
        return nil, nil, err
    }
    
    iv := make([]byte, aes.BlockSize)
    if _, err := io.ReadFull(rand.Reader, iv); err != nil {
        return nil, nil, err
    }
    
    encryptedText := make([]byte, len(plainText))
    stream := cipher.NewCFBEncrypter(block, iv)
    stream.XORKeyStream(encryptedText, plainText)
    
    return encryptedText, iv, nil
}

// CFBモード復号化
func DecryptCFB(key, encryptedText, iv []byte) ([]byte, error) {
    block, err := aes.NewCipher(key)
    if err != nil {
        return nil, err
    }
    
    decryptedText := make([]byte, len(encryptedText))
    stream := cipher.NewCFBDecrypter(block, iv)
    stream.XORKeyStream(decryptedText, encryptedText)
    
    return decryptedText, nil
}

func main() {
    key := []byte("examplekey123456")
    plainText := []byte("Hello, CFB mode!")
    encryptedText, iv, err := encryptCFB(key, plainText)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Encrypted:", hex.EncodeToString(encryptedText))
    fmt.Println("IV:", hex.EncodeToString(iv))
    decryptedText, err := DecryptCFB(key, encryptedText, iv)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Decrypted:", string(decryptedText))
}

出力結果例

ターミナル
Encrypted: 28c5949596b8d3c54ae0a29c26ef4556
IV: 47588bfd3422ea0669d7cadb432aba84
Decrypted: Hello, CFB mode!

まとめ

今回はCFBモードについて書いていきました。
IVを使うのがCBCモードと似ている一方、パディングが不要だという点が大きく違う点でした。
また、Go 1.24にて非推奨になったことで、別の暗号利用モードやその関数を用いる必要がありそうということも知ることができました。
次回は、OFBモードについて書いていこうと思います。

参考

Discussion