You have 2 free member-only stories left this month.

Goでのパスワードを使用したデータ暗号化(前半)

gavin.zhou
Jul 6 · 12 min read

Introduction

データを暗号化する場合、通常はそのデータを復号化できるランダムキーを作成します。特定のケースにおいては、パスワードのようにデータを復号化するためにユーザが指定したキーを使用する場合もあります。しかし、暗号化アルゴリズムに使用されるキーは、通常、少なくとも32バイトである必要があります。しかし、私たちのパスワードではその基準を満たしていない可能性が高いので、それを解決する方法が必要になります。最近、ずっと私は解決策を模索していたので、この記事ではその方法をご紹介します。しかし、その前に、私たちは重要な事を押さえておきましょう。

免責事項: 私は暗号化の専門家ではありませんが、この記事で提供されている解決策にたどり着くために使用したソースについて色々な見解を述べています。私はみなさんがよりよく理解するために、これらのソースを読んだり、見たりすることを強くお勧めします。

OKです。少し話がそれましたが、始めましょう!

Encrypt

まずはデータを暗号化することから始めましょう。まず、キーとデータの引数を受け取るEncrypt関数を作成します。これを元に、キーを使って復号できるデータを暗号化します。まず、32個のランダムなバイトを使ってキーを生成し、後でパスワードに置き換えます。次に生成されたキーでデータを暗号化するコードを示しておきます。

import ("crypto/aes""crypto/cipher""crypto/rand")func Encrypt(key, data []byte) ([]byte, error) {blockCipher, err := aes.NewCipher(key)if err != nil {return nil, err}gcm, err := cipher.NewGCM(blockCipher)if err != nil {return nil, err}nonce := make([]byte, gcm.NonceSize())if _, err = rand.Read(nonce); err != nil {return nil, err}ciphertext := gcm.Seal(nonce, nonce, data, nil)return ciphertext, nil}

では、コードを確認して、何が行われているのかを見てみましょう。

func Encrypt(key, data []byte) ([]byte, error)

まず、Encrypt関数を作成することから始めます。その関数でキーとデータ引数を受け取ります。データ引数として、io.Readerの代わりにバイトスライスを使用します。一方、io.Readerを使用すると、io.Readerインターフェースを実装している他のすべてのタイプでEncrypt関数を使用することができるようになります。(Ryer 2015) しかし、データのストリームであるio.Readerの性質上、暗号文を復号化したいときには、その全体を見る必要があります。解決策としては、ストリームを個別のチャンクに分割することが考えられますが、これでは問題がより複雑化する可能性があります。.1 (Isom 2015)

blockCipher, err := aes.NewCipher(key)

提供したキーをベースにしたブロック暗号を初期化しています。ここでは、AES34 (Advanced Encryption Standard:高度暗号化標準) 暗号化アルゴリズムを実装した crypto/aes2 パッケージを使用しています。AES はシンメトリックキー暗号化(対称鍵暗号)アルゴリズムで、現代のユースケースでも十分に安全です。さらに、AES はほとんどのプラットフォームでハードウェアアクセラレーションを使用しているので、かなり高速で使用できます。(Tankersley 2016)

gcm, err := cipher.NewGCM(blockCipher)

ここではブロック暗号を特定のモードでラップしています。これは cipher.Block インターフェイスを直接使うべきではないからです。これはブロック暗号が16バイトのデータを暗号化するだけで、それ以上は何もしないからです。つまり、blockCiper.Encrypt() を呼び出しても最初の 16 バイトしか暗号化されないのです。そこで、その上にブロック暗号をラップする何かが必要になります。それらのことをモードと呼びます。ここでもいくつかのモードがありますが、ここでは「Galois Counter Mode (GCM) 5:認証子付き暗号モード」 を使用します。

GCMだけが認証された暗号化を提供しており、cipher.AEADインターフェイス(Authenticated Encryption with Associated Data:認証付き暗号)6を実装しています。認証された暗号化とは、あなたのデータが機密、秘密、暗号化されるだけでなく、改ざん防止にもなることを意味します。誰かが暗号を改ざんしても、それを正当に復号化することはできません。認証された暗号化を使用している場合にもし誰かがあなたのデータに手を加えても、復号化に失敗するだけです。(Tankersley 2016; Isom 2015)

nonce := make([]byte, gcm.NonceSize())if _, err = rand.Read(nonce); err != nil {return nil, err}

バイトを暗号化する前に、ランダム化された nonce を生成する必要があります。そして、その長さはGCMが指定します。nonceとは「一度だけ使用された数」を意味していて、繰り返し使用してはならないデータであり、特定のキーと組み合わせて一度だけ使用されます。つまり、キーとnonceの組み合わせを2回以上繰り返してはいけないということです。しかし、それをどうやって記録しているのでしょうか?nonce に十分に大きな数を使えば、このユースケースでは問題ないでしょう。(Isom 2015; Viega and Messier 2003, 134–35) これを実行するためにGoのcrypto/randパッケージを使用して、ランダム化されたバイトをnonceのバイトスライス7に読み込ませます。

encryptedData := gcm.Seal(nonce, nonce, data, nil)

データを暗号化する際に使用する nonce は、復号化する際にも必要になります。そのため、復号中にnonce を参照できるようにする必要があり、暗号化されたデータに追加するのが戦略の一つです。この例では、暗号化されたデータに nonce を追加します。この例では、暗号化されたデータにnonceを前置します。nonceをSeal関数の第一引数dstとして渡すことで、暗号化されたデータがそれに追加されます。nonceはシークレットで必要はなく、単にユニークであれさえすればいいので、これが可能なのです。(Tankersley 2016)

Decrypt

これでデータを暗号化できるようになったので、Decrypt関数を実装してみましょう。

import ("crypto/aes""crypto/cipher")func Decrypt(key, data []byte) ([]byte, error) {blockCipher, err := aes.NewCipher(key)if err != nil {return nil, err}gcm, err := cipher.NewGCM(blockCipher)if err != nil {return nil, err}nonce, ciphertext := data[:gcm.NonceSize()], data[gcm.NonceSize():]plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)if err != nil {return nil, err}return plaintext, nil}

もう一度、コードを確認してみましょう。大体はEncrypt関数と同じコードなので、異なる部分を確認してみましょう。

nonce, ciphertext := data[:gcm.NonceSize()], data[gcm.NonceSize():]

前のセクションで、gcm.Seal を使ってデータに nonce を付加して暗号文を作成したことを覚えていますか?今度はそれらを独立して使えるように分割する必要があります。そして、gcmが提供するnonceのサイズに基づいてデータをスライスすることで、これらの部分を作成していきます。

plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)

それではgcmを使います。

Open を使って暗号文を平文9に復号化します。

Key

Encrypt関数とDecrypt関数の両方にキーを渡していますが、まだ作っていないので、それをやってみましょう。

import ("crypto/rand")func GenerateKey() ([]byte, error) {key := make([]byte, 32)_, err := rand.Read(key)if err != nil {return nil, err}return key, nil}

ここでは、Goのcrypto/randパッケージを使ってランダムキーを生成しています。AESの場合は32バイトの長さのキーが必要なので、サイズ32のバイトスライスを作成します。次に、rand.Read()でランダムなバイト数10でスライスを埋めます。

これでいくつかのデータを暗号化して復号化するのに十分な量になったので、それをまとめてテストしてみましょう。

// crypto.gopackage mainimport ("crypto/aes""crypto/cipher""crypto/rand""encoding/hex""fmt""log")func Encrypt(key, data []byte) ([]byte, error) {blockCipher, err := aes.NewCipher(key)if err != nil {return nil, err}gcm, err := cipher.NewGCM(blockCipher)if err != nil {return nil, err}nonce := make([]byte, gcm.NonceSize())if _, err = rand.Read(nonce); err != nil {return nil, err}ciphertext := gcm.Seal(nonce, nonce, data, nil)return ciphertext, nil}func Decrypt(key, data []byte) ([]byte, error) {blockCipher, err := aes.NewCipher(key)if err != nil {return nil, err}gcm, err := cipher.NewGCM(blockCipher)if err != nil {return nil, err}nonce, ciphertext := data[:gcm.NonceSize()], data[gcm.NonceSize():]plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)if err != nil {return nil, err}return plaintext, nil}func GenerateKey() ([]byte, error) {key := make([]byte, 32)_, err := rand.Read(key)if err != nil {return nil, err}return key, nil}func main() {data := []byte("our super secret text")key, err := GenerateKey()if err != nil {log.Fatal(err)}ciphertext, err := Encrypt(key, data)if err != nil {log.Fatal(err)}fmt.Printf("ciphertext: %s\n", hex.EncodeToString(ciphertext))plaintext, err := Decrypt(key, ciphertext)if err != nil {log.Fatal(err)}fmt.Printf("plaintext: %s\n", plaintext)}

次のコードを使ってこの例を走らせることができます:

$ go run crypto.go

これで、ランダム化されたキーを使って データを暗号化したり復号化したりできるようになりました。最高ですよね。データを暗号化したり復号化したりできるキーが手に入りました。しかし、それはキーが今私たちのパスワードになり、自分でそれを選択することができなかったことを意味し、さらに、それは32バイトの長さを持っているということを意味します。

しかし、この記事の冒頭で述べたように、私たちは独自のキー、つまり私たちが使用することを選択したパスワードを提供することによって、データを暗号化したり復号化したりできるようにしたいと思います。それについては次のセクションで説明します。


Orangesys.ioでは、kuberneteの運用、DevOps、監視のお手伝いをさせていただいています。ぜひ私たちにおまかせください。

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app