フューチャー技術ブログ

Go言語によるCFB8暗号化

はじめに

こんにちは。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.go
package 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での暗号化が必要になった方のお役にたてば幸いです。