Шифрование AES-256-CBC не совпадает между golang и узлом / php - PullRequest
0 голосов
/ 08 июня 2018

У меня возникли некоторые проблемы при попытке выяснить, почему мое шифрование отличается по ходу по сравнению с php и узлом.Я надеялся, что кто-нибудь поможет мне разобраться в различиях.Давайте предположим, что это данные:

открытый текст: hello big worldshello big worlds

ключ: jJr44P3WSM5F8AC573racFpzU5zj7Rg5

iv: 97iEhhtgVjoVwdUw

Вот результатшифрование в base64:

Возврат узла и PHP:

OTdpRWhodGdWam9Wd2RVd0OgJ+Z7pSCVioYq41721jarxqLKXN3PcnnY6/AOrHeEfsTxXfCgm2uUi+vmCAdpvw==

Go возвращает:

OTdpRWhodGdWam9Wd2RVd0OgJ+Z7pSCVioYq41721jarxqLKXN3PcnnY6/AOrHeE

Как видите, они почти идентичны, и это сводит меня с ума.Не могли бы вы, ребята, быстро взглянуть на приведенный ниже код шифрования и дать мне подсказки, в чем может быть проблема?

GO:

func EncryptString(plainstring string, keystring string, encFormat int, ivOverride bool) (string) {
    // Load your secret key from a safe place and reuse it across multiple
    // NewCipher calls. (Obviously don't use this example key for anything
    // real.) If you want to convert a passphrase to a key, use a suitable
    // package like bcrypt or scrypt.
    key := []byte(keystring)
    plaintext := []byte(plainstring)

    // CBC mode works on blocks so plaintexts may need to be padded to the
    // next whole block. For an example of such padding, see
    // https://tools.ietf.org/html/rfc5246#section-6.2.3.2. Here we'll
    // assume that the plaintext is already of the correct length.
    if len(plaintext)%aes.BlockSize != 0 {
        panic("plaintext is not a multiple of the block size")
    }

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

    // The IV needs to be unique, but not secure. Therefore it's common to
    // include it at the beginning of the ciphertext.
    ciphertext := make([]byte, aes.BlockSize+len(plaintext))
    iv := ciphertext[:aes.BlockSize]
    if _, err := io.ReadFull(bytes.NewReader([]byte("97iEhhtgVjoVwdUw")), iv); err != nil {
       panic(err)
    }

    mode := cipher.NewCBCEncrypter(block, iv)
    mode.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)

    // It's important to remember that ciphertexts must be authenticated
    // (i.e. by using crypto/hmac) as well as being encrypted in order to
    // be secure.

    return base64.StdEncoding.EncodeToString(ciphertext)
}

УЗЕЛ:

encryptString: function(string, key, fmt = null, ivOverride = false) {
    // Build an initialisation vector
    let iv;
    if(!ivOverride) {
        iv = crypto.randomBytes(IV_NUM_BYTES).toString('hex').slice(0,16);
    } else {
        iv = IV_OVERRIDE_VALUE; //97iEhhtgVjoVwdUw
    }
    // and encrypt
    let encryptor = crypto.createCipheriv('aes-256-cbc', key, iv);
    let encryptedData = encryptor.update(string, 'utf8', 'binary') + encryptor.final('binary');
    encryptedData = iv+''+encryptedData;
    encryptedData = Buffer.from(encryptedData, 'binary').toString('base64');

    return encryptedData;
}

Я заметил, что удаление encryptor.final('binary') приводит к тому, что два результата приводят к одному и тому же шифрованию, но php не поддерживает функцию .final ().Php uses open_ssl_encrypt() который, кажется, имеет встроенный. Есть ли способ добавить эквивалент в го?Ищу совет.Спасибо

1 Ответ

0 голосов
/ 08 июня 2018

Хорошо, мне удалось получить выход go, соответствующий вашим другим выводам, но я не на 100% ясен во всех деталях - в частности, почему версии PHP и Node ведут себя так, как они (ваш вывод изкак я объясню, версия Go кажется мне «правильным» результатом.

Моим первым наблюдением было то, что ваш вывод из Node и PHP был длиннее, чем версия Go, примерно на одну длину блокаи только хвостовой конец отличается.Это сказало мне, что каким-то образом эти версии дополняются на больше , чем версия Go.

Итак, я попытался дополнить версию Go в соответствии со схемой заполнения по умолчанию, используемой PHP и Node, PKCS # 7 .По сути, если вам нужно заполнить 5 байтами, то каждый из байтов заполнения должен быть равен 0x05, 6 байтов дополнены 0x06 и т. Д. Go по умолчанию aes.BlockSize равно 16, поэтому я попытался заполнить вашу входную строку с помощью16 0x10 байт.Это привело к правильному ответу!

Честно говоря, не заполнение ввода вообще, если оно уже выровнено по блокам, понятное поведение, но, по-видимому, Node и PHP следуют RFC 5652 и всегда добавляют заполнение (см. Редактирование), дажеесли им нужно добавить еще один целый блок только для заполнения.

Вот код Go, чтобы ваши выходные данные совпали:

package main

import (
    "bytes"
    "crypto/aes"
    "crypto/cipher"
    "encoding/base64"
    "fmt"
    "io"
)

// Based on Wikipedia: https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7
func PadToBlockSize(input string) string {
    paddingNeeded := aes.BlockSize - (len(input) % aes.BlockSize)
    if paddingNeeded >= 256 {
        panic("I'm too lazy to handle this case for the sake of an example :)")
    }

    if paddingNeeded == 0 {
        paddingNeeded = aes.BlockSize
    }

    // Inefficient, once again, this is an example only!
    for i := 0; i < paddingNeeded; i++ {
        input += string(byte(paddingNeeded))
    }
    return input
}

// (Identical to your code, I just deleted comments to save space)
func EncryptString(plainstring string, keystring string, encFormat int, ivOverride bool) string {
    key := []byte(keystring)
    plaintext := []byte(plainstring)
    if len(plaintext)%aes.BlockSize != 0 {
        panic("plaintext is not a multiple of the block size")
    }
    block, err := aes.NewCipher(key)
    if err != nil {
        panic(err)
    }
    ciphertext := make([]byte, aes.BlockSize+len(plaintext))
    iv := ciphertext[:aes.BlockSize]
    if _, err := io.ReadFull(bytes.NewReader([]byte("97iEhhtgVjoVwdUw")), iv); err != nil {
        panic(err)
    }
    mode := cipher.NewCBCEncrypter(block, iv)
    mode.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)
    return base64.StdEncoding.EncodeToString(ciphertext)
}

func main() {
    plaintext := "hello big worldshello big worlds"
    key := "jJr44P3WSM5F8AC573racFpzU5zj7Rg5"
    phpText := "OTdpRWhodGdWam9Wd2RVd0OgJ+Z7pSCVioYq41721jarxqLKXN3PcnnY6/AOrHeEfsTxXfCgm2uUi+vmCAdpvw=="

    fmt.Println("Go : " + EncryptString(PadToBlockSize(plaintext), key, 0, false))
    fmt.Println("PHP: " + phpText)
}

Редактировать:

На самом деле это выглядит такNode и PHP просто следуют RFC 5652 правильно, что требует дополнения всего ввода.Тот факт, что заполнение всегда будет присутствовать, даже если вход был выровнен по блокам, устраняет неоднозначность дешифрования.Go просто оставляет шаг заполнения для пользователя.

...