はじめに
こんにちは。2020年1月にキャリア入社した、TIGの八巻です。今年初めて、プロジェクトでGo言語に触れました。Go言語初心者です。
プロジェクトで利用したデータ取得のAPIで、リクエストパラメータを暗号化利用モード「CFB8」で暗号化する必要がありました。この暗号化利用モード「CFB8」をGo言語で実装する際に躓いたため、実装内容を備忘として記載したいと思います。
目次
- CFB暗号化利用モードについて
- Go言語での実装
- まとめ
CFB暗号化利用モードについて
まず、暗号利用モードとは、ブロック暗号を繰り返し利用することで、一定のブロック長よりも長いメッセージを暗号化するメカニズムのことで、CFB(Cipher Feedback Mode)もその1つです。CFBの他に、ECB、CBC、OFBなどの暗号化利用モードがありますが、これらは、FIPS(Federal Information Processing Standards)で標準化されています。
※FIPS PUB-81
CFBモードは、1つ前の暗号文ブロックを暗号化アルゴリズムの入力に戻し、次の平文ブロックとのXORを取って暗号ブロックとして出力します。最初の暗号文ブロックを作る時は、1つ前の暗号文ブロックが存在しないため、初期ベクトル(iv)と呼ばれるランダムなビット列を使用します。この暗号ブロックを入力に戻すのがCFBモードの特徴です。
Go言語での実装
環境
本記事では以下の環境で行いました。
OS: macOS Big Sur 11.4
Go:1.17
Go言語でのCFB8のブロック暗号化
Goの「crypto/cipher」パッケージに記載されているサンプルコードでは、CFB8の暗号文を得られなかったのが、プロジェクトでの躓きポイントでした。CFB8では、セグメントサイズが8ビットとなるようですが、Goの「crypto/cipher」パッケージにあるサンプルのままでは対応していないらしく、手を加える必要がありました。
※ここで言うセグメントサイズは、暗号文と平文をXORで結合するビット数のことを指します。
(この辺り、もう少し深く理解したい。)
CFB8での実装例は以下となります。
main.gopackage main
import ( "crypto/aes" "crypto/cipher" "crypto/rand" "fmt" "io" "log" )
type cfb8 struct { b cipher.Block blockSize int in []byte out []byte
decrypt bool }
var plaintext string = "平文の文字列"
func main() { key := []byte("0123456789ABCDEF") block, err := aes.NewCipher(key) if err != nil { panic(err) }
ciphertext := make([]byte, aes.BlockSize+len(plaintext)) iv := ciphertext[:aes.BlockSize]
if _, err = io.ReadFull(rand.Reader, iv); err != nil { panic(err) }
stream := newCFB8Encrypter(block, iv) stream.XORKeyStream(ciphertext[aes.BlockSize:], []byte(plaintext)) fmt.Printf("Encrypted result:%x\n", plaintext)
stream = newCFB8Decrypter(block, iv) stream.XORKeyStream([]byte(plaintext), ciphertext[aes.BlockSize:]) fmt.Printf("Decrypted result:%s\n", plaintext) }
func newCFB8Encrypter(block cipher.Block, iv []byte) cipher.Stream { return newCFB8(block, iv, false) }
func newCFB8Decrypter(block cipher.Block, iv []byte) cipher.Stream { return newCFB8(block, iv, true) }
func newCFB8(block cipher.Block, iv []byte, decrypt bool) cipher.Stream { blockSize := block.BlockSize()
if len(iv) != blockSize { panic("cipher.newCFB: IV length must equal block size") } x := &cfb8{ b: block, blockSize: blockSize, out: make([]byte, blockSize), in: make([]byte, blockSize), decrypt: decrypt, } copy(x.in, iv)
return x }
func (x *cfb8) XORKeyStream(dst, src []byte) { for i := range src { x.b.Encrypt(x.out, x.in) copy(x.in[:x.blockSize-1], x.in[1:]) if x.decrypt { x.in[x.blockSize-1] = src[i] } dst[i] = src[i] ^ x.out[0] if !x.decrypt { x.in[x.blockSize-1] = dst[i] } } }
|
※keyは、長さは16、24、32バイトのいずれかである必要があります。ここでは便宜上「0~F」の16文字を設定しています。
参考:https://stackoverflow.com/questions/23897809/different-results-in-go-and-pycrypto-when-using-aes-cfb
実行結果は、以下のようになります。
実行結果Encrypted result:e5b9b3e69687e381aee69687e5ad97e58897 Decrypted result:平文の文字列
|
まとめ
今回は、Go言語での「CFB8」の実装について記載させていただきました。
言語やパッケージによっても、暗号化利用モードの細かい部分に違いがあることがわかりました。
ニッチな内容ではありますが、Go言語でCFB8での暗号化が必要になった方のお役にたてば幸いです。