Насколько безопасно мое шифрование с помощью пароля? (Голанг, AES256, pbkdf2, hmac)

Во-первых, я хочу сказать, что это всего лишь учебное упражнение, и я не собираюсь использовать его в производстве.

Я написал небольшое приложение на Голанге с двумя функциями: encrypt(plaintext string, password string) а также decrypt(encrypted string, password string)

Шаги шифрования:

  1. Генерация случайных 256 битов для использования в качестве соли
  2. Генерация 128 битов для использования в качестве вектора инициализации
  3. Используйте PDKDF2 для генерации 32-битного ключа из пароля и соли
  4. Создайте 32-битный HMAC с ключом и открытым текстом и добавьте его в начало открытого текста
  5. Зашифруйте открытый текст hmac+ с помощью AES в режиме CFB

Возвращенный байтовый массив выглядит так:

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

При расшифровке:

  1. Извлеките соль и используйте ее с предоставленным паролем для вычисления ключа.
  2. Извлеките IV и расшифруйте зашифрованную часть зашифрованного текста.
  3. Извлечь Mac из расшифрованного значения
  4. Подтвердите Mac с открытым текстом

Я не настолько сумасшедший, чтобы использовать свой собственный сценарий шифрования в любых производственных проектах, поэтому, пожалуйста, укажите мне на любые библиотеки, которые делают это для меня (простое шифрование пароля / сообщения, которое является относительно безопасным)

Вот исходный код двух функций:

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)
}

1 ответ

Решение

Обратите внимание, поскольку речь шла о шифровании сообщений, а не паролей: если вы шифруете небольшие сообщения, а не хэшируете пароли, то пакет Secretbox от Go - как часть его реализации NaCl - является подходящим вариантом. Если вы намереваетесь развернуть свое собственное - и я настоятельно рекомендую против этого, если оно не находится в пределах вашей собственной среды разработки - тогда AES-GCM - это то, что вам нужно.

В противном случае, большинство из нижеприведенного все еще применимо:

  1. Симметричное шифрование не полезно для паролей. Не должно быть никаких причин, по которым вам нужен открытый текст - вам нужно заботиться только о сравнении хешей (или, точнее, производных ключей).
  2. PBKDF2, по сравнению с scrypt или bcrypt, не идеален (10002 раундов в 2015 году, вероятно, тоже немного низок). scrypt не хватает памяти и намного труднее распараллелить на GPU, и в 2015 году он имел достаточно долгий срок службы, чтобы сделать его более безопасным, чем bcrypt (вы все равно будете использовать bcrypt в тех случаях, когда библиотека scrypt для вашего языка не слишком хороша).
  3. У MAC-then-encrypt есть проблемы - вы должны зашифровать-затем-MAC.
  4. Учитывая № 3, вы должны использовать AES-GCM (режим счетчика Галуа) поверх AES-CBC + HMAC.

Go имеет отличный пакет bcrypt с простым в использовании API (генерирует соли для вас; надежно сравнивает).

Я также написал пакет scrypt, который отражает этот пакет, так как базовый пакет scrypt требует от вас проверки ваших собственных параметров и генерации ваших собственных солей.

Другие вопросы по тегам