【问题标题】:How to rewrite rijndaelManaged function from C# to Go?如何将 rijndaelManaged 函数从 C# 重写为 Go?
【发布时间】:2021-09-11 20:46:25
【问题描述】:

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

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

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

我们有这个返回输出(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=

【问题讨论】:

  • 只是我的 2 个注释:您的 C# 代码使用 Rijndael 算法,选择的块长度为 256 位 = 32 字节。您在链接的 Go 代码中使用的 AES 算法的块长度为 128 位 = 16 字节,因此您需要找到支持 256 位/32 字节块长度的 Go 实现。第二:在您的 C# 代码中,您使用的是 static IV,它使您的完全加密 unsecure,所以我的建议是使用随机生成的 IV 并将其与密文发送给收件人进行解密。
  • C# 代码没有进行十六进制解码。不要那样做。此外,如果您希望它以与 C# 代码相同的方式工作,您应该将 IV 传递给 cipher.NewCBCEncrypter(block, iv) 而不是 iv
  • @Brannon 当您以这种方式指定 IV 时,控制台会出现以下错误:panic: cipher.NewCBCEncrypter: IV length must equal block size。我相信这就是 Michael 提到的,因为 Go 实现是 128bit/16byte,所以它不会起作用。此外,对于这种情况,我想使用静态 IV。那么,到底..不使用十六进制解码?获得一些示例会很棒,但我会尝试找到另一个 256bit/32byte 的 go 实现。

标签: c# go encryption


【解决方案1】:

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

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

静态 IV 是不安全的。出于安全原因,密钥/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# 代码。

【讨论】:

    猜你喜欢
    • 2019-10-03
    • 1970-01-01
    • 2016-01-21
    • 2021-12-24
    • 2022-11-02
    • 2011-09-01
    • 2021-11-12
    • 1970-01-01
    • 2020-07-16
    相关资源
    最近更新 更多