1

まず、これは単なる学習演習であり、本番環境で使用するつもりはありません。

Golang で次の 2 つの機能を持つ小さなアプリケーションを作成しましたencrypt(plaintext string, password string)decrypt(encrypted string, password string)

暗号化の手順は次のとおりです。

  1. ソルトとして使用するランダムな 256 ビットを生成します
  2. 初期化ベクトルとして使用する 128 ビットを生成します
  3. PDKDF2 を使用して、パスワードとソルトから 32 ビット キーを生成します。
  4. キーと平文を使用して 32 ビット HMAC を生成し、それを平文の先頭に追加します。
  5. CFB モードで AES を使用して hmac+plaintext を暗号化する

返されるバイト配列は次のようになります。

[256 bit salt] [128 bit iv] encrypted([256 bit hmac] [plaintext])

復号化時:

  1. ソルトを抽出し、提供されたパスワードで使用してキーを計算します
  2. IV を抽出し、暗号文の暗号化された部分を復号化します
  3. 復号化された値から mac を抽出します
  4. プレーンテキストで mac を検証する

私は本番プロジェクトで独自の暗号化スクリプトを使用するほどクレイジーではないので、これを行うライブラリを教えてください(比較的安全な単純なパスワード/メッセージ暗号化)

2 つの関数のソース コードは次のとおりです。

package main

import (
    "io"
    "crypto/rand"
    "crypto/cipher"
    "crypto/aes"
    "crypto/sha256"
    "crypto/hmac"
    "golang.org/x/crypto/pbkdf2"
)


const saltlen = 32
const keylen = 32
const iterations = 100002

// returns ciphertext of the following format:
// [32 bit salt][128 bit iv][encrypted plaintext]
func encrypt(plaintext string, password string) string {
    // allocate memory to hold the header of the ciphertext
    header := make([]byte, saltlen + aes.BlockSize)

    // generate salt
    salt := header[:saltlen]
    if _, err := io.ReadFull(rand.Reader, salt); err != nil {
        panic(err)
    }

    // generate initialization vector
    iv := header[saltlen:aes.BlockSize+saltlen]
    if _, err := io.ReadFull(rand.Reader, iv); err != nil {
        panic(err)
    }

    // generate a 32 bit key with the provided password
    key := pbkdf2.Key([]byte(password), salt, iterations, keylen, sha256.New)

    // generate a hmac for the message with the key
    mac := hmac.New(sha256.New, key)
    mac.Write([]byte(plaintext))
    hmac := mac.Sum(nil)

    // append this hmac to the plaintext
    plaintext = string(hmac) + plaintext

    //create the cipher
    block, err := aes.NewCipher(key)
    if err != nil {
        panic(err)
    }

    // allocate space for the ciphertext and write the header to it
    ciphertext := make([]byte, len(header) + len(plaintext))
    copy(ciphertext, header)

    // encrypt
    stream := cipher.NewCFBEncrypter(block, iv)
    stream.XORKeyStream(ciphertext[aes.BlockSize+saltlen:], []byte(plaintext))
    return string(ciphertext)
}

func decrypt(encrypted string, password string) string {
    ciphertext := []byte(encrypted)
    // get the salt from the ciphertext
    salt := ciphertext[:saltlen]
    // get the IV from the ciphertext
    iv := ciphertext[saltlen:aes.BlockSize+saltlen]
    // generate the key with the KDF
    key := pbkdf2.Key([]byte(password), salt, iterations, keylen, sha256.New)

    block, err := aes.NewCipher(key)
    if (err != nil) {
        panic(err)
    }

    if len(ciphertext) < aes.BlockSize {
        return ""
    }

    decrypted := ciphertext[saltlen+aes.BlockSize:]
    stream := cipher.NewCFBDecrypter(block, iv)
    stream.XORKeyStream(decrypted, decrypted)

    // extract hmac from plaintext
    extractedMac := decrypted[:32]
    plaintext := decrypted[32:]

    // validate the hmac
    mac := hmac.New(sha256.New, key)
    mac.Write(plaintext)
    expectedMac := mac.Sum(nil)
    if !hmac.Equal(extractedMac, expectedMac) {
        return ""
    }

    return string(plaintext)
}
4

1 に答える 1

8

質問はパスワードではなくメッセージの暗号化に関するものだったので注意してください: パスワードをハッシュするのではなく小さなメッセージを暗号化する場合は、Go のsecretboxパッケージ (NaCl 実装の一部として) が最適です。独自のものを作成するつもりなら (独自の開発環境内にとどまらない限り、そうしないことを強くお勧めします)、AES-GCM がここでの方法です。

それ以外の場合でも、以下のほとんどが適用されます。

  1. 対称暗号化はパスワードには役に立ちません。プレーンテキストを戻す必要がある理由はありません。ハッシュ (または、より正確には、派生キー) の比較のみを気にする必要があります。
  2. PBKDF2 は、scrypt や bcrypt と比較して、理想的ではありません (2015 年の 10002 ラウンドもおそらく少し少ないでしょう)。scrypt はメモリが多く、GPU で並列化するのがはるかに難しく、2015 年には十分に長い寿命があり、bcrypt よりも安全です (言語の scrypt ライブラリが優れていない場合でも、bcrypt を使用します)。 )。
  3. MAC-then-encryptには問題があります。encrypt-then-MAC を使用する必要があります。
  4. #3 を考えると、AES-CBC + HMAC よりも AES-GCM (Galois Counter Mode) を使用する必要があります。

Go には、使いやすい API を備えた優れたbcryptパッケージがあります (salt を生成し、安全に比較します)。

基になるscryptパッケージでは、独自のパラメーターを検証して独自のソルトを生成する必要があるため、そのパッケージをミラーリングするscrypt パッケージも作成しました。

于 2015-12-31T23:54:38.887 に答える