如何将 rijndaelManaged 函数从 C# 重写为 Go?

How to rewrite rijndaelManaged function from C# to Go?

可能只是我犯了一个我看不到的小错误。也许其他查看此内容的人可以弄清楚我做错了什么。

这是C#中的函数,我试图在Go中重写,objective是在调用函数时输出相同的值。

public static string NewEncrypt(string Input)
{
    RijndaelManaged rijndaelManaged = new RijndaelManaged();
    rijndaelManaged.KeySize = 256;
    rijndaelManaged.BlockSize = 256;
    rijndaelManaged.Padding = PaddingMode.PKCS7;
    rijndaelManaged.Key = Convert.FromBase64String(Convert.ToBase64String(Encoding.UTF8.GetBytes("095fc90fe8b18e8f243e4b07a9c0d170")));
    rijndaelManaged.IV = Convert.FromBase64String(Convert.ToBase64String(Encoding.UTF8.GetBytes("8bef55a546d27958ead1fdddba4d36ea")));
    ICryptoTransform transform = rijndaelManaged.CreateEncryptor(rijndaelManaged.Key, rijndaelManaged.IV);
    byte[] myArray = null;
    using (MemoryStream memoryStream = new MemoryStream())
    {
        using (CryptoStream cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Write))
        {
            byte[] bytes = Encoding.UTF8.GetBytes(Input);
            cryptoStream.Write(bytes, 0, bytes.Length);
        }
        myArray = memoryStream.ToArray();
    }
    return Convert.ToBase64String(myArray);
}

您可以使用以下方式调用它:

NewEncrypt("{\"randJsonList\":[ \"abc\" ], \"name\":\"baron\", \"support\":\"king\"}")

我们有这个 return 输出 (myArray):

DdSUyoYRYW/zDNSVaA1JZ39WqJt06qp0FiJUlCW5BbZWEt41GzsmtgVnGZuHigZNs7qKhI+kHAKMXL8EPnK1vg==

现在开始我的 Go 实现(我试图利用 GitHub 资源:https://gist.github.com/huyinghuan/7bf174017bf54efb91ece04a48589b22):

您可能会注意到的第一件事是我不知道在哪里可以使用全局 IV 变量,每次您 运行 这段代码都会显示不同的输出。我想输出和C#一样的结果,除非修改了输入字符串

package main

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

var KEY = []byte("095fc90fe8b18e8f243e4b07a9c0d170")
var IV = []byte("8bef55a546d27958ead1fdddba4d36ea")

func encrypt(myInput string) []byte {
    plaintext := []byte(myInput)

    // Create the AES cipher
    block, err := aes.NewCipher(KEY)
    if err != nil {
        panic(err)
    }

    plaintext, _ = pkcs7Pad(plaintext, block.BlockSize())
    // 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(rand.Reader, iv); err != nil {
        panic(err)
    }
    bm := cipher.NewCBCEncrypter(block, iv)
    bm.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)
    //stream := cipher.NewCFBEncrypter(block, iv)
    //stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext)

    return ciphertext
}

// pkcs7Pad right-pads the given byte slice with 1 to n bytes, where
// n is the block size. The size of the result is x times n, where x
// is at least 1.
func pkcs7Pad(b []byte, blocksize int) ([]byte, error) {
    if blocksize <= 0 {
        return nil, errors.New("invalid blocksize")
    }
    if b == nil || len(b) == 0 {
        return nil, errors.New("invalid PKCS7 data (empty or not padded)")
    }
    n := blocksize - (len(b) % blocksize)
    pb := make([]byte, len(b)+n)
    copy(pb, b)
    copy(pb[len(b):], bytes.Repeat([]byte{byte(n)}, n))
    return pb, nil
}

func main() {
    plainText := "{\"randJsonList\":[ \"abc\" ], \"name\":\"baron\", \"support\":\"king\"}"
    x := encrypt(plainText)

    outputString := base64.StdEncoding.EncodeToString(x)
    fmt.Println(outputString)
}

示例输出(与 C# 不同):

PS D:\Software\Git\repositories\tet> go run .\main.go
N+hm5TItq367eXAz+WbtKXhhhMAy4woEKSngTf6rGUt8GZce7LsUxaqNtheceGDZ2dK8Bx187x87NeRPC1UQ6lUokjy7t1MLU8NcCtjODCM=

PS D:\Software\Git\repositories\tet> go run .\main.go
OT/CngTVs2O4BR4czjvR3MLVPoKFH2dUtW8LsIDUgLXfikJrRKsvKGaf0JFe39Cwf1/00HP7mvmCure7+IO+vupzAtdLX6nTQt1KZGsNp4o=

PS D:\Software\Git\repositories\tet> go run .\main.go
yDRHxWTvjX4HnSW8jbao+0Mhf77zgRj9tKXA3MNtAoF1I3bRou5Sv4Ds+r0HRuiA7NkoBR57m4aCYcU6quYzQA3R0GCGB8TGUfrWS5PvMNU=

C# 代码使用块大小为 256 位的 Rijndael(参见注释)、静态 IV(参见注释)和 returns 仅密文(即没有前置 IV)。
根据定义,Go 代码应用 AES,块大小为 128 位,随机生成的 IV(代码中的静态 IV 被忽略)和 returns IV 和密文的串联。

AES 是 Rijndael 的子集。 Rijndael 以 32 位步长定义了 128 位和 256 位之间的不同块大小和密钥大小,请参阅 here。对于 AES,仅定义了 128 位的块大小和 128、192 和 256 位的密钥大小。请注意,标准是 AES 而不是 Rijndael,因此 AES 应该优于 Rijndael(许多库甚至不实现 Rijndael,而是实现 AES)。

静态 IV 是不安全的。出于安全原因,Key/IV 对不得重复。因此,一般情况下,使用固定密钥,每次加密都会生成一个随机IV。 IV 不是秘密的,它与密文一起传递到解密端,通常按 IV|ciphertext.

的顺序连接

因此,当前的 Go 代码是一个安全的实现(更安全的是经过身份验证的加密,例如通过 GCM),而 C# 代码不是。因此,将 C# 代码修改为在功能上等同于 Go 代码会更有意义。
然而,由于C#代码似乎是参考,因此需要在Go代码中进行以下更改,使其在功能上与C#代码相同:

  • 必须应用 Rijndael 而不是 AES。在以下示例中,使用了 pkg.go.dev/github.com/azihsoyn/rijndael256。为此,导入 "github.com/azihsoyn/rijndael256" 并正式将 aes 替换为 rijndael256。您当然可以应用其他实现。
  • 将应用静态 IV:bm := cipher.NewCBCEncrypter(block, IV)iv 及其填充物将与相关导入一起删除。
  • enecrypt()方法只返回密文:return ciphertext[rijndael256.BlockSize:].

以下Go代码给出了C#代码的结果:

package main

import (
    "bytes"
    "github.com/azihsoyn/rijndael256"
    "crypto/cipher"
    "encoding/base64"
    "errors"
    "fmt"
)

var KEY = []byte("095fc90fe8b18e8f243e4b07a9c0d170")
var IV = []byte("8bef55a546d27958ead1fdddba4d36ea")

func encrypt(myInput string) []byte {
    plaintext := []byte(myInput)

    // Create the AES cipher
    block, err := rijndael256.NewCipher(KEY)
    if err != nil {
        panic(err)
    }

    plaintext, _ = pkcs7Pad(plaintext, block.BlockSize())
    // 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, rijndael256.BlockSize+len(plaintext))
    bm := cipher.NewCBCEncrypter(block, IV)
    bm.CryptBlocks(ciphertext[rijndael256.BlockSize:], plaintext)

    return ciphertext[rijndael256.BlockSize:]
}

// pkcs7Pad right-pads the given byte slice with 1 to n bytes, where
// n is the block size. The size of the result is x times n, where x
// is at least 1.
func pkcs7Pad(b []byte, blocksize int) ([]byte, error) {
    if blocksize <= 0 {
        return nil, errors.New("invalid blocksize")
    }
    if b == nil || len(b) == 0 {
        return nil, errors.New("invalid PKCS7 data (empty or not padded)")
    }
    n := blocksize - (len(b) % blocksize)
    pb := make([]byte, len(b)+n)
    copy(pb, b)
    copy(pb[len(b):], bytes.Repeat([]byte{byte(n)}, n))
    return pb, nil
}

func main() {
    plainText := "{\"randJsonList\":[ \"abc\" ], \"name\":\"baron\", \"support\":\"king\"}"
    x := encrypt(plainText)

    outputString := base64.StdEncoding.EncodeToString(x)
    fmt.Println(outputString)
}

输出:

DdSUyoYRYW/zDNSVaA1JZ39WqJt06qp0FiJUlCW5BbZWEt41GzsmtgVnGZuHigZNs7qKhI+kHAKMXL8EPnK1vg==

等同于C#代码。