🔮

Goの共通鍵暗号の暗号モード CTRで暗号化

2023/02/28に公開

はじめに

Goで暗号化・復号処理をするという機会があったので、その際に調べたことを覚書として残しておきます。
暗号化自体の全体像に触れてから、細かい実装について記載します。

共通鍵暗号とは

暗号化は二種類あります。
共通鍵暗号と公開鍵暗号です。
違いとしては、

共通鍵暗号

  • 暗号化する鍵と復号する鍵が同じ。
  • 公開鍵暗号に比べて暗号化の処理速度が速い。
  • 標準規格として、DES(Data Encryption Standard)やAES(Advanced Encryption Standard)がある。安全性の観点からAESの方がよく使われる。

公開鍵暗号

  • 暗号化する鍵と復号する鍵が違う。
  • 共通鍵暗号に比べて暗号化の処理速度が遅い。

暗号モードとは

まず前提として暗号アルゴリズムには、ブロック暗号とストリーム暗号があります。
ストリーム暗号では平文を逐次暗号化していくのに対して、メッセージを固定長のまとまりごとに暗号化するのをブロック暗号といいます。

共通鍵暗号のAESではブロック暗号で暗号化します。

AESではブロック暗号のモードがいくつかあります。
例えば以下のようなものがあります。

  • ECBモード(Electronic CodeBook mode)
  • CBCモード(Cipher Block Chaining mode)
  • CFBモード(Cipher-FeedBack mode)
  • OFBモード(Output-FeedBack mode)
  • CTRモード(CounTeR mode)

CTRモードとは

その中でも、CTRモードはCRYPTREC, Practical Cryptgraphy推奨(こちらを参照)という安心っぽい名目があるのと、任意の長さの平文を暗号化できるというのがあったので、CTRモードに注目しました。

詳細は以下を参照していただきたいのですが、
https://ja.wikipedia.org/wiki/暗号利用モード#Counter_(CTR)
CTRモードの特徴としては、

  • ノンスという乱数とカウントアップしていく番号を合わせたカウンタというものを用いて暗号化する。
  • カウンタと平文のXORを取ることで暗号化する。
  • 暗号化と復号が同じ構造になるので実装が簡単になりやすい。

というようなものがあります。

GoでCTRモードの暗号化

具体的には以下のような実装で暗号化・復号処理を実装しました。

package main

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"fmt"
	"io"
)

// AESの暗号鍵の長さは16,24,32バイトのどれかの長さ。
const key = "passpass123456789abcdefghijklmno"

var block cipher.Block

func main() {
	b := []byte("test test")
	// 暗号鍵からAESのブロック暗号をnewする
	block, _ = aes.NewCipher([]byte(key))
	e, _ := Encrypt(b)
	d, _ := Decrypt(e)
	fmt.Println("decrypted:", string(d)) // decrypted: test test

}
func Encrypt(in []byte) ([]byte, error) {
	// ノンスの作成
	// ノンスはランダムなバイト列
	cipherText := make([]byte, aes.BlockSize+len(in))
	nonce := cipherText[:aes.BlockSize]
	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
		return nil, err
	}

	/// CTRモード
	encryptStream := cipher.NewCTR(block, nonce)
	// ストリーム暗号で暗号化
	encryptStream.XORKeyStream(cipherText[aes.BlockSize:], in)
	return cipherText, nil
}

func Decrypt(in []byte) ([]byte, error) {
	// 暗号化の際に入れたノンスの部分を取得
	nonce := in[:aes.BlockSize]

	// ノンスを除いた本来の復号すべき部分を取得
	in = in[aes.BlockSize:]
	// CTRモード
	decryptStream := cipher.NewCTR(block, nonce)
	// ストリーム暗号で復号
	decryptStream.XORKeyStream(in, in)
	return in, nil
}

参考文献

https://pkg.go.dev/crypto/cipher

https://ja.wikipedia.org/wiki/暗号利用モード

https://qiita.com/asksaito/items/1793b8d8b3069b0b8d68

https://qiita.com/opengl-8080/items/85df520e2d8c4e19be8e

https://qiita.com/omiso/items/6082b765c1257b71985b

https://qiita.com/liveasnotes/items/a5e35419242883029e25

https://deeeet.com/writing/2015/11/10/go-crypto/

Discussion