パブリックなネットワーク上でデータのやり取りを行う場合には、第三者にデータを盗み見や改ざんさせない仕組みが必要になります。
その仕組みとしてデータの暗号化があります。暗号化方式には共通鍵暗号と公開鍵暗号が2種類あり、まずは共通鍵暗号を勉強してみました。

共通鍵暗号

暗号化と復号を同一鍵で行う暗号方式です。
共通鍵暗号の暗号化アルゴリズムでは長い間DESが利用されてきましたが、コンピューター性能が上がってきたことでDESの安全性が低下してきました。そこで後継となる次世代アルゴリズムの公募があり2000年にAESが採択されました。
現在はこのAESが利用されおり、ブロック暗号で鍵長が128bit / 192bit / 256bitから選択でき、また平文は128bitずつ暗号化されます。

暗号モード

DESやAESといったブロック暗号は一定のビット数を暗号化する方式でしたが、メッセージはビット数までしか暗号化できませんでした。そこで暗号化したい長いメッセージをビット長のブロックに分割し、繰り返しブロック暗号化を行えるようにするため暗号モードが必要になりました。

暗号モードには、秘匿用の利用モードと、認証用の利用モードがあります。

秘匿用の利用モード

暗号化モードでECB, CBC, OFB, CFB, CTRのモードがあり、ブロック長とストリームで分類できます。

  • ECB, CBCは、特定のブロック長で処理を行う暗号化
  • OFB, CFB, CTRは、ストリームで順次処理を行う暗号化

今回はブロック暗号化に絞って調べてみました

ECB(Electronic CodeBook)

ECBモードは、単純な暗号利用モードでメッセージがブロックに分割され、それぞれのブロックは独立して暗号化されます。
このモードは欠点がって、長いメッセージ(画像データなど)のある部分が他の部分と同じであるかどうかが暗号文の比較によって判断できてしまうので、他のCBCモードなどが考えられました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
plainText := "fizzbuzzfizzbuzz"	  // 16bye
key := []byte("passpasspasspass") // 16bye

block, err := aes.NewCipher(key)
if err != nil {
fmt.Printf("err: %s\n", err)
return
}

var cipherText []byte

// Encrypt
cipherText = make([]byte, len(plainText))
block.Encrypt(cipherText, []byte(plainText))
fmt.Printf("Cipher text: %x\n", cipherText)

// Decrypt
decryptedText := make([]byte, len(cipherText))
block.Decrypt(decryptedText, cipherText)
fmt.Printf("Decrypted text: %s\n", string(decryptedText))

CBC(Cipher Block Chaining)

CBCモードは、ECBモードの欠点を補われたモードで最も広く用いられている暗号利用モードになります。
1つ前の暗号文ブロックと平文ブロックをXORしたものを暗号化することを繰り返す手法になります。またメッセージごとのユニーク性を確保するため、最初のブロックの暗号化には初期化ベクトル(iv)が利用されます。
ただブロック暗号化の仕様上しかたない部分ではありますが、ブロックのビット長の整数倍となるようメッセージはパディング調整する必要があります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
plainText := "fizzbuzzfizzbuzz"	  // 16bye
key := []byte("passpasspasspass") // 16bye

block, err := aes.NewCipher(key)
if err != nil {
fmt.Printf("err: %s\n", err)
return
}

var cipherText []byte

// Encrypt
cipherText = make([]byte, aes.BlockSize+len(plainText))
iv := cipherText[:aes.BlockSize]
_, err = rand.Read(iv)
if err != nil {
fmt.Printf("err: %s\n", err)
return
}
cbc := cipher.NewCBCEncrypter(block, iv)
cbc.CryptBlocks(cipherText[aes.BlockSize:], []byte(plainText))
fmt.Printf("Cipher text: %x\n", cipherText)

// Decrypt
_ = cipherText[:aes.BlockSize] // iv
cipherText = cipherText[aes.BlockSize:]
decryptedText := make([]byte, len(cipherText))
cbc = cipher.NewCBCDecrypter(block, iv)
cbc.CryptBlocks(decryptedText, cipherText)
fmt.Printf("Decrypted text: %s\n", string(decryptedText))

認証用の利用モード

暗号化と同時に完全性や認証性も実現するための暗号モードで、総称してAEDA (Authenticated Encryption with Asocciated Data) と呼ばれます。
次のものなどが知られていて、今回はGCMについて調べてみました。

  • CCM (Counter with CBC-MAC)
  • GCM (Galois/Counter Mode)
  • OCB (Offset CodeBook)
  • XCBC (eXtended Ciphertext Block Chaining)

GCM (Galois/Counter Mode)

GCMは暗号化としてCTRモードを、認証としてTAG(Galois mode)を生成してを組み合わせたもので、これによりデータ保護と認証性(完全性確認)が実現されます。
ブロック長128ビットのブロック暗号に適用可能でパディングは不要です。最初に任意長の初期化ベクトル(iv)を必要とします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
plainText := "fizzbuzzfizzbuzz..."
key := []byte("passpasspasspass") // 16bye

block, err := aes.NewCipher(key)
if err != nil {
fmt.Printf("err: %s\n", err)
return
}

aesGCM, err := cipher.NewGCM(block)
if err != nil {
fmt.Printf("err: %s\n", err)
return
}

// Encrypt
nonce := make([]byte, aesGCM.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
fmt.Printf("err: %s\n", err)
}
cipherText := aesGCM.Seal(nonce, nonce, []byte(plainText), nil)
fmt.Printf("Cipher text: %x\n", cipherText)

// Decrypt
nonceSize := aesGCM.NonceSize()
nonce, cipherText = cipherText[:nonceSize], cipherText[nonceSize:]
decryptedText, err := aesGCM.Open(nil, nonce, cipherText, nil)
if err != nil {
return
}
fmt.Printf("Decrypted text: %s\n", string(decryptedText))

参考URL


X