Насколько безопасно мое шифрование с помощью пароля? (Голанг, AES256, pbkdf2, hmac)
Во-первых, я хочу сказать, что это всего лишь учебное упражнение, и я не собираюсь использовать его в производстве.
Я написал небольшое приложение на Голанге с двумя функциями: encrypt(plaintext string, password string)
а также decrypt(encrypted string, password string)
Шаги шифрования:
- Генерация случайных 256 битов для использования в качестве соли
- Генерация 128 битов для использования в качестве вектора инициализации
- Используйте PDKDF2 для генерации 32-битного ключа из пароля и соли
- Создайте 32-битный HMAC с ключом и открытым текстом и добавьте его в начало открытого текста
- Зашифруйте открытый текст hmac+ с помощью AES в режиме CFB
Возвращенный байтовый массив выглядит так:
[256 bit salt] [128 bit iv] encrypted([256 bit hmac] [plaintext])
При расшифровке:
- Извлеките соль и используйте ее с предоставленным паролем для вычисления ключа.
- Извлеките IV и расшифруйте зашифрованную часть зашифрованного текста.
- Извлечь Mac из расшифрованного значения
- Подтвердите 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 - это то, что вам нужно.
В противном случае, большинство из нижеприведенного все еще применимо:
- Симметричное шифрование не полезно для паролей. Не должно быть никаких причин, по которым вам нужен открытый текст - вам нужно заботиться только о сравнении хешей (или, точнее, производных ключей).
- PBKDF2, по сравнению с scrypt или bcrypt, не идеален (10002 раундов в 2015 году, вероятно, тоже немного низок). scrypt не хватает памяти и намного труднее распараллелить на GPU, и в 2015 году он имел достаточно долгий срок службы, чтобы сделать его более безопасным, чем bcrypt (вы все равно будете использовать bcrypt в тех случаях, когда библиотека scrypt для вашего языка не слишком хороша).
- У MAC-then-encrypt есть проблемы - вы должны зашифровать-затем-MAC.
- Учитывая № 3, вы должны использовать AES-GCM (режим счетчика Галуа) поверх AES-CBC + HMAC.
Go имеет отличный пакет bcrypt с простым в использовании API (генерирует соли для вас; надежно сравнивает).
Я также написал пакет scrypt, который отражает этот пакет, так как базовый пакет scrypt требует от вас проверки ваших собственных параметров и генерации ваших собственных солей.