перейти на шифрование AES CBC

Проблема

Мне нужно перенести функцию с C# на GO, которая использует шифрование AES. очевидно, я должен получить тот же результат с GO, что и с C#

C#

кодовая скрипка

Я подготовил небольшую скрипку с C#

using System;
using System.Linq;
using System.Security.Cryptography;
using System.Text;

public class Program
{
    public static void Main()
    {
        var query = "csharp -> golang";
        var key = Encoding.UTF8.GetBytes("12345678901234567890123456789012");
        var iv = Encoding.UTF8.GetBytes("1234567890123456");
        using (var aes = (RijndaelManaged)RijndaelManaged.Create())
        {
            aes.KeySize = 256;
            aes.Mode = CipherMode.CBC;
            aes.Key = key;
            aes.IV = iv;
            using (var transform = aes.CreateEncryptor())
            {
                Console.WriteLine("query => " + query);
                var toEncodeByte = Encoding.UTF8.GetBytes(query);
                Console.WriteLine("toEncodeByte = " + ToString(toEncodeByte));
                var encrypted = transform.TransformFinalBlock(toEncodeByte, 0, toEncodeByte.Length);
                Console.WriteLine("encrypted = " + ToString(encrypted));
            }
        }
    }

    public static string ToString(byte[] b)
    {
        return "[" + String.Join(" ", b.Select(h => h.ToString())) + "]";
    }
}

консольный вывод

query => csharp -> golang
toEncodeByte = [99 115 104 97 114 112 32 45 62 32 103 111 108 97 110 103]
encrypted = [110 150 8 224 44 118 15 182 248 172 105 14 61 212 219 205 216 31 76 112 179 76 214 154 227 112 159 176 24 61 108 100]

GO

кодовая скрипка

я приготовил небольшую скрипку с GO

package main

import (
    "bytes"
    "crypto/aes"
    "crypto/cipher"
    "encoding/hex"
    "fmt"
)

func main() {
    query := "csharp -> golang"
    key := []byte("12345678901234567890123456789012")
    iv := []byte("1234567890123456")

    if len(key) != 32 {
        fmt.Printf("key len must be 16. its: %v\n", len(key))
    }
    if len(iv) != 16 {
        fmt.Printf("IV len must be 16. its: %v\n", len(iv))
    }
    var encrypted string
    toEncodeByte := []byte(query)
    fmt.Println("query =>", query)
    fmt.Println("toEncodeByte = ", toEncodeByte)
    toEncodeBytePadded := PKCS5Padding(toEncodeByte, len(key))

    // aes
    block, err := aes.NewCipher(key)
    if err != nil {
        fmt.Println("CBC Enctryping failed.")
    }
    ciphertext := make([]byte, len(toEncodeBytePadded))
    mode := cipher.NewCBCEncrypter(block, iv)
    mode.CryptBlocks(ciphertext, toEncodeBytePadded)
    encrypted = hex.EncodeToString(ciphertext)
    // end of aes
    fmt.Println("encrypted", []byte(encrypted))
}

func PKCS5Padding(ciphertext []byte, blockSize int) []byte {
    padding := (blockSize - len(ciphertext)%blockSize)
    padtext := bytes.Repeat([]byte{byte(padding)}, padding)
    return append(ciphertext, padtext...)
}

консольный вывод

query => csharp -> golang
toEncodeByte =  [99 115 104 97 114 112 32 45 62 32 103 111 108 97 110 103]
encrypted [54 101 57 54 48 56 101 48 50 99 55 54 48 102 98 54 102 56 97 99 54 57 48 101 51 100 100 52 100 98 99 100 100 56 49 102 52 99 55 48 98 51 52 99 100 54 57 97 101 51 55 48 57 102 98 48 49 56 51 100 54 99 54 52]

Резюме

Я заметил, что в C# входные данные не обязательно должны быть того же размера, что и block.
В GO это, кажется, не работает без заполнения (поэтому я добавил заполнение в GOcode), но результат отличается.
Решение для получения результата equal было бы отличным


person lumo    schedule 11.03.2021    source источник
comment
Режим заполнения по умолчанию в С# отличается от других приложений. Режим заполнения должен совпадать. Вы можете изменить режим заполнения в коде GO (чтобы соответствовать С#) или в коде С# (чтобы соответствовать GO).   -  person jdweng    schedule 11.03.2021
comment
Если это поможет: eli.thegreenplace.net/2019/ aes-шифрование файлов в пути   -  person Eli Bendersky    schedule 11.03.2021


Ответы (1)


Во-первых, шифротексты обоих кодов идентичны. Однако зашифрованный текст в коде Голанга преобразуется неправильно.

В коде C# содержимое byte[] печатается в десятичном формате. Чтобы получить эквивалентный вывод в коде Golang, содержимое []byte также должно быть напечатано в десятичном формате, что легко достигается с помощью:

fmt.Println("ciphertext ", ciphertext)

и производит следующий вывод:

ciphertext  [110 150 8 224 44 118 15 182 248 172 105 14 61 212 219 205 216 31 76 112 179 76 214 154 227 112 159 176 24 61 108 100]

и идентичен выходу кода C#.


В текущем коде зашифрованный текст сначала кодируется с помощью:

encrypted = hex.EncodeToString(ciphertext)

в шестнадцатеричную строку, которую легко проверить с помощью:

fmt.Println("encrypted (hex)", encrypted)

производя следующий вывод:

encrypted (hex) 6e9608e02c760fb6f8ac690e3dd4dbcdd81f4c70b34cd69ae3709fb0183d6c64

При преобразовании шестнадцатеричной строки в []byte с []byte(encrypted) происходит кодировка Utf8, которая удваивает размер данных, как результат:

fmt.Println("encrypted", []byte(encrypted))

в текущем коде показывает:

encrypted [54 101 57 54 48 56 101 48 50 99 55 54 48 102 98 54 102 56 97 99 54 57 48 101 51 100 100 52 100 98 99 100 100 56 49 102 52 99 55 48 98 51 52 99 100 54 57 97 101 51 55 48 57 102 98 48 49 56 51 100 54 99 54 52]

CBC — это режим блочного шифрования, т. е. длина открытого текста должна быть целым числом, кратным размеру блока (16 байт для AES). Если это не так, необходимо применить заполнение. Код C# неявно использует заполнение PKCS7. По этой причине также обрабатываются открытые тексты, не удовлетворяющие условию длины.

Напротив, в коде Golang заполнение должно выполняться явно, что достигается с помощью метода PKCS5Padding(), реализующего заполнение PKCS7. Второй аргумент метода PKCS5Padding() — это размер блока, который для AES составляет 16 байт. Для этого параметра на самом деле должно быть передано aes.BlockSize. В настоящее время здесь передается len(key), размер которого для AES-256 составляет 32 байта. Хотя это совместимо с кодом C# для текущей длины открытого текста, оно несовместимо с произвольной длиной открытого текста (например, 32 байта).


Следующий код содержит изменения и выходные данные, описанные выше:

package main

import (
    "bytes"
    "crypto/aes"
    "crypto/cipher"
    "encoding/hex"
    "fmt"
)

func main() {
    query := "csharp -> golang"
    key := []byte("12345678901234567890123456789012")
    iv := []byte("1234567890123456")

    if len(key) != 32 {
        fmt.Printf("key len must be 16. its: %v\n", len(key))
    }
    if len(iv) != 16 {
        fmt.Printf("IV len must be 16. its: %v\n", len(iv))
    }
    var encrypted string
    toEncodeByte := []byte(query)
    fmt.Println("query =>", query)
    fmt.Println("toEncodeByte = ", toEncodeByte)
    toEncodeBytePadded := PKCS5Padding(toEncodeByte, aes.BlockSize)

    // aes
    block, err := aes.NewCipher(key)
    if err != nil {
        fmt.Println("CBC Enctryping failed.")
    }
    ciphertext := make([]byte, len(toEncodeBytePadded))
    mode := cipher.NewCBCEncrypter(block, iv)
    mode.CryptBlocks(ciphertext, toEncodeBytePadded)
    encrypted = hex.EncodeToString(ciphertext)
    // end of aes
    fmt.Println("encrypted", []byte(encrypted))
    fmt.Println("encrypted (hex)", encrypted)
    fmt.Println("ciphertext", ciphertext)
    fmt.Println("aes.BlockSize", aes.BlockSize)
}

func PKCS5Padding(ciphertext []byte, blockSize int) []byte {
    padding := (blockSize - len(ciphertext)%blockSize)
    padtext := bytes.Repeat([]byte{byte(padding)}, padding)
    return append(ciphertext, padtext...)
}
person user 9014097    schedule 11.03.2021
comment
большое спасибо за идеальное объяснение! - person lumo; 12.03.2021